@byline/host-tanstack-start 2.5.1 → 2.6.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.
- package/dist/admin-shell/admin-roles/container.js +38 -24
- package/dist/admin-shell/admin-roles/delete.js +9 -7
- package/dist/admin-shell/admin-roles/list.js +20 -16
- package/dist/admin-shell/admin-users/container.js +79 -56
- package/dist/admin-shell/admin-users/delete.js +10 -8
- package/dist/admin-shell/admin-users/list.js +27 -18
- package/dist/admin-shell/chrome/admin-app-bar.js +5 -2
- package/dist/admin-shell/chrome/breadcrumbs/breadcrumbs.js +3 -1
- package/dist/admin-shell/chrome/dashboard.js +13 -11
- package/dist/admin-shell/chrome/hamburger.js +3 -1
- package/dist/admin-shell/chrome/menu-drawer.js +7 -5
- package/dist/admin-shell/chrome/preview-toggle.js +5 -3
- package/dist/admin-shell/chrome/route-error.d.ts +3 -2
- package/dist/admin-shell/chrome/route-error.js +29 -22
- package/dist/admin-shell/chrome/sign-in-page.d.ts +16 -4
- package/dist/admin-shell/chrome/sign-in-page.js +38 -13
- package/dist/admin-shell/chrome/sign-in-page.module.js +1 -0
- package/dist/admin-shell/chrome/sign-in-page_module.css +8 -1
- package/dist/admin-shell/collections/api.js +6 -5
- package/dist/admin-shell/collections/create.js +12 -4
- package/dist/admin-shell/collections/edit.js +112 -37
- package/dist/admin-shell/collections/history.js +17 -12
- package/dist/admin-shell/collections/list.js +18 -13
- package/dist/admin-shell/collections/preview-link.d.ts +1 -10
- package/dist/admin-shell/collections/preview-link.js +9 -11
- package/dist/admin-shell/collections/resolve-preview-url.d.ts +34 -0
- package/dist/admin-shell/collections/resolve-preview-url.js +17 -0
- package/dist/admin-shell/collections/restore-version-modal.js +13 -14
- package/dist/admin-shell/collections/tanstack-navigation-guard.d.ts +1 -1
- package/dist/admin-shell/collections/view-menu.js +7 -5
- package/dist/i18n/index.d.ts +19 -0
- package/dist/i18n/index.js +4 -0
- package/dist/i18n/locale-cookie.d.ts +17 -0
- package/dist/i18n/locale-cookie.js +26 -0
- package/dist/i18n/locale-definitions.d.ts +29 -0
- package/dist/i18n/locale-definitions.js +27 -0
- package/dist/i18n/resolve-locale.d.ts +20 -0
- package/dist/i18n/resolve-locale.js +43 -0
- package/dist/i18n/server-translator.d.ts +33 -0
- package/dist/i18n/server-translator.js +19 -0
- package/dist/integrations/byline-admin-services.js +2 -0
- package/dist/integrations/byline-field-services.d.ts +3 -3
- package/dist/routes/create-admin-account-route.js +6 -3
- package/dist/routes/create-admin-dashboard-route.js +3 -1
- package/dist/routes/create-admin-layout-route.js +48 -25
- package/dist/routes/create-admin-permissions-route.js +4 -2
- package/dist/routes/create-admin-role-edit-route.js +5 -3
- package/dist/routes/create-admin-roles-list-route.js +4 -2
- package/dist/routes/create-admin-user-edit-route.js +5 -3
- package/dist/routes/create-admin-users-list-route.js +4 -2
- package/dist/routes/create-collection-api-route.js +5 -3
- package/dist/routes/create-collection-create-route.js +4 -2
- package/dist/routes/create-collection-edit-route.js +4 -2
- package/dist/routes/create-collection-history-route.js +5 -3
- package/dist/routes/create-collection-list-route.js +11 -5
- package/dist/routes/create-sign-in-route.js +10 -1
- package/dist/server-fns/admin-account/change-password.d.ts +1 -0
- package/dist/server-fns/admin-account/get.d.ts +1 -0
- package/dist/server-fns/admin-account/update.d.ts +1 -0
- package/dist/server-fns/admin-users/create.d.ts +1 -0
- package/dist/server-fns/admin-users/get.d.ts +1 -0
- package/dist/server-fns/admin-users/list.d.ts +1 -0
- package/dist/server-fns/admin-users/set-password.d.ts +1 -0
- package/dist/server-fns/admin-users/update.d.ts +1 -0
- package/dist/server-fns/auth/sign-in.js +18 -0
- package/dist/server-fns/i18n/get-active-locale.d.ts +8 -0
- package/dist/server-fns/i18n/get-active-locale.js +6 -0
- package/dist/server-fns/i18n/index.d.ts +10 -0
- package/dist/server-fns/i18n/index.js +2 -0
- package/dist/server-fns/i18n/set-locale.d.ts +25 -0
- package/dist/server-fns/i18n/set-locale.js +42 -0
- package/package.json +16 -7
- package/src/admin-shell/admin-roles/container.tsx +41 -31
- package/src/admin-shell/admin-roles/delete.tsx +10 -11
- package/src/admin-shell/admin-roles/list.tsx +29 -16
- package/src/admin-shell/admin-users/container.tsx +77 -50
- package/src/admin-shell/admin-users/delete.tsx +11 -12
- package/src/admin-shell/admin-users/list.tsx +39 -18
- package/src/admin-shell/chrome/admin-app-bar.tsx +5 -2
- package/src/admin-shell/chrome/breadcrumbs/breadcrumbs.tsx +3 -1
- package/src/admin-shell/chrome/dashboard.tsx +9 -3
- package/src/admin-shell/chrome/hamburger.tsx +3 -1
- package/src/admin-shell/chrome/menu-drawer.tsx +7 -5
- package/src/admin-shell/chrome/preview-toggle.tsx +6 -4
- package/src/admin-shell/chrome/route-error.tsx +39 -26
- package/src/admin-shell/chrome/sign-in-page.module.css +10 -1
- package/src/admin-shell/chrome/sign-in-page.tsx +46 -12
- package/src/admin-shell/collections/api.tsx +5 -1
- package/src/admin-shell/collections/create.tsx +10 -4
- package/src/admin-shell/collections/edit.tsx +79 -72
- package/src/admin-shell/collections/history.tsx +18 -12
- package/src/admin-shell/collections/list.tsx +25 -14
- package/src/admin-shell/collections/preview-link.tsx +20 -33
- package/src/admin-shell/collections/resolve-preview-url.test.node.ts +167 -0
- package/src/admin-shell/collections/resolve-preview-url.ts +67 -0
- package/src/admin-shell/collections/restore-version-modal.tsx +11 -12
- package/src/admin-shell/collections/tanstack-navigation-guard.ts +1 -1
- package/src/admin-shell/collections/view-menu.tsx +9 -5
- package/src/i18n/index.ts +26 -0
- package/src/i18n/locale-cookie.ts +68 -0
- package/src/i18n/locale-definitions.ts +48 -0
- package/src/i18n/resolve-locale.ts +96 -0
- package/src/i18n/server-translator.ts +60 -0
- package/src/integrations/byline-admin-services.ts +2 -0
- package/src/integrations/byline-field-services.ts +7 -3
- package/src/routes/create-admin-account-route.tsx +6 -4
- package/src/routes/create-admin-dashboard-route.tsx +5 -1
- package/src/routes/create-admin-layout-route.tsx +53 -20
- package/src/routes/create-admin-permissions-route.tsx +4 -2
- package/src/routes/create-admin-role-edit-route.tsx +5 -3
- package/src/routes/create-admin-roles-list-route.tsx +5 -2
- package/src/routes/create-admin-user-edit-route.tsx +5 -3
- package/src/routes/create-admin-users-list-route.tsx +4 -2
- package/src/routes/create-collection-api-route.tsx +5 -3
- package/src/routes/create-collection-create-route.tsx +7 -2
- package/src/routes/create-collection-edit-route.tsx +4 -2
- package/src/routes/create-collection-history-route.tsx +5 -3
- package/src/routes/create-collection-list-route.tsx +8 -10
- package/src/routes/create-sign-in-route.tsx +14 -1
- package/src/server-fns/auth/sign-in.ts +45 -0
- package/src/server-fns/i18n/get-active-locale.ts +26 -0
- package/src/server-fns/i18n/index.ts +11 -0
- package/src/server-fns/i18n/set-locale.ts +103 -0
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
import { useState } from 'react'
|
|
10
10
|
|
|
11
|
+
import { FormRenderer } from '@byline/admin/react'
|
|
11
12
|
import type { CollectionAdminConfig, CollectionDefinition } from '@byline/core'
|
|
12
13
|
import { getDefaultStatus, getWorkflowStatuses } from '@byline/core'
|
|
13
14
|
import type { AnyCollectionSchemaTypes } from '@byline/core/zod-schemas'
|
|
14
|
-
import {
|
|
15
|
+
import { useTranslation } from '@byline/i18n/react'
|
|
16
|
+
import { Container, Section, useToastManager } from '@byline/ui/react'
|
|
15
17
|
|
|
16
18
|
import {
|
|
17
19
|
copyDocumentToLocale,
|
|
@@ -47,12 +49,15 @@ export const EditView = ({
|
|
|
47
49
|
defaultContentLocale: string
|
|
48
50
|
}) => {
|
|
49
51
|
const toastManager = useToastManager()
|
|
52
|
+
const { t } = useTranslation('byline-admin')
|
|
50
53
|
const [_editState, setEditState] = useState<EditState>({
|
|
51
54
|
status: 'idle',
|
|
52
55
|
message: '',
|
|
53
56
|
})
|
|
54
57
|
const navigate = useNavigate()
|
|
55
58
|
const { labels, path, fields } = collectionDefinition
|
|
59
|
+
const singular = labels.singular
|
|
60
|
+
const singularLower = singular.toLowerCase()
|
|
56
61
|
|
|
57
62
|
// Compute the next forward workflow status for the status button.
|
|
58
63
|
const workflowStatuses = getWorkflowStatuses(collectionDefinition)
|
|
@@ -77,9 +82,10 @@ export const EditView = ({
|
|
|
77
82
|
await updateDocumentStatus({
|
|
78
83
|
data: { collection: path, id: String(initialData.id), status },
|
|
79
84
|
})
|
|
85
|
+
const description = t('collections.edit.statusChangedDescription', { status })
|
|
80
86
|
toastManager.add({
|
|
81
|
-
title:
|
|
82
|
-
description
|
|
87
|
+
title: t('collections.edit.statusUpdateTitle', { label: singular }),
|
|
88
|
+
description,
|
|
83
89
|
data: {
|
|
84
90
|
intent: 'success',
|
|
85
91
|
iconType: 'success',
|
|
@@ -87,10 +93,7 @@ export const EditView = ({
|
|
|
87
93
|
close: true,
|
|
88
94
|
},
|
|
89
95
|
})
|
|
90
|
-
setEditState({
|
|
91
|
-
status: 'success',
|
|
92
|
-
message: `Status changed to "${status}"`,
|
|
93
|
-
})
|
|
96
|
+
setEditState({ status: 'success', message: description })
|
|
94
97
|
// Refresh the page to reflect the new status.
|
|
95
98
|
navigate({
|
|
96
99
|
to: '/admin/collections/$collection/$id' as never,
|
|
@@ -99,9 +102,12 @@ export const EditView = ({
|
|
|
99
102
|
})
|
|
100
103
|
} catch (err) {
|
|
101
104
|
console.error('Status change error:', err)
|
|
105
|
+
const description = t('collections.edit.statusChangeFailedDescription', {
|
|
106
|
+
message: (err as Error).message,
|
|
107
|
+
})
|
|
102
108
|
toastManager.add({
|
|
103
|
-
title:
|
|
104
|
-
description
|
|
109
|
+
title: t('collections.edit.statusUpdateTitle', { label: singular }),
|
|
110
|
+
description,
|
|
105
111
|
data: {
|
|
106
112
|
intent: 'danger',
|
|
107
113
|
iconType: 'danger',
|
|
@@ -109,10 +115,7 @@ export const EditView = ({
|
|
|
109
115
|
close: true,
|
|
110
116
|
},
|
|
111
117
|
})
|
|
112
|
-
setEditState({
|
|
113
|
-
status: 'failed',
|
|
114
|
-
message: `Failed to change status: ${(err as Error).message}`,
|
|
115
|
-
})
|
|
118
|
+
setEditState({ status: 'failed', message: description })
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
|
|
@@ -131,9 +134,10 @@ export const EditView = ({
|
|
|
131
134
|
const handleUnpublish = async () => {
|
|
132
135
|
try {
|
|
133
136
|
await unpublishDocument({ data: { collection: path, id: String(initialData.id) } })
|
|
137
|
+
const description = t('collections.edit.unpublishedDescription')
|
|
134
138
|
toastManager.add({
|
|
135
|
-
title:
|
|
136
|
-
description
|
|
139
|
+
title: t('collections.edit.unpublishTitle', { label: singular }),
|
|
140
|
+
description,
|
|
137
141
|
data: {
|
|
138
142
|
intent: 'success',
|
|
139
143
|
iconType: 'success',
|
|
@@ -141,10 +145,7 @@ export const EditView = ({
|
|
|
141
145
|
close: true,
|
|
142
146
|
},
|
|
143
147
|
})
|
|
144
|
-
setEditState({
|
|
145
|
-
status: 'success',
|
|
146
|
-
message: 'Published version has been taken offline.',
|
|
147
|
-
})
|
|
148
|
+
setEditState({ status: 'success', message: description })
|
|
148
149
|
navigate({
|
|
149
150
|
to: '/admin/collections/$collection/$id' as never,
|
|
150
151
|
params: { collection: path, id: String(initialData.id) },
|
|
@@ -152,9 +153,12 @@ export const EditView = ({
|
|
|
152
153
|
})
|
|
153
154
|
} catch (err) {
|
|
154
155
|
console.error('Unpublish error:', err)
|
|
156
|
+
const description = t('collections.edit.unpublishFailedDescription', {
|
|
157
|
+
message: (err as Error).message,
|
|
158
|
+
})
|
|
155
159
|
toastManager.add({
|
|
156
|
-
title:
|
|
157
|
-
description
|
|
160
|
+
title: t('collections.edit.unpublishTitle', { label: singular }),
|
|
161
|
+
description,
|
|
158
162
|
data: {
|
|
159
163
|
intent: 'danger',
|
|
160
164
|
iconType: 'danger',
|
|
@@ -162,10 +166,7 @@ export const EditView = ({
|
|
|
162
166
|
close: true,
|
|
163
167
|
},
|
|
164
168
|
})
|
|
165
|
-
setEditState({
|
|
166
|
-
status: 'failed',
|
|
167
|
-
message: `Failed to unpublish: ${(err as Error).message}`,
|
|
168
|
-
})
|
|
169
|
+
setEditState({ status: 'failed', message: description })
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
@@ -174,11 +175,12 @@ export const EditView = ({
|
|
|
174
175
|
const result = await duplicateCollectionDocument({
|
|
175
176
|
data: { collection: path, id: String(initialData.id) },
|
|
176
177
|
})
|
|
178
|
+
const description = result.pathRetried
|
|
179
|
+
? t('collections.edit.duplicatedAutoPathDescription', { path: result.newPath })
|
|
180
|
+
: t('collections.edit.duplicatedPathDescription', { path: result.newPath })
|
|
177
181
|
toastManager.add({
|
|
178
|
-
title:
|
|
179
|
-
description
|
|
180
|
-
? `Created with auto-generated path "${result.newPath}" (the preferred slug was already in use).`
|
|
181
|
-
: `Created with path "${result.newPath}". Update the title and path in the new document.`,
|
|
182
|
+
title: t('collections.edit.duplicatedTitle', { label: singular }),
|
|
183
|
+
description,
|
|
182
184
|
data: {
|
|
183
185
|
intent: 'success',
|
|
184
186
|
iconType: 'success',
|
|
@@ -188,7 +190,7 @@ export const EditView = ({
|
|
|
188
190
|
})
|
|
189
191
|
setEditState({
|
|
190
192
|
status: 'success',
|
|
191
|
-
message:
|
|
193
|
+
message: t('collections.edit.duplicatedSuccessMessage', { label: singular }),
|
|
192
194
|
})
|
|
193
195
|
// Navigate to the new document's edit view.
|
|
194
196
|
navigate({
|
|
@@ -197,9 +199,12 @@ export const EditView = ({
|
|
|
197
199
|
})
|
|
198
200
|
} catch (err) {
|
|
199
201
|
console.error('Duplicate error:', err)
|
|
202
|
+
const description = t('collections.edit.duplicateFailedDescription', {
|
|
203
|
+
message: (err as Error).message,
|
|
204
|
+
})
|
|
200
205
|
toastManager.add({
|
|
201
|
-
title:
|
|
202
|
-
description
|
|
206
|
+
title: t('collections.edit.duplicateTitle', { label: singular }),
|
|
207
|
+
description,
|
|
203
208
|
data: {
|
|
204
209
|
intent: 'danger',
|
|
205
210
|
iconType: 'danger',
|
|
@@ -207,10 +212,7 @@ export const EditView = ({
|
|
|
207
212
|
close: true,
|
|
208
213
|
},
|
|
209
214
|
})
|
|
210
|
-
setEditState({
|
|
211
|
-
status: 'failed',
|
|
212
|
-
message: `Failed to duplicate: ${(err as Error).message}`,
|
|
213
|
-
})
|
|
215
|
+
setEditState({ status: 'failed', message: description })
|
|
214
216
|
}
|
|
215
217
|
}
|
|
216
218
|
|
|
@@ -235,12 +237,20 @@ export const EditView = ({
|
|
|
235
237
|
contentLocales.find((l) => l.code === result.sourceLocale)?.label ?? result.sourceLocale
|
|
236
238
|
const targetLabel =
|
|
237
239
|
contentLocales.find((l) => l.code === result.targetLocale)?.label ?? result.targetLocale
|
|
240
|
+
const description =
|
|
241
|
+
result.fieldsUpdated > 0
|
|
242
|
+
? t('collections.edit.copiedFieldsDescription', {
|
|
243
|
+
count: result.fieldsUpdated,
|
|
244
|
+
source: sourceLabel,
|
|
245
|
+
target: targetLabel,
|
|
246
|
+
})
|
|
247
|
+
: t('collections.edit.copiedNoFieldsDescription', {
|
|
248
|
+
source: sourceLabel,
|
|
249
|
+
target: targetLabel,
|
|
250
|
+
})
|
|
238
251
|
toastManager.add({
|
|
239
|
-
title:
|
|
240
|
-
description
|
|
241
|
-
result.fieldsUpdated > 0
|
|
242
|
-
? `Copied ${result.fieldsUpdated} field${result.fieldsUpdated === 1 ? '' : 's'} from ${sourceLabel} to ${targetLabel}.`
|
|
243
|
-
: `No fields needed copying from ${sourceLabel} to ${targetLabel} under the current rule.`,
|
|
252
|
+
title: t('collections.edit.copyToLocaleTitle', { label: singular }),
|
|
253
|
+
description,
|
|
244
254
|
data: {
|
|
245
255
|
intent: 'success',
|
|
246
256
|
iconType: 'success',
|
|
@@ -250,7 +260,10 @@ export const EditView = ({
|
|
|
250
260
|
})
|
|
251
261
|
setEditState({
|
|
252
262
|
status: 'success',
|
|
253
|
-
message:
|
|
263
|
+
message: t('collections.edit.copiedSuccessMessage', {
|
|
264
|
+
source: sourceLabel,
|
|
265
|
+
target: targetLabel,
|
|
266
|
+
}),
|
|
254
267
|
})
|
|
255
268
|
// Switch the form to the target locale so the editor sees the
|
|
256
269
|
// copied content immediately.
|
|
@@ -261,9 +274,12 @@ export const EditView = ({
|
|
|
261
274
|
})
|
|
262
275
|
} catch (err) {
|
|
263
276
|
console.error('Copy to locale error:', err)
|
|
277
|
+
const description = t('collections.edit.copyFailedDescription', {
|
|
278
|
+
message: (err as Error).message,
|
|
279
|
+
})
|
|
264
280
|
toastManager.add({
|
|
265
|
-
title:
|
|
266
|
-
description
|
|
281
|
+
title: t('collections.edit.copyToLocaleTitle', { label: singular }),
|
|
282
|
+
description,
|
|
267
283
|
data: {
|
|
268
284
|
intent: 'danger',
|
|
269
285
|
iconType: 'danger',
|
|
@@ -271,19 +287,17 @@ export const EditView = ({
|
|
|
271
287
|
close: true,
|
|
272
288
|
},
|
|
273
289
|
})
|
|
274
|
-
setEditState({
|
|
275
|
-
status: 'failed',
|
|
276
|
-
message: `Failed to copy: ${(err as Error).message}`,
|
|
277
|
-
})
|
|
290
|
+
setEditState({ status: 'failed', message: description })
|
|
278
291
|
}
|
|
279
292
|
}
|
|
280
293
|
|
|
281
294
|
const handleDelete = async () => {
|
|
282
295
|
try {
|
|
283
296
|
await deleteDocument({ data: { collection: path, id: String(initialData.id) } })
|
|
297
|
+
const description = t('collections.edit.deletedDescription', { label: singular })
|
|
284
298
|
toastManager.add({
|
|
285
|
-
title:
|
|
286
|
-
description
|
|
299
|
+
title: t('collections.edit.deleteTitle', { label: singular }),
|
|
300
|
+
description,
|
|
287
301
|
data: {
|
|
288
302
|
intent: 'success',
|
|
289
303
|
iconType: 'success',
|
|
@@ -291,10 +305,7 @@ export const EditView = ({
|
|
|
291
305
|
close: true,
|
|
292
306
|
},
|
|
293
307
|
})
|
|
294
|
-
setEditState({
|
|
295
|
-
status: 'success',
|
|
296
|
-
message: `${labels.singular} has been deleted.`,
|
|
297
|
-
})
|
|
308
|
+
setEditState({ status: 'success', message: description })
|
|
298
309
|
// Navigate back to the collection list after deletion.
|
|
299
310
|
navigate({
|
|
300
311
|
to: '/admin/collections/$collection' as never,
|
|
@@ -302,9 +313,12 @@ export const EditView = ({
|
|
|
302
313
|
})
|
|
303
314
|
} catch (err) {
|
|
304
315
|
console.error('Delete error:', err)
|
|
316
|
+
const description = t('collections.edit.deleteFailedDescription', {
|
|
317
|
+
message: (err as Error).message,
|
|
318
|
+
})
|
|
305
319
|
toastManager.add({
|
|
306
|
-
title:
|
|
307
|
-
description
|
|
320
|
+
title: t('collections.edit.deleteTitle', { label: singular }),
|
|
321
|
+
description,
|
|
308
322
|
data: {
|
|
309
323
|
intent: 'danger',
|
|
310
324
|
iconType: 'danger',
|
|
@@ -312,10 +326,7 @@ export const EditView = ({
|
|
|
312
326
|
close: true,
|
|
313
327
|
},
|
|
314
328
|
})
|
|
315
|
-
setEditState({
|
|
316
|
-
status: 'failed',
|
|
317
|
-
message: `Failed to delete: ${(err as Error).message}`,
|
|
318
|
-
})
|
|
329
|
+
setEditState({ status: 'failed', message: description })
|
|
319
330
|
}
|
|
320
331
|
}
|
|
321
332
|
|
|
@@ -342,9 +353,10 @@ export const EditView = ({
|
|
|
342
353
|
},
|
|
343
354
|
})
|
|
344
355
|
|
|
356
|
+
const description = t('collections.edit.updatedDescription', { label: singularLower })
|
|
345
357
|
toastManager.add({
|
|
346
|
-
title:
|
|
347
|
-
description
|
|
358
|
+
title: t('collections.edit.updateTitle', { label: singular }),
|
|
359
|
+
description,
|
|
348
360
|
data: {
|
|
349
361
|
intent: 'success',
|
|
350
362
|
iconType: 'success',
|
|
@@ -353,10 +365,7 @@ export const EditView = ({
|
|
|
353
365
|
},
|
|
354
366
|
})
|
|
355
367
|
|
|
356
|
-
setEditState({
|
|
357
|
-
status: 'success',
|
|
358
|
-
message: `Successfully updated ${labels.singular.toLowerCase()}`,
|
|
359
|
-
})
|
|
368
|
+
setEditState({ status: 'success', message: description })
|
|
360
369
|
|
|
361
370
|
// Re-navigate to the same route so the loader re-fetches the document.
|
|
362
371
|
// The new version will have a fresh version ID, draft status, and
|
|
@@ -368,9 +377,10 @@ export const EditView = ({
|
|
|
368
377
|
})
|
|
369
378
|
} catch (err) {
|
|
370
379
|
console.error('Network error:', err)
|
|
380
|
+
const description = t('collections.edit.updateFailedDescription', { label: singularLower })
|
|
371
381
|
toastManager.add({
|
|
372
|
-
title:
|
|
373
|
-
description
|
|
382
|
+
title: t('collections.edit.updateTitle', { label: singular }),
|
|
383
|
+
description,
|
|
374
384
|
data: {
|
|
375
385
|
intent: 'danger',
|
|
376
386
|
iconType: 'danger',
|
|
@@ -379,10 +389,7 @@ export const EditView = ({
|
|
|
379
389
|
},
|
|
380
390
|
})
|
|
381
391
|
|
|
382
|
-
setEditState({
|
|
383
|
-
status: 'failed',
|
|
384
|
-
message: `An error occurred while updating ${labels.singular.toLowerCase()}`,
|
|
385
|
-
})
|
|
392
|
+
setEditState({ status: 'failed', message: description })
|
|
386
393
|
}
|
|
387
394
|
}
|
|
388
395
|
|
|
@@ -9,19 +9,19 @@
|
|
|
9
9
|
import { lazy, Suspense, useState } from 'react'
|
|
10
10
|
import { useParams, useRouterState } from '@tanstack/react-router'
|
|
11
11
|
|
|
12
|
+
import { renderFormatted, StatusBadge } from '@byline/admin/react'
|
|
12
13
|
import { useBylineAdminServices } from '@byline/admin/services'
|
|
13
14
|
import type { CollectionAdminConfig, CollectionDefinition, WorkflowStatus } from '@byline/core'
|
|
14
15
|
import type { AnyCollectionSchemaTypes } from '@byline/core/zod-schemas'
|
|
16
|
+
import { useTranslation } from '@byline/i18n/react'
|
|
15
17
|
import {
|
|
16
18
|
Button,
|
|
17
19
|
CloseIcon,
|
|
18
20
|
Container,
|
|
19
21
|
IconButton,
|
|
20
22
|
Modal,
|
|
21
|
-
renderFormatted,
|
|
22
23
|
Section,
|
|
23
24
|
Select,
|
|
24
|
-
StatusBadge,
|
|
25
25
|
Table,
|
|
26
26
|
} from '@byline/ui/react'
|
|
27
27
|
import cx from 'classnames'
|
|
@@ -48,7 +48,7 @@ function getColumnValue(document: any, fieldName: string): any {
|
|
|
48
48
|
|
|
49
49
|
// Lazy-load DiffModal because react-diff-viewer-continued uses a web worker
|
|
50
50
|
// bundle that cannot be resolved by Node during SSR.
|
|
51
|
-
const DiffModal = lazy(() => import('@byline/
|
|
51
|
+
const DiffModal = lazy(() => import('@byline/admin/react').then((m) => ({ default: m.DiffModal })))
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Safely extract a displayable string from a field value that may be a plain
|
|
@@ -116,6 +116,7 @@ export const HistoryView = ({
|
|
|
116
116
|
})
|
|
117
117
|
const navigate = useNavigate()
|
|
118
118
|
const { getCollectionDocumentVersion } = useBylineAdminServices()
|
|
119
|
+
const { t } = useTranslation('byline-admin')
|
|
119
120
|
const columns = adminConfig?.columns || []
|
|
120
121
|
const { labels } = collectionDefinition
|
|
121
122
|
const location = useRouterState({ select: (s) => s.location })
|
|
@@ -152,7 +153,8 @@ export const HistoryView = ({
|
|
|
152
153
|
<Container>
|
|
153
154
|
<div className={cx('byline-coll-history-head', styles.head)}>
|
|
154
155
|
<h2 className={cx('byline-coll-history-title', styles.title)}>
|
|
155
|
-
{
|
|
156
|
+
{t('collections.history.title', { label: labels.singular })}{' '}
|
|
157
|
+
<Stats total={data?.meta.total} />
|
|
156
158
|
</h2>
|
|
157
159
|
<ViewMenu
|
|
158
160
|
collection={collection}
|
|
@@ -174,7 +176,7 @@ export const HistoryView = ({
|
|
|
174
176
|
showFirstButton
|
|
175
177
|
showLastButton
|
|
176
178
|
componentName="pagerTop"
|
|
177
|
-
aria-label=
|
|
179
|
+
aria-label={t('collections.list.pagerTopAriaLabel')}
|
|
178
180
|
/>
|
|
179
181
|
</div>
|
|
180
182
|
<Table.Container className={cx('byline-coll-history-table-wrap', styles.tableWrap)}>
|
|
@@ -229,8 +231,8 @@ export const HistoryView = ({
|
|
|
229
231
|
size="xs"
|
|
230
232
|
variant="outlined"
|
|
231
233
|
intent="noeffect"
|
|
232
|
-
aria-label=
|
|
233
|
-
title=
|
|
234
|
+
aria-label={t('collections.history.compareAriaLabel')}
|
|
235
|
+
title={t('collections.history.compareTitle')}
|
|
234
236
|
className={cx(
|
|
235
237
|
'byline-coll-history-version-button',
|
|
236
238
|
styles.versionButton
|
|
@@ -349,9 +351,9 @@ export const HistoryView = ({
|
|
|
349
351
|
'byline-coll-history-restore-button',
|
|
350
352
|
styles.restoreButton
|
|
351
353
|
)}
|
|
352
|
-
title=
|
|
354
|
+
title={t('collections.history.restoreButtonTitle')}
|
|
353
355
|
>
|
|
354
|
-
|
|
356
|
+
{t('collections.history.restoreButton')}
|
|
355
357
|
</Button>
|
|
356
358
|
) : null}
|
|
357
359
|
</Table.Cell>,
|
|
@@ -394,7 +396,7 @@ export const HistoryView = ({
|
|
|
394
396
|
showFirstButton
|
|
395
397
|
showLastButton
|
|
396
398
|
componentName="pagerBottom"
|
|
397
|
-
aria-label=
|
|
399
|
+
aria-label={t('collections.list.pagerBottomAriaLabel')}
|
|
398
400
|
/>
|
|
399
401
|
</div>
|
|
400
402
|
</Container>
|
|
@@ -425,8 +427,12 @@ export const HistoryView = ({
|
|
|
425
427
|
<Modal.Header
|
|
426
428
|
className={cx('byline-coll-history-restore-modal-head', styles.restoreModalHead)}
|
|
427
429
|
>
|
|
428
|
-
<h3 className="m-0">
|
|
429
|
-
<IconButton
|
|
430
|
+
<h3 className="m-0">{t('collections.history.restoreModalTitle')}</h3>
|
|
431
|
+
<IconButton
|
|
432
|
+
aria-label={t('common.actions.close')}
|
|
433
|
+
size="xs"
|
|
434
|
+
onClick={() => setRestoreTarget(null)}
|
|
435
|
+
>
|
|
430
436
|
<CloseIcon width="14px" height="14px" svgClassName="white-icon" />
|
|
431
437
|
</IconButton>
|
|
432
438
|
</Modal.Header>
|
|
@@ -9,19 +9,20 @@
|
|
|
9
9
|
import { useEffect, useMemo, useState } from 'react'
|
|
10
10
|
import { useRouter, useRouterState } from '@tanstack/react-router'
|
|
11
11
|
|
|
12
|
+
import { renderFormatted, StatusBadge } from '@byline/admin/react'
|
|
12
13
|
import type { ColumnDefinition, WorkflowStatus } from '@byline/core'
|
|
13
14
|
import type { AnyCollectionSchemaTypes } from '@byline/core/zod-schemas'
|
|
15
|
+
import type { UseTranslationReturn } from '@byline/i18n/react'
|
|
16
|
+
import { useTranslation } from '@byline/i18n/react'
|
|
14
17
|
import {
|
|
15
18
|
Container,
|
|
16
19
|
GripperVerticalIcon,
|
|
17
20
|
IconButton,
|
|
18
21
|
LoaderRing,
|
|
19
22
|
PlusIcon,
|
|
20
|
-
renderFormatted,
|
|
21
23
|
Search,
|
|
22
24
|
Section,
|
|
23
25
|
Select,
|
|
24
|
-
StatusBadge,
|
|
25
26
|
Table,
|
|
26
27
|
useToastManager,
|
|
27
28
|
} from '@byline/ui/react'
|
|
@@ -108,10 +109,12 @@ function padRows(value: number) {
|
|
|
108
109
|
function SortableTableRow({
|
|
109
110
|
id,
|
|
110
111
|
disabled,
|
|
112
|
+
t,
|
|
111
113
|
children,
|
|
112
114
|
}: {
|
|
113
115
|
id: string
|
|
114
116
|
disabled: boolean
|
|
117
|
+
t: UseTranslationReturn['t']
|
|
115
118
|
children: React.ReactNode
|
|
116
119
|
}) {
|
|
117
120
|
const { setNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({
|
|
@@ -132,7 +135,9 @@ function SortableTableRow({
|
|
|
132
135
|
type="button"
|
|
133
136
|
className={cx('byline-coll-list-drag-handle', styles.dragHandle)}
|
|
134
137
|
aria-label={
|
|
135
|
-
disabled
|
|
138
|
+
disabled
|
|
139
|
+
? t('collections.list.dragDisabledAriaLabel')
|
|
140
|
+
: t('collections.list.dragHandleAriaLabel')
|
|
136
141
|
}
|
|
137
142
|
disabled={disabled}
|
|
138
143
|
{...attributes}
|
|
@@ -166,6 +171,7 @@ export const ListView = ({
|
|
|
166
171
|
const navigate = useNavigate()
|
|
167
172
|
const router = useRouter()
|
|
168
173
|
const toastManager = useToastManager()
|
|
174
|
+
const { t } = useTranslation('byline-admin')
|
|
169
175
|
const location = useRouterState({ select: (s) => s.location })
|
|
170
176
|
|
|
171
177
|
// Local mirror of the loader docs so drag-and-drop can paint the new
|
|
@@ -229,8 +235,8 @@ export const ListView = ({
|
|
|
229
235
|
} catch (_err) {
|
|
230
236
|
setLocalDocs(previousDocs)
|
|
231
237
|
toastManager.add({
|
|
232
|
-
title: '
|
|
233
|
-
description: '
|
|
238
|
+
title: t('collections.list.reorderFailedToast'),
|
|
239
|
+
description: t('collections.list.reorderFailedDescription'),
|
|
234
240
|
data: { intent: 'danger', iconType: 'danger', icon: true, close: true },
|
|
235
241
|
})
|
|
236
242
|
} finally {
|
|
@@ -244,10 +250,10 @@ export const ListView = ({
|
|
|
244
250
|
// exceeded" inside SelectRoot after navigations that cause a re-render).
|
|
245
251
|
const statusItems = useMemo(
|
|
246
252
|
() => [
|
|
247
|
-
{ value: '_all', label: '
|
|
253
|
+
{ value: '_all', label: t('collections.list.statusFilterAll') },
|
|
248
254
|
...(workflowStatuses?.map((ws) => ({ value: ws.name, label: ws.label ?? ws.name })) ?? []),
|
|
249
255
|
],
|
|
250
|
-
[workflowStatuses]
|
|
256
|
+
[workflowStatuses, t]
|
|
251
257
|
)
|
|
252
258
|
|
|
253
259
|
const handleOnSearch = (query: string): void => {
|
|
@@ -311,7 +317,7 @@ export const ListView = ({
|
|
|
311
317
|
</h1>
|
|
312
318
|
<Stats total={data?.meta.total} />
|
|
313
319
|
<IconButton
|
|
314
|
-
aria-label=
|
|
320
|
+
aria-label={t('collections.list.createAriaLabel')}
|
|
315
321
|
render={
|
|
316
322
|
<Link
|
|
317
323
|
to={'/admin/collections/$collection/create' as never}
|
|
@@ -327,7 +333,7 @@ export const ListView = ({
|
|
|
327
333
|
onSearch={handleOnSearch}
|
|
328
334
|
onClear={handleOnClear}
|
|
329
335
|
inputSize="sm"
|
|
330
|
-
placeholder=
|
|
336
|
+
placeholder={t('collections.list.searchPlaceholder')}
|
|
331
337
|
className={cx('byline-coll-list-search', styles.search)}
|
|
332
338
|
/>
|
|
333
339
|
|
|
@@ -348,7 +354,7 @@ export const ListView = ({
|
|
|
348
354
|
showFirstButton
|
|
349
355
|
showLastButton
|
|
350
356
|
componentName="pagerTop"
|
|
351
|
-
aria-label=
|
|
357
|
+
aria-label={t('collections.list.pagerTopAriaLabel')}
|
|
352
358
|
/>
|
|
353
359
|
</div>
|
|
354
360
|
<Table.Container className={cx('byline-coll-list-table-wrap', styles.tableWrap)}>
|
|
@@ -388,8 +394,8 @@ export const ListView = ({
|
|
|
388
394
|
search: params,
|
|
389
395
|
})
|
|
390
396
|
}}
|
|
391
|
-
aria-label=
|
|
392
|
-
title=
|
|
397
|
+
aria-label={t('collections.list.sortManualOrderAriaLabel')}
|
|
398
|
+
title={t('collections.list.sortManualOrderAriaLabel')}
|
|
393
399
|
>
|
|
394
400
|
<SortAscendingIcon />
|
|
395
401
|
</button>
|
|
@@ -412,7 +418,12 @@ export const ListView = ({
|
|
|
412
418
|
|
|
413
419
|
<Table.Body>
|
|
414
420
|
{localDocs.map((document) => (
|
|
415
|
-
<SortableTableRow
|
|
421
|
+
<SortableTableRow
|
|
422
|
+
key={document.id}
|
|
423
|
+
id={document.id}
|
|
424
|
+
disabled={!dragEnabled}
|
|
425
|
+
t={t}
|
|
426
|
+
>
|
|
416
427
|
{columns.map((column) => (
|
|
417
428
|
<Table.Cell
|
|
418
429
|
key={String(column.fieldName)}
|
|
@@ -567,7 +578,7 @@ export const ListView = ({
|
|
|
567
578
|
showFirstButton
|
|
568
579
|
showLastButton
|
|
569
580
|
componentName="pagerBottom"
|
|
570
|
-
aria-label=
|
|
581
|
+
aria-label={t('collections.list.pagerBottomAriaLabel')}
|
|
571
582
|
/>
|
|
572
583
|
</div>
|
|
573
584
|
</Container>
|
|
@@ -18,11 +18,15 @@
|
|
|
18
18
|
* `window.location.assign(url)`.
|
|
19
19
|
*
|
|
20
20
|
* The preview URL comes from `CollectionAdminConfig.preview.url(doc, ctx)`
|
|
21
|
-
* when configured; otherwise it falls back
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* when configured; otherwise it falls back through the schema's
|
|
22
|
+
* `CollectionDefinition.buildDocumentPath` (the single source of truth
|
|
23
|
+
* the richtext embed walker also reads) and finally to the conventional
|
|
24
|
+
* `/${collectionPath}/${doc.path}`. When the configured `url(...)`
|
|
25
|
+
* returns `null` (missing slug, missing required relation, etc.), the
|
|
26
|
+
* icon is not rendered at all — there is no public URL meaningful for
|
|
27
|
+
* this document yet, so offering a preview link would just lead to a
|
|
28
|
+
* 404. Hosts that need a locale prefix, query string, or other request-
|
|
29
|
+
* scoped composition still write their own `preview.url`.
|
|
26
30
|
*
|
|
27
31
|
* The component does not load the document itself. Callers pass the
|
|
28
32
|
* already-loaded `doc` from their route loader. The edit-view loader
|
|
@@ -42,10 +46,14 @@
|
|
|
42
46
|
import { useState } from 'react'
|
|
43
47
|
|
|
44
48
|
import type { CollectionAdminConfig, PreviewDocument } from '@byline/core'
|
|
49
|
+
import { useTranslation } from '@byline/i18n/react'
|
|
45
50
|
import { ExternalLinkIcon, IconButton, useToastManager } from '@byline/ui/react'
|
|
46
51
|
import cx from 'classnames'
|
|
47
52
|
|
|
48
53
|
import { enablePreviewModeFn } from '../../server-fns/preview/index.js'
|
|
54
|
+
import { resolvePreviewUrl } from './resolve-preview-url.js'
|
|
55
|
+
|
|
56
|
+
export { resolvePreviewUrl } from './resolve-preview-url.js'
|
|
49
57
|
|
|
50
58
|
export interface PreviewLinkProps {
|
|
51
59
|
/** Collection path (e.g. `'news'`, `'pages'`). */
|
|
@@ -60,30 +68,6 @@ export interface PreviewLinkProps {
|
|
|
60
68
|
className?: string
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
/**
|
|
64
|
-
* Resolve the preview URL for a doc against an admin config. Exported so
|
|
65
|
-
* other surfaces (list-row preview links in the future) can share the
|
|
66
|
-
* same fallback logic.
|
|
67
|
-
*
|
|
68
|
-
* Returns:
|
|
69
|
-
* - `string` → URL to open
|
|
70
|
-
* - `null` → no preview URL meaningful for this doc; hide affordance
|
|
71
|
-
*/
|
|
72
|
-
export function resolvePreviewUrl(
|
|
73
|
-
doc: PreviewDocument,
|
|
74
|
-
collectionPath: string,
|
|
75
|
-
adminConfig: CollectionAdminConfig | undefined,
|
|
76
|
-
locale: string | undefined
|
|
77
|
-
): string | null {
|
|
78
|
-
if (adminConfig?.preview) {
|
|
79
|
-
return adminConfig.preview.url(doc, { locale })
|
|
80
|
-
}
|
|
81
|
-
// Default convention: collection lives at `/${collectionPath}/${path}`.
|
|
82
|
-
// Returns null when the doc has no path yet (unsaved, awaiting slug).
|
|
83
|
-
if (!doc.path) return null
|
|
84
|
-
return `/${collectionPath}/${doc.path}`
|
|
85
|
-
}
|
|
86
|
-
|
|
87
71
|
export const PreviewLink = ({
|
|
88
72
|
collectionPath,
|
|
89
73
|
doc,
|
|
@@ -92,6 +76,7 @@ export const PreviewLink = ({
|
|
|
92
76
|
className,
|
|
93
77
|
}: PreviewLinkProps) => {
|
|
94
78
|
const toastManager = useToastManager()
|
|
79
|
+
const { t } = useTranslation('byline-admin')
|
|
95
80
|
const [busy, setBusy] = useState(false)
|
|
96
81
|
|
|
97
82
|
const url = resolvePreviewUrl(doc, collectionPath, adminConfig, locale)
|
|
@@ -108,8 +93,10 @@ export const PreviewLink = ({
|
|
|
108
93
|
window.location.assign(url)
|
|
109
94
|
} catch (err) {
|
|
110
95
|
toastManager.add({
|
|
111
|
-
title: '
|
|
112
|
-
description:
|
|
96
|
+
title: t('collections.preview.toastTitle'),
|
|
97
|
+
description: t('collections.preview.failedDescription', {
|
|
98
|
+
message: (err as Error).message,
|
|
99
|
+
}),
|
|
113
100
|
data: {
|
|
114
101
|
intent: 'danger',
|
|
115
102
|
iconType: 'danger',
|
|
@@ -129,8 +116,8 @@ export const PreviewLink = ({
|
|
|
129
116
|
variant="text"
|
|
130
117
|
disabled={busy}
|
|
131
118
|
onClick={handleClick}
|
|
132
|
-
aria-label=
|
|
133
|
-
title=
|
|
119
|
+
aria-label={t('collections.preview.openAriaLabel')}
|
|
120
|
+
title={t('collections.preview.title')}
|
|
134
121
|
>
|
|
135
122
|
<ExternalLinkIcon width="20px" height="20px" className="byline-preview-link-icon" />
|
|
136
123
|
</IconButton>
|