@haus-tech/badge-plugin 4.0.3 → 4.0.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/CHANGELOG.md +17 -0
- package/README.md +141 -0
- package/package.json +8 -14
- package/{dist → src}/api/admin.resolver.d.ts +1 -0
- package/{dist → src}/api/admin.resolver.js +14 -2
- package/src/api/admin.resolver.js.map +1 -0
- package/{dist → src}/api/api-extensions.js +3 -0
- package/src/api/api-extensions.js.map +1 -0
- package/{dist → src}/api/shop.resolver.js +9 -8
- package/src/api/shop.resolver.js.map +1 -0
- package/{dist → src}/badge.plugin.js +11 -11
- package/src/badge.plugin.js.map +1 -0
- package/{dist → src}/constants.js +1 -0
- package/src/constants.js.map +1 -0
- package/src/dashboard/badge-detail.tsx +457 -0
- package/src/dashboard/badge-list.tsx +142 -0
- package/src/dashboard/index.tsx +7 -0
- package/{dist → src}/entity/badge.entity.js +3 -2
- package/src/entity/badge.entity.js.map +1 -0
- package/{dist → src}/gql/generated.d.ts +1 -0
- package/{dist → src}/gql/generated.js +17 -16
- package/src/gql/generated.js.map +1 -0
- package/{dist → src}/index.js +1 -0
- package/src/index.js.map +1 -0
- package/{dist → src}/service/badge.service.js +9 -5
- package/src/service/badge.service.js.map +1 -0
- package/{dist → src}/types.js +1 -0
- package/src/types.js.map +1 -0
- package/{dist → src}/ui/badge-list.component.d.ts +1 -2
- package/{dist → src}/ui/badge-list.component.js +3 -2
- package/src/ui/badge-list.component.js.map +1 -0
- package/{dist → src}/ui/gql/gql.d.ts +2 -2
- package/{dist → src}/ui/gql/gql.js +19 -9
- package/src/ui/gql/gql.js.map +1 -0
- package/{dist → src}/ui/gql/graphql.d.ts +5 -15
- package/{dist → src}/ui/gql/graphql.js +17 -16
- package/src/ui/gql/graphql.js.map +1 -0
- package/{dist → src}/ui/gql/index.js +1 -0
- package/src/ui/gql/index.js.map +1 -0
- package/src/ui/providers.d.ts +2 -0
- package/{dist → src}/ui/providers.js +1 -0
- package/src/ui/providers.js.map +1 -0
- package/src/ui/routes.d.ts +31 -0
- package/{dist → src}/ui/routes.js +1 -0
- package/src/ui/routes.js.map +1 -0
- package/{dist → src}/ui/update-badge.component.js +3 -2
- package/src/ui/update-badge.component.js.map +1 -0
- package/dist/ui/providers.d.ts +0 -2
- package/dist/ui/routes.d.ts +0 -31
- package/src/config/README.md +0 -1
- /package/{dist → src}/api/api-extensions.d.ts +0 -0
- /package/{dist → src}/api/shop.resolver.d.ts +0 -0
- /package/{dist → src}/badge.plugin.d.ts +0 -0
- /package/{dist → src}/constants.d.ts +0 -0
- /package/{dist → src}/entity/badge.entity.d.ts +0 -0
- /package/{dist → src}/index.d.ts +0 -0
- /package/{dist → src}/service/badge.service.d.ts +0 -0
- /package/{dist → src}/types.d.ts +0 -0
- /package/{dist → src}/ui/badge-list.component.html +0 -0
- /package/{dist → src}/ui/badge-list.component.scss +0 -0
- /package/{dist → src}/ui/badge-list.component.ts +0 -0
- /package/{dist → src}/ui/gql/gql.ts +0 -0
- /package/{dist → src}/ui/gql/graphql.ts +0 -0
- /package/{dist → src}/ui/gql/index.d.ts +0 -0
- /package/{dist → src}/ui/gql/index.ts +0 -0
- /package/{dist → src}/ui/providers.ts +0 -0
- /package/{dist → src}/ui/routes.ts +0 -0
- /package/{dist → src}/ui/translations/en.json +0 -0
- /package/{dist → src}/ui/translations/sv.json +0 -0
- /package/{dist → src}/ui/update-badge.component.d.ts +0 -0
- /package/{dist → src}/ui/update-badge.component.html +0 -0
- /package/{dist → src}/ui/update-badge.component.scss +0 -0
- /package/{dist → src}/ui/update-badge.component.ts +0 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DashboardRouteDefinition,
|
|
3
|
+
detailPageRouteLoader,
|
|
4
|
+
useDetailPage,
|
|
5
|
+
Page,
|
|
6
|
+
PageTitle,
|
|
7
|
+
PageActionBar,
|
|
8
|
+
PageActionBarRight,
|
|
9
|
+
PageLayout,
|
|
10
|
+
PageBlock,
|
|
11
|
+
FormFieldWrapper,
|
|
12
|
+
Button,
|
|
13
|
+
Select,
|
|
14
|
+
SelectContent,
|
|
15
|
+
SelectItem,
|
|
16
|
+
SelectTrigger,
|
|
17
|
+
SelectValue,
|
|
18
|
+
Label,
|
|
19
|
+
VendureImage,
|
|
20
|
+
} from '@vendure/dashboard'
|
|
21
|
+
import { AnyRoute, useNavigate } from '@tanstack/react-router'
|
|
22
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
23
|
+
import { useCallback, useState, useRef, useEffect } from 'react'
|
|
24
|
+
import { toast } from 'sonner'
|
|
25
|
+
import { api } from '@vendure/dashboard'
|
|
26
|
+
import { graphql } from '@/gql'
|
|
27
|
+
import { UploadIcon } from 'lucide-react'
|
|
28
|
+
|
|
29
|
+
const getBadgeDetailDocument = graphql(`
|
|
30
|
+
query GetBadgeDetail($id: ID!) {
|
|
31
|
+
badge(id: $id) {
|
|
32
|
+
id
|
|
33
|
+
createdAt
|
|
34
|
+
updatedAt
|
|
35
|
+
collection {
|
|
36
|
+
id
|
|
37
|
+
name
|
|
38
|
+
}
|
|
39
|
+
collectionId
|
|
40
|
+
position
|
|
41
|
+
assetId
|
|
42
|
+
asset {
|
|
43
|
+
id
|
|
44
|
+
name
|
|
45
|
+
type
|
|
46
|
+
mimeType
|
|
47
|
+
width
|
|
48
|
+
height
|
|
49
|
+
fileSize
|
|
50
|
+
source
|
|
51
|
+
preview
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`)
|
|
56
|
+
|
|
57
|
+
const createBadgeDocument = graphql(`
|
|
58
|
+
mutation CreateBadge($input: CreateBadgeInput!) {
|
|
59
|
+
createBadge(input: $input) {
|
|
60
|
+
id
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
`)
|
|
64
|
+
|
|
65
|
+
const updateBadgeDocument = graphql(`
|
|
66
|
+
mutation UpdateBadge($input: UpdateBadgeInput!) {
|
|
67
|
+
updateBadge(input: $input) {
|
|
68
|
+
id
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
`)
|
|
72
|
+
|
|
73
|
+
const getBadgePluginConfigDocument = graphql(`
|
|
74
|
+
query GetBadgePluginConfig {
|
|
75
|
+
getBadgePluginConfig {
|
|
76
|
+
availablePositions
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`)
|
|
80
|
+
|
|
81
|
+
const getCollectionsDocument = graphql(`
|
|
82
|
+
query GetCollections {
|
|
83
|
+
collections {
|
|
84
|
+
items {
|
|
85
|
+
id
|
|
86
|
+
name
|
|
87
|
+
slug
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
`)
|
|
92
|
+
|
|
93
|
+
const createAssetsDocument = graphql(`
|
|
94
|
+
mutation CreateAssets($input: [CreateAssetInput!]!) {
|
|
95
|
+
createAssets(input: $input) {
|
|
96
|
+
... on Asset {
|
|
97
|
+
id
|
|
98
|
+
name
|
|
99
|
+
source
|
|
100
|
+
preview
|
|
101
|
+
}
|
|
102
|
+
... on MimeTypeError {
|
|
103
|
+
message
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
`)
|
|
108
|
+
|
|
109
|
+
export const badgeDetailRoute: DashboardRouteDefinition = {
|
|
110
|
+
path: '/badges/$id',
|
|
111
|
+
loader: detailPageRouteLoader({
|
|
112
|
+
queryDocument: getBadgeDetailDocument,
|
|
113
|
+
breadcrumb: (isNew, entity) => [
|
|
114
|
+
{ path: '/badges', label: 'Badges' },
|
|
115
|
+
isNew ? 'New Badge' : `Badge ${entity?.id || ''}`,
|
|
116
|
+
],
|
|
117
|
+
}),
|
|
118
|
+
component: (route) => {
|
|
119
|
+
return <BadgeDetailPage route={route as any} />
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function BadgeDetailPage({ route }: { route: AnyRoute }) {
|
|
124
|
+
const params = route.useParams()
|
|
125
|
+
const navigate = useNavigate()
|
|
126
|
+
const queryClient = useQueryClient()
|
|
127
|
+
const creatingNewEntity = params.id === 'new'
|
|
128
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
129
|
+
const [uploadedAsset, setUploadedAsset] = useState<{
|
|
130
|
+
id: string
|
|
131
|
+
name: string
|
|
132
|
+
preview: string
|
|
133
|
+
} | null>(null)
|
|
134
|
+
const [uploading, setUploading] = useState(false)
|
|
135
|
+
|
|
136
|
+
const { data: configData } = useQuery({
|
|
137
|
+
queryKey: ['badge-plugin-config'],
|
|
138
|
+
queryFn: () => api.query(getBadgePluginConfigDocument),
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const { data: collectionsData } = useQuery({
|
|
142
|
+
queryKey: ['collections'],
|
|
143
|
+
queryFn: () => api.query(getCollectionsDocument),
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const availablePositions = configData?.getBadgePluginConfig?.availablePositions || []
|
|
147
|
+
|
|
148
|
+
const collections = collectionsData?.collections?.items || []
|
|
149
|
+
|
|
150
|
+
const setValuesForUpdate = useCallback(
|
|
151
|
+
(
|
|
152
|
+
badge:
|
|
153
|
+
| {
|
|
154
|
+
id: string
|
|
155
|
+
position: string
|
|
156
|
+
collectionId: string | null
|
|
157
|
+
assetId: string
|
|
158
|
+
asset: {
|
|
159
|
+
id: string
|
|
160
|
+
name: string
|
|
161
|
+
preview: string
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
| null
|
|
165
|
+
| undefined,
|
|
166
|
+
) => {
|
|
167
|
+
const position =
|
|
168
|
+
badge?.position && badge.position.trim() !== ''
|
|
169
|
+
? badge.position
|
|
170
|
+
: availablePositions?.length > 0
|
|
171
|
+
? availablePositions[0]
|
|
172
|
+
: ''
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
id: badge?.id ?? '',
|
|
176
|
+
position: position || 'top-left',
|
|
177
|
+
collectionId: badge?.collectionId ?? null,
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
[availablePositions],
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
const { form, submitHandler, entity, isPending, resetForm, refreshEntity } = useDetailPage({
|
|
184
|
+
queryDocument: getBadgeDetailDocument,
|
|
185
|
+
createDocument: creatingNewEntity ? createBadgeDocument : undefined,
|
|
186
|
+
updateDocument: creatingNewEntity ? undefined : updateBadgeDocument,
|
|
187
|
+
setValuesForUpdate,
|
|
188
|
+
params: creatingNewEntity ? { id: 'new' } : { id: params.id },
|
|
189
|
+
onSuccess: async (data) => {
|
|
190
|
+
toast.success(creatingNewEntity ? 'Successfully created badge' : 'Successfully saved badge')
|
|
191
|
+
resetForm()
|
|
192
|
+
if (creatingNewEntity) {
|
|
193
|
+
await navigate({ to: `/badges/${data.id}` })
|
|
194
|
+
} else {
|
|
195
|
+
await refreshEntity()
|
|
196
|
+
}
|
|
197
|
+
queryClient.invalidateQueries({ queryKey: ['badges'] })
|
|
198
|
+
},
|
|
199
|
+
onError: (err) => {
|
|
200
|
+
toast.error(creatingNewEntity ? 'Failed to create badge' : 'Failed to save badge', {
|
|
201
|
+
description: err instanceof Error ? err.message : 'Unknown error',
|
|
202
|
+
})
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const createAssetMutation = useMutation({
|
|
207
|
+
mutationFn: async (file: File) => {
|
|
208
|
+
const input = [{ file }]
|
|
209
|
+
const result: any = await api.mutate(createAssetsDocument, { input })
|
|
210
|
+
const assetResult = result.createAssets[0]
|
|
211
|
+
if ('id' in assetResult) {
|
|
212
|
+
return assetResult
|
|
213
|
+
} else if ('message' in assetResult) {
|
|
214
|
+
throw new Error(assetResult.message)
|
|
215
|
+
}
|
|
216
|
+
throw new Error('Failed to upload asset')
|
|
217
|
+
},
|
|
218
|
+
onSuccess: (asset) => {
|
|
219
|
+
setUploadedAsset({
|
|
220
|
+
id: asset.id,
|
|
221
|
+
name: asset.name || 'Uploaded image',
|
|
222
|
+
preview: asset.preview || '',
|
|
223
|
+
})
|
|
224
|
+
setUploading(false)
|
|
225
|
+
toast.success('Image uploaded successfully')
|
|
226
|
+
},
|
|
227
|
+
onError: (error) => {
|
|
228
|
+
toast.error('Failed to upload image', {
|
|
229
|
+
description: error instanceof Error ? error.message : 'Unknown error',
|
|
230
|
+
})
|
|
231
|
+
setUploading(false)
|
|
232
|
+
},
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// Track synced entity ID to prevent re-syncing
|
|
236
|
+
const syncedEntityIdRef = useRef<string | null>(null)
|
|
237
|
+
const entityIdRef = useRef<string | null>(null)
|
|
238
|
+
|
|
239
|
+
// Sync uploadedAsset state when entity changes (only when entity.id changes)
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
if (entity?.id && entity.id !== entityIdRef.current) {
|
|
242
|
+
entityIdRef.current = entity.id
|
|
243
|
+
syncedEntityIdRef.current = null // Reset sync tracking when entity changes
|
|
244
|
+
if (entity?.asset) {
|
|
245
|
+
setUploadedAsset({
|
|
246
|
+
id: entity.asset.id,
|
|
247
|
+
name: entity.asset.name,
|
|
248
|
+
preview: entity.asset.preview,
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}, [entity?.id, entity?.asset])
|
|
253
|
+
|
|
254
|
+
// Sync form values when entity loads (for edit mode)
|
|
255
|
+
// Run whenever entity data or availablePositions changes
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
const badge = entity
|
|
258
|
+
if (!creatingNewEntity && badge?.id) {
|
|
259
|
+
const entityPosition = badge.position
|
|
260
|
+
const entityCollectionId = badge.collectionId
|
|
261
|
+
|
|
262
|
+
// Set position - use entity value if available, otherwise keep what's there
|
|
263
|
+
if (entityPosition) {
|
|
264
|
+
if (availablePositions.length > 0 && availablePositions.includes(entityPosition)) {
|
|
265
|
+
form.setValue('position', entityPosition, { shouldDirty: false })
|
|
266
|
+
} else if (availablePositions.length === 0) {
|
|
267
|
+
// If positions not loaded yet, set it anyway (will be validated later)
|
|
268
|
+
form.setValue('position', entityPosition, { shouldDirty: false })
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Always sync collection - set it immediately
|
|
273
|
+
form.setValue('collectionId', entityCollectionId ?? null, { shouldDirty: false })
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Initialize default values for new badges
|
|
277
|
+
if (creatingNewEntity && availablePositions.length > 0) {
|
|
278
|
+
form.setValue('position', availablePositions[0])
|
|
279
|
+
form.setValue('collectionId', null)
|
|
280
|
+
}
|
|
281
|
+
}, [entity, availablePositions, creatingNewEntity, form])
|
|
282
|
+
|
|
283
|
+
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
284
|
+
const file = event.target.files?.[0]
|
|
285
|
+
if (file) {
|
|
286
|
+
if (!file.type.startsWith('image/')) {
|
|
287
|
+
toast.error('Please select an image file')
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
setUploading(true)
|
|
291
|
+
createAssetMutation.mutate(file)
|
|
292
|
+
}
|
|
293
|
+
if (fileInputRef.current) {
|
|
294
|
+
fileInputRef.current.value = ''
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
299
|
+
e.preventDefault()
|
|
300
|
+
|
|
301
|
+
if (creatingNewEntity) {
|
|
302
|
+
if (!uploadedAsset?.id) {
|
|
303
|
+
toast.error('Please upload an image first')
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const formValues = form.getValues()
|
|
308
|
+
try {
|
|
309
|
+
const result = await api.mutate(createBadgeDocument, {
|
|
310
|
+
input: {
|
|
311
|
+
assetId: uploadedAsset.id,
|
|
312
|
+
position: formValues.position || availablePositions[0] || 'top-left',
|
|
313
|
+
collectionId: formValues.collectionId || null,
|
|
314
|
+
},
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
toast.success('Badge created successfully')
|
|
318
|
+
await navigate({ to: `/badges/${result.createBadge.id}` })
|
|
319
|
+
queryClient.invalidateQueries({ queryKey: ['badges'] })
|
|
320
|
+
} catch (error: any) {
|
|
321
|
+
toast.error('Failed to create badge', {
|
|
322
|
+
description: error instanceof Error ? error.message : 'Unknown error',
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
await submitHandler(e)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const currentAsset =
|
|
332
|
+
uploadedAsset ||
|
|
333
|
+
(entity?.asset
|
|
334
|
+
? {
|
|
335
|
+
id: entity.asset.id,
|
|
336
|
+
name: entity.asset.name,
|
|
337
|
+
preview: entity.asset.preview,
|
|
338
|
+
}
|
|
339
|
+
: null)
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<Page pageId="badge-detail" form={form} submitHandler={handleSubmit}>
|
|
343
|
+
<PageTitle>
|
|
344
|
+
{creatingNewEntity ? 'New Badge' : entity?.id ? `Badge ${entity.id}` : 'Edit Badge'}
|
|
345
|
+
</PageTitle>
|
|
346
|
+
<PageActionBar>
|
|
347
|
+
<PageActionBarRight>
|
|
348
|
+
<Button
|
|
349
|
+
type="submit"
|
|
350
|
+
disabled={
|
|
351
|
+
creatingNewEntity
|
|
352
|
+
? !uploadedAsset || !form.formState.isValid || isPending || uploading
|
|
353
|
+
: !form.formState.isDirty || !form.formState.isValid || isPending
|
|
354
|
+
}
|
|
355
|
+
>
|
|
356
|
+
{isPending || uploading ? 'Saving...' : creatingNewEntity ? 'Create' : 'Update'}
|
|
357
|
+
</Button>
|
|
358
|
+
</PageActionBarRight>
|
|
359
|
+
</PageActionBar>
|
|
360
|
+
<PageLayout>
|
|
361
|
+
<PageBlock column="main" blockId="main-form">
|
|
362
|
+
<div className="space-y-4">
|
|
363
|
+
<div className="space-y-2" style={{ maxWidth: '400px' }}>
|
|
364
|
+
<Label>Image</Label>
|
|
365
|
+
<div className="space-y-4">
|
|
366
|
+
{currentAsset ? (
|
|
367
|
+
<div className="space-y-2">
|
|
368
|
+
<VendureImage
|
|
369
|
+
asset={entity?.asset || ({ ...currentAsset, type: 'IMAGE' } as any)}
|
|
370
|
+
alt={currentAsset.name}
|
|
371
|
+
preset="medium"
|
|
372
|
+
/>
|
|
373
|
+
<div className="text-sm text-muted-foreground">{currentAsset.name}</div>
|
|
374
|
+
</div>
|
|
375
|
+
) : (
|
|
376
|
+
<div className="border-2 border-dashed border-muted rounded-lg p-8 text-center">
|
|
377
|
+
<UploadIcon className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
|
|
378
|
+
<p className="text-sm text-muted-foreground mb-4">No image uploaded</p>
|
|
379
|
+
</div>
|
|
380
|
+
)}
|
|
381
|
+
<div>
|
|
382
|
+
<input
|
|
383
|
+
ref={fileInputRef}
|
|
384
|
+
type="file"
|
|
385
|
+
accept="image/*"
|
|
386
|
+
onChange={handleFileSelect}
|
|
387
|
+
className="hidden"
|
|
388
|
+
/>
|
|
389
|
+
<Button
|
|
390
|
+
type="button"
|
|
391
|
+
variant="outline"
|
|
392
|
+
onClick={() => fileInputRef.current?.click()}
|
|
393
|
+
disabled={uploading}
|
|
394
|
+
className="w-full"
|
|
395
|
+
>
|
|
396
|
+
<UploadIcon className="mr-2 h-4 w-4" />
|
|
397
|
+
{uploading ? 'Uploading...' : currentAsset ? 'Change Image' : 'Upload Image'}
|
|
398
|
+
</Button>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<div style={{ maxWidth: '700px' }} className="space-y-4">
|
|
404
|
+
<FormFieldWrapper
|
|
405
|
+
control={form.control}
|
|
406
|
+
name="position"
|
|
407
|
+
label="Position"
|
|
408
|
+
render={({ field }) => {
|
|
409
|
+
// Ensure value is valid - use field.value if it exists and is in availablePositions, otherwise empty string
|
|
410
|
+
const validValue =
|
|
411
|
+
field.value && availablePositions.includes(field.value) ? field.value : ''
|
|
412
|
+
return (
|
|
413
|
+
<Select value={validValue} onValueChange={field.onChange}>
|
|
414
|
+
<SelectTrigger className="w-full">
|
|
415
|
+
<SelectValue placeholder="Select position" />
|
|
416
|
+
</SelectTrigger>
|
|
417
|
+
<SelectContent>
|
|
418
|
+
{availablePositions.map((pos) => (
|
|
419
|
+
<SelectItem key={pos} value={pos}>
|
|
420
|
+
{pos}
|
|
421
|
+
</SelectItem>
|
|
422
|
+
))}
|
|
423
|
+
</SelectContent>
|
|
424
|
+
</Select>
|
|
425
|
+
)
|
|
426
|
+
}}
|
|
427
|
+
/>
|
|
428
|
+
<FormFieldWrapper
|
|
429
|
+
control={form.control}
|
|
430
|
+
name="collectionId"
|
|
431
|
+
label="Collection"
|
|
432
|
+
render={({ field }) => (
|
|
433
|
+
<Select
|
|
434
|
+
value={field.value}
|
|
435
|
+
onValueChange={(val) => field.onChange(val === 'none' ? null : val)}
|
|
436
|
+
>
|
|
437
|
+
<SelectTrigger className="w-full">
|
|
438
|
+
<SelectValue placeholder="No collection" />
|
|
439
|
+
</SelectTrigger>
|
|
440
|
+
<SelectContent>
|
|
441
|
+
<SelectItem value="none">No collection</SelectItem>
|
|
442
|
+
{collections.map((collection) => (
|
|
443
|
+
<SelectItem key={collection.id} value={collection.id}>
|
|
444
|
+
{collection.name}
|
|
445
|
+
</SelectItem>
|
|
446
|
+
))}
|
|
447
|
+
</SelectContent>
|
|
448
|
+
</Select>
|
|
449
|
+
)}
|
|
450
|
+
/>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
</PageBlock>
|
|
454
|
+
</PageLayout>
|
|
455
|
+
</Page>
|
|
456
|
+
)
|
|
457
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DashboardRouteDefinition,
|
|
3
|
+
ListPage,
|
|
4
|
+
PageActionBarRight,
|
|
5
|
+
Button,
|
|
6
|
+
VendureImage,
|
|
7
|
+
Badge as BadgeComponent,
|
|
8
|
+
} from '@vendure/dashboard'
|
|
9
|
+
import { PencilIcon, PlusIcon } from 'lucide-react'
|
|
10
|
+
import { Link } from '@tanstack/react-router'
|
|
11
|
+
import { graphql } from '@/gql'
|
|
12
|
+
|
|
13
|
+
const getBadgeListDocument = graphql(`
|
|
14
|
+
query GetBadges($options: BadgeListOptions) {
|
|
15
|
+
badges(options: $options) {
|
|
16
|
+
items {
|
|
17
|
+
id
|
|
18
|
+
createdAt
|
|
19
|
+
updatedAt
|
|
20
|
+
collection {
|
|
21
|
+
id
|
|
22
|
+
name
|
|
23
|
+
}
|
|
24
|
+
collectionId
|
|
25
|
+
position
|
|
26
|
+
asset {
|
|
27
|
+
id
|
|
28
|
+
name
|
|
29
|
+
type
|
|
30
|
+
mimeType
|
|
31
|
+
width
|
|
32
|
+
height
|
|
33
|
+
fileSize
|
|
34
|
+
source
|
|
35
|
+
preview
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
totalItems
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`)
|
|
42
|
+
|
|
43
|
+
const deleteBadgeDocument = graphql(`
|
|
44
|
+
mutation DeleteBadge($id: ID!) {
|
|
45
|
+
deleteBadge(ids: [$id]) {
|
|
46
|
+
result
|
|
47
|
+
message
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
`)
|
|
51
|
+
|
|
52
|
+
export const badgeListRoute: DashboardRouteDefinition = {
|
|
53
|
+
navMenuItem: {
|
|
54
|
+
sectionId: 'catalog',
|
|
55
|
+
id: 'badges',
|
|
56
|
+
url: '/badges',
|
|
57
|
+
title: 'Badges',
|
|
58
|
+
},
|
|
59
|
+
path: '/badges',
|
|
60
|
+
loader: () => ({
|
|
61
|
+
breadcrumb: 'Badges',
|
|
62
|
+
}),
|
|
63
|
+
component: (route) => {
|
|
64
|
+
const navigate = route.useNavigate()
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<ListPage
|
|
68
|
+
pageId="badge-list"
|
|
69
|
+
title="Badges"
|
|
70
|
+
listQuery={getBadgeListDocument}
|
|
71
|
+
deleteMutation={deleteBadgeDocument}
|
|
72
|
+
route={route}
|
|
73
|
+
customizeColumns={{
|
|
74
|
+
asset: {
|
|
75
|
+
cell: ({ row }) => {
|
|
76
|
+
const badge = row.original
|
|
77
|
+
return (
|
|
78
|
+
<div className="flex items-center gap-2">
|
|
79
|
+
<VendureImage
|
|
80
|
+
asset={badge.asset}
|
|
81
|
+
alt={badge.asset.name}
|
|
82
|
+
preset="thumb"
|
|
83
|
+
className="w-12 h-12 object-cover rounded"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
)
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
position: {
|
|
90
|
+
cell: ({ row }) => {
|
|
91
|
+
return <BadgeComponent variant="secondary">{row.original.position}</BadgeComponent>
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
collection: {
|
|
95
|
+
cell: ({ row }) => {
|
|
96
|
+
const badge = row.original
|
|
97
|
+
return badge.collection ? (
|
|
98
|
+
<Link
|
|
99
|
+
to={`/collections/${badge.collection.id}`}
|
|
100
|
+
className="text-sm text-primary hover:underline"
|
|
101
|
+
>
|
|
102
|
+
{badge.collection.name}
|
|
103
|
+
</Link>
|
|
104
|
+
) : (
|
|
105
|
+
<span className="text-sm text-muted-foreground">—</span>
|
|
106
|
+
)
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
}}
|
|
110
|
+
rowActions={[
|
|
111
|
+
{
|
|
112
|
+
label: (
|
|
113
|
+
<>
|
|
114
|
+
<PencilIcon className="mr-2 h-4 w-4" />
|
|
115
|
+
Edit
|
|
116
|
+
</>
|
|
117
|
+
),
|
|
118
|
+
onClick: (row) => {
|
|
119
|
+
navigate({ to: `/badges/${row.id}` })
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
]}
|
|
123
|
+
defaultVisibility={{
|
|
124
|
+
asset: true,
|
|
125
|
+
position: true,
|
|
126
|
+
collection: true,
|
|
127
|
+
updatedAt: true,
|
|
128
|
+
}}
|
|
129
|
+
defaultColumnOrder={['asset', 'position', 'collection', 'updatedAt']}
|
|
130
|
+
>
|
|
131
|
+
<PageActionBarRight>
|
|
132
|
+
<Button asChild>
|
|
133
|
+
<Link to="./new">
|
|
134
|
+
<PlusIcon className="mr-2 h-4 w-4" />
|
|
135
|
+
New Badge
|
|
136
|
+
</Link>
|
|
137
|
+
</Button>
|
|
138
|
+
</PageActionBarRight>
|
|
139
|
+
</ListPage>
|
|
140
|
+
)
|
|
141
|
+
},
|
|
142
|
+
}
|
|
@@ -29,6 +29,7 @@ let Badge = class Badge extends core_1.VendureEntity {
|
|
|
29
29
|
collection;
|
|
30
30
|
collectionId;
|
|
31
31
|
};
|
|
32
|
+
exports.Badge = Badge;
|
|
32
33
|
__decorate([
|
|
33
34
|
(0, typeorm_1.ManyToMany)((type) => core_1.Channel),
|
|
34
35
|
(0, typeorm_1.JoinTable)(),
|
|
@@ -73,8 +74,8 @@ __decorate([
|
|
|
73
74
|
(0, core_1.EntityId)({ nullable: true }),
|
|
74
75
|
__metadata("design:type", Object)
|
|
75
76
|
], Badge.prototype, "collectionId", void 0);
|
|
76
|
-
Badge = __decorate([
|
|
77
|
+
exports.Badge = Badge = __decorate([
|
|
77
78
|
(0, typeorm_1.Entity)(),
|
|
78
79
|
__metadata("design:paramtypes", [Object])
|
|
79
80
|
], Badge);
|
|
80
|
-
|
|
81
|
+
//# sourceMappingURL=badge.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"badge.entity.js","sourceRoot":"","sources":["../../../../../packages/badge-plugin/src/entity/badge.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,wCAQsB;AACtB,qCASgB;AAGT,IAAM,KAAK,GAAX,MAAM,KAAM,SAAQ,oBAAa;IACtC,YAAY,KAA0B;QACpC,KAAK,CAAC,KAAK,CAAC,CAAA;QAEZ,sEAAsE;QACtE,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QAC5B,CAAC;IACH,CAAC;IAID,QAAQ,CAAW;IAGnB,QAAQ,CAAQ;IAGhB,KAAK,CAAQ;IAGb,IAAI,CAAQ;IAUZ,KAAK,CAAO;IAGZ,OAAO,CAAI;IAOX,UAAU,CAAmB;IAG7B,YAAY,CAAW;CACxB,CAAA;AA7CY,sBAAK;AAYhB;IAFC,IAAA,oBAAU,EAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAO,CAAC;IAC7B,IAAA,mBAAS,GAAE;;uCACO;AAGnB;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;;uCAChB;AAGhB;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;oCACV;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mCACf;AAUZ;IARC,IAAA,kBAAQ,EAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAK,EAAE;QACzB,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,IAAI;KACZ,CAAC;IACD,IAAA,oBAAU,EAAC;QACV,IAAI,EAAE,SAAS;QACf,oBAAoB,EAAE,IAAI;KAC3B,CAAC;8BACK,YAAK;oCAAA;AAGZ;IADC,IAAA,eAAQ,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;sCAClB;AAOX;IALC,IAAA,kBAAQ,EAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC/D,IAAA,oBAAU,EAAC;QACV,IAAI,EAAE,cAAc;QACpB,oBAAoB,EAAE,IAAI;KAC3B,CAAC;;yCAC2B;AAG7B;IADC,IAAA,eAAQ,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACN;gBA5CZ,KAAK;IADjB,IAAA,gBAAM,GAAE;;GACI,KAAK,CA6CjB"}
|
|
@@ -744,6 +744,7 @@ export type CreateAssetResult = Asset | MimeTypeError;
|
|
|
744
744
|
export type CreateBadgeInput = {
|
|
745
745
|
assetId: Scalars['ID']['input'];
|
|
746
746
|
position?: InputMaybe<Scalars['String']['input']>;
|
|
747
|
+
collectionId?: InputMaybe<Scalars['ID']['input']>;
|
|
747
748
|
};
|
|
748
749
|
export type CreateChannelCustomFieldsInput = {
|
|
749
750
|
lastCancelTime?: InputMaybe<Scalars['Int']['input']>;
|