@haus-tech/badge-plugin 4.0.5 → 4.0.6
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 +10 -0
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/api/admin.resolver.d.ts +1 -0
- package/src/api/admin.resolver.js +11 -0
- package/src/api/admin.resolver.js.map +1 -1
- package/src/api/api-extensions.js +3 -0
- package/src/api/api-extensions.js.map +1 -1
- package/src/badge.plugin.js +1 -0
- package/src/badge.plugin.js.map +1 -1
- package/src/dashboard/badge-detail.tsx +470 -0
- package/src/dashboard/badge-list.tsx +142 -0
- package/src/dashboard/index.tsx +7 -0
- package/src/gql/generated.d.ts +2 -0
- package/src/gql/generated.js.map +1 -1
- package/src/service/badge.service.js +7 -3
- package/src/service/badge.service.js.map +1 -1
- package/src/ui/gql/graphql.d.ts +1 -0
- package/src/ui/gql/graphql.js +231 -5
- package/src/ui/gql/graphql.js.map +1 -1
- package/src/ui/gql/graphql.ts +4131 -4014
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 4.0.6-0
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- **badge-plugin:** add dashboard integration and enhance badge management ([2603563](https://github.com/WeAreHausTech/haus-tech-vendure-plugins/commit/2603563))
|
|
6
|
+
|
|
7
|
+
## 4.0.5
|
|
8
|
+
|
|
9
|
+
This was a version bump only for badge-plugin to align it with other projects, there were no code changes.
|
|
10
|
+
|
|
1
11
|
## 4.0.4
|
|
2
12
|
|
|
3
13
|
This was a version bump only for badge-plugin to align it with other projects, there were no code changes.
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ export declare class BadgeAdminResolver {
|
|
|
8
8
|
private badgeService;
|
|
9
9
|
constructor(config: BadgePluginOptions, badgeService: BadgeService);
|
|
10
10
|
badges(ctx: RequestContext, args: any): Promise<PaginatedList<Badge>>;
|
|
11
|
+
badge(ctx: RequestContext, id: string): Promise<Badge | null>;
|
|
11
12
|
createBadge(ctx: RequestContext, input: CreateBadgeInput): Promise<Badge>;
|
|
12
13
|
updateBadge(ctx: RequestContext, input: UpdateBadgeInput): Promise<Badge | ErrorResult>;
|
|
13
14
|
deleteBadge(ctx: RequestContext, ids: string[]): Promise<DeletionResponse>;
|
|
@@ -29,6 +29,9 @@ let BadgeAdminResolver = class BadgeAdminResolver {
|
|
|
29
29
|
async badges(ctx, args) {
|
|
30
30
|
return this.badgeService.findAll(ctx, args.options || undefined);
|
|
31
31
|
}
|
|
32
|
+
async badge(ctx, id) {
|
|
33
|
+
return this.badgeService.findOne(ctx, parseInt(id, 10));
|
|
34
|
+
}
|
|
32
35
|
async createBadge(ctx, input) {
|
|
33
36
|
return this.badgeService.create(ctx, input);
|
|
34
37
|
}
|
|
@@ -62,6 +65,14 @@ __decorate([
|
|
|
62
65
|
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
63
66
|
__metadata("design:returntype", Promise)
|
|
64
67
|
], BadgeAdminResolver.prototype, "badges", null);
|
|
68
|
+
__decorate([
|
|
69
|
+
(0, graphql_1.Query)(),
|
|
70
|
+
__param(0, (0, core_1.Ctx)()),
|
|
71
|
+
__param(1, (0, graphql_1.Args)('id')),
|
|
72
|
+
__metadata("design:type", Function),
|
|
73
|
+
__metadata("design:paramtypes", [core_1.RequestContext, String]),
|
|
74
|
+
__metadata("design:returntype", Promise)
|
|
75
|
+
], BadgeAdminResolver.prototype, "badge", null);
|
|
65
76
|
__decorate([
|
|
66
77
|
(0, graphql_1.Mutation)(),
|
|
67
78
|
__param(0, (0, core_1.Ctx)()),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.resolver.js","sourceRoot":"","sources":["../../../../../packages/badge-plugin/src/api/admin.resolver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,6CAAiE;AACjE,wCAAuF;AACvF,4DAAuD;AAEvD,gDAKyB;AACzB,4CAA6D;AAC7D,2CAAuC;AAIhC,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAEU;IAC7B;IAFV,YACuC,MAA0B,EACvD,YAA0B;QADG,WAAM,GAAN,MAAM,CAAoB;QACvD,iBAAY,GAAZ,YAAY,CAAc;IACjC,CAAC;IAGE,AAAN,KAAK,CAAC,MAAM,CAAQ,GAAmB,EAAU,IAAS;QACxD,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,IAAI,SAAS,CAAC,CAAA;IAClE,CAAC;IAGK,AAAN,KAAK,CAAC,WAAW,CACR,GAAmB,EACX,KAAuB;QAEtC,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC7C,CAAC;IAGK,AAAN,KAAK,CAAC,WAAW,CACR,GAAmB,EACX,KAAuB;QAEtC,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC7C,CAAC;IAGK,AAAN,KAAK,CAAC,WAAW,CACR,GAAmB,EACb,GAAa;QAE1B,IAAI,CAAC;YACH,KAAK,MAAM,OAAO,IAAI,GAAG,EAAE,CAAC;gBAC1B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;gBACzE,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;gBAC5C,CAAC;YACH,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,0BAAc,CAAC,OAAO,EAAE,CAAA;QAC3C,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,EAAE,MAAM,EAAE,0BAAc,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAA;QACnE,CAAC;IACH,CAAC;IAGD,oBAAoB;QAClB,OAAO,IAAI,CAAC,MAAM,CAAA;IACpB,CAAC;CACF,CAAA;
|
|
1
|
+
{"version":3,"file":"admin.resolver.js","sourceRoot":"","sources":["../../../../../packages/badge-plugin/src/api/admin.resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,11 +24,13 @@ exports.adminApiExtensions = (0, graphql_tag_1.gql) `
|
|
|
24
24
|
|
|
25
25
|
extend type Query {
|
|
26
26
|
badges(options: BadgeListOptions): BadgeList!
|
|
27
|
+
badge(id: ID!): Badge
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
input CreateBadgeInput {
|
|
30
31
|
assetId: ID!
|
|
31
32
|
position: String
|
|
33
|
+
collectionId: ID
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
extend type Mutation {
|
|
@@ -43,6 +45,7 @@ exports.adminApiExtensions = (0, graphql_tag_1.gql) `
|
|
|
43
45
|
id: ID!
|
|
44
46
|
collectionId: ID
|
|
45
47
|
position: String
|
|
48
|
+
assetId: ID
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
extend type Mutation {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-extensions.js","sourceRoot":"","sources":["../../../../../packages/badge-plugin/src/api/api-extensions.ts"],"names":[],"mappings":";;;AAAA,6CAAiC;AAEpB,QAAA,kBAAkB,GAAG,IAAA,iBAAG,EAAA
|
|
1
|
+
{"version":3,"file":"api-extensions.js","sourceRoot":"","sources":["../../../../../packages/badge-plugin/src/api/api-extensions.ts"],"names":[],"mappings":";;;AAAA,6CAAiC;AAEpB,QAAA,kBAAkB,GAAG,IAAA,iBAAG,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDpC,CAAA;AAEY,QAAA,iBAAiB,GAAG,IAAA,iBAAG,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CnC,CAAA"}
|
package/src/badge.plugin.js
CHANGED
package/src/badge.plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"badge.plugin.js","sourceRoot":"","sources":["../../../../packages/badge-plugin/src/badge.plugin.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,wCAAiE;AACjE,yDAA4E;AAC5E,yDAAyD;AAEzD,2CAAiD;AACjD,wDAA6C;AAE7C,+BAA2B;AAC3B,2DAAsD;AACtD,uDAK4B;
|
|
1
|
+
{"version":3,"file":"badge.plugin.js","sourceRoot":"","sources":["../../../../packages/badge-plugin/src/badge.plugin.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,wCAAiE;AACjE,yDAA4E;AAC5E,yDAAyD;AAEzD,2CAAiD;AACjD,wDAA6C;AAE7C,+BAA2B;AAC3B,2DAAsD;AACtD,uDAK4B;AAiCrB,IAAM,WAAW,GAAjB,MAAM,WAAW;;IACtB,MAAM,CAAC,OAAO,GAAuB;QACnC,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,CAAC;KAC7E,CAAA;IAED,MAAM,CAAC,IAAI,CAAC,OAA4B;QACtC,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACxB,CAAC;QACD,OAAO,aAAW,CAAA;IACpB,CAAC;IAED,MAAM,CAAC,EAAE,GAAqB;QAC5B,aAAa,EAAE,IAAA,WAAI,EAAC,SAAS,EAAE,IAAI,CAAC;QACpC,YAAY,EAAE;YACZ,EAAE,EAAE,IAAA,WAAI,EAAC,SAAS,EAAE,yBAAyB,CAAC;YAC9C,EAAE,EAAE,IAAA,WAAI,EAAC,SAAS,EAAE,yBAAyB,CAAC;SAC/C;QACD,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;QACpD,SAAS,EAAE,CAAC,cAAc,CAAC;QAC3B,iBAAiB;QACjB,QAAQ;QACR,sBAAsB;QACtB,yBAAyB;QACzB,6CAA6C;QAC7C,qCAAqC;QACrC,SAAS;QACT,QAAQ;QACR,wBAAwB;QACxB,iDAAiD;QACjD,yCAAyC;QACzC,SAAS;QACT,OAAO;KACR,CAAA;;AAjCU,kCAAW;sBAAX,WAAW;IA3BvB,IAAA,oBAAa,EAAC;QACb,OAAO,EAAE,CAAC,yBAAkB,CAAC;QAC7B,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,+BAAmB;gBAC5B,UAAU,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO;aACtC;YACD,4BAAY;SACb;QACD,iBAAiB,EAAE;YACjB,MAAM,EAAE,kCAAiB;YACzB,SAAS,EAAE;gBACT,iCAAiB;gBACjB,qCAAqB;gBACrB,0CAA0B;gBAC1B,4CAA4B;aAC7B;SACF;QACD,kBAAkB,EAAE;YAClB,MAAM,EAAE,mCAAkB;YAC1B,SAAS,EAAE,CAAC,mCAAkB,CAAC;SAChC;QACD,QAAQ,EAAE,CAAC,oBAAK,CAAC;QACjB,SAAS,EAAE,uBAAuB;QAElC,aAAa,EAAE,QAAQ;KACxB,CAAC;GACW,WAAW,CAkCvB"}
|
|
@@ -0,0 +1,470 @@
|
|
|
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
|
+
import type { ComponentProps } from 'react'
|
|
29
|
+
|
|
30
|
+
const getBadgeDetailDocument = graphql(`
|
|
31
|
+
query GetBadgeDetail($id: ID!) {
|
|
32
|
+
badge(id: $id) {
|
|
33
|
+
id
|
|
34
|
+
createdAt
|
|
35
|
+
updatedAt
|
|
36
|
+
collection {
|
|
37
|
+
id
|
|
38
|
+
name
|
|
39
|
+
}
|
|
40
|
+
collectionId
|
|
41
|
+
position
|
|
42
|
+
assetId
|
|
43
|
+
asset {
|
|
44
|
+
id
|
|
45
|
+
name
|
|
46
|
+
type
|
|
47
|
+
mimeType
|
|
48
|
+
width
|
|
49
|
+
height
|
|
50
|
+
fileSize
|
|
51
|
+
source
|
|
52
|
+
preview
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`)
|
|
57
|
+
|
|
58
|
+
const createBadgeDocument = graphql(`
|
|
59
|
+
mutation CreateBadge($input: CreateBadgeInput!) {
|
|
60
|
+
createBadge(input: $input) {
|
|
61
|
+
id
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
`)
|
|
65
|
+
|
|
66
|
+
const updateBadgeDocument = graphql(`
|
|
67
|
+
mutation UpdateBadge($input: UpdateBadgeInput!) {
|
|
68
|
+
updateBadge(input: $input) {
|
|
69
|
+
id
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
`)
|
|
73
|
+
|
|
74
|
+
const getBadgePluginConfigDocument = graphql(`
|
|
75
|
+
query GetBadgePluginConfig {
|
|
76
|
+
getBadgePluginConfig {
|
|
77
|
+
availablePositions
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`)
|
|
81
|
+
|
|
82
|
+
const getCollectionsDocument = graphql(`
|
|
83
|
+
query GetCollections {
|
|
84
|
+
collections {
|
|
85
|
+
items {
|
|
86
|
+
id
|
|
87
|
+
name
|
|
88
|
+
slug
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
`)
|
|
93
|
+
|
|
94
|
+
const createAssetsDocument = graphql(`
|
|
95
|
+
mutation CreateAssets($input: [CreateAssetInput!]!) {
|
|
96
|
+
createAssets(input: $input) {
|
|
97
|
+
... on Asset {
|
|
98
|
+
id
|
|
99
|
+
name
|
|
100
|
+
source
|
|
101
|
+
preview
|
|
102
|
+
}
|
|
103
|
+
... on MimeTypeError {
|
|
104
|
+
message
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
`)
|
|
109
|
+
|
|
110
|
+
export const badgeDetailRoute: DashboardRouteDefinition = {
|
|
111
|
+
path: '/badges/$id',
|
|
112
|
+
loader: detailPageRouteLoader({
|
|
113
|
+
queryDocument: getBadgeDetailDocument,
|
|
114
|
+
breadcrumb: (isNew, entity) => [
|
|
115
|
+
{ path: '/badges', label: 'Badges' },
|
|
116
|
+
isNew ? 'New Badge' : `Badge ${entity?.id || ''}`,
|
|
117
|
+
],
|
|
118
|
+
}),
|
|
119
|
+
component: (route) => {
|
|
120
|
+
return <BadgeDetailPage route={route as any} />
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function BadgeDetailPage({ route }: { route: AnyRoute }) {
|
|
125
|
+
const params = route.useParams()
|
|
126
|
+
const navigate = useNavigate()
|
|
127
|
+
const queryClient = useQueryClient()
|
|
128
|
+
const creatingNewEntity = params.id === 'new'
|
|
129
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
130
|
+
const [uploadedAsset, setUploadedAsset] = useState<{
|
|
131
|
+
id: string
|
|
132
|
+
name: string
|
|
133
|
+
preview: string
|
|
134
|
+
source: string
|
|
135
|
+
} | null>(null)
|
|
136
|
+
const [uploading, setUploading] = useState(false)
|
|
137
|
+
|
|
138
|
+
const { data: configData } = useQuery({
|
|
139
|
+
queryKey: ['badge-plugin-config'],
|
|
140
|
+
queryFn: () => api.query(getBadgePluginConfigDocument),
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const { data: collectionsData } = useQuery({
|
|
144
|
+
queryKey: ['collections'],
|
|
145
|
+
queryFn: () => api.query(getCollectionsDocument),
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const availablePositions = configData?.getBadgePluginConfig?.availablePositions || []
|
|
149
|
+
|
|
150
|
+
const collections = collectionsData?.collections?.items || []
|
|
151
|
+
|
|
152
|
+
const setValuesForUpdate = useCallback(
|
|
153
|
+
(
|
|
154
|
+
badge:
|
|
155
|
+
| {
|
|
156
|
+
id: string
|
|
157
|
+
position: string
|
|
158
|
+
collectionId: string | null
|
|
159
|
+
assetId: string
|
|
160
|
+
asset: {
|
|
161
|
+
id: string
|
|
162
|
+
name: string
|
|
163
|
+
preview: string
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
| null
|
|
167
|
+
| undefined,
|
|
168
|
+
) => {
|
|
169
|
+
const position =
|
|
170
|
+
badge?.position && badge.position.trim() !== ''
|
|
171
|
+
? badge.position
|
|
172
|
+
: availablePositions?.length > 0
|
|
173
|
+
? availablePositions[0]
|
|
174
|
+
: ''
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
id: badge?.id ?? '',
|
|
178
|
+
position: position || 'top-left',
|
|
179
|
+
collectionId: badge?.collectionId ?? null,
|
|
180
|
+
assetId: badge?.assetId ?? '',
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
[availablePositions],
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
const { form, submitHandler, entity, isPending, resetForm, refreshEntity } = useDetailPage({
|
|
187
|
+
queryDocument: getBadgeDetailDocument,
|
|
188
|
+
createDocument: creatingNewEntity ? createBadgeDocument : undefined,
|
|
189
|
+
updateDocument: creatingNewEntity ? undefined : updateBadgeDocument,
|
|
190
|
+
setValuesForUpdate,
|
|
191
|
+
params: creatingNewEntity ? { id: 'new' } : { id: params.id },
|
|
192
|
+
onSuccess: async (data) => {
|
|
193
|
+
toast.success(creatingNewEntity ? 'Successfully created badge' : 'Successfully saved badge')
|
|
194
|
+
resetForm()
|
|
195
|
+
if (creatingNewEntity) {
|
|
196
|
+
await navigate({ to: `/badges/${data.id}` })
|
|
197
|
+
} else {
|
|
198
|
+
await refreshEntity()
|
|
199
|
+
}
|
|
200
|
+
queryClient.invalidateQueries({ queryKey: ['badges'] })
|
|
201
|
+
},
|
|
202
|
+
onError: (err) => {
|
|
203
|
+
toast.error(creatingNewEntity ? 'Failed to create badge' : 'Failed to save badge', {
|
|
204
|
+
description: err instanceof Error ? err.message : 'Unknown error',
|
|
205
|
+
})
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const createAssetMutation = useMutation({
|
|
210
|
+
mutationFn: async (file: File) => {
|
|
211
|
+
const input = [{ file }]
|
|
212
|
+
const result: any = await api.mutate(createAssetsDocument, { input })
|
|
213
|
+
const assetResult = result.createAssets[0]
|
|
214
|
+
if ('id' in assetResult) {
|
|
215
|
+
return assetResult
|
|
216
|
+
} else if ('message' in assetResult) {
|
|
217
|
+
throw new Error(assetResult.message)
|
|
218
|
+
}
|
|
219
|
+
throw new Error('Failed to upload asset')
|
|
220
|
+
},
|
|
221
|
+
onSuccess: (asset) => {
|
|
222
|
+
setUploadedAsset({
|
|
223
|
+
id: asset.id,
|
|
224
|
+
name: asset.name || 'Uploaded image',
|
|
225
|
+
preview: asset.preview || '',
|
|
226
|
+
source: asset.source || asset.preview || '',
|
|
227
|
+
})
|
|
228
|
+
form.setValue('assetId', asset.id, { shouldDirty: true, shouldValidate: true })
|
|
229
|
+
setUploading(false)
|
|
230
|
+
toast.success('Image uploaded successfully')
|
|
231
|
+
},
|
|
232
|
+
onError: (error) => {
|
|
233
|
+
toast.error('Failed to upload image', {
|
|
234
|
+
description: error instanceof Error ? error.message : 'Unknown error',
|
|
235
|
+
})
|
|
236
|
+
setUploading(false)
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
// Track synced entity ID to prevent re-syncing
|
|
241
|
+
const syncedEntityIdRef = useRef<string | null>(null)
|
|
242
|
+
const entityIdRef = useRef<string | null>(null)
|
|
243
|
+
|
|
244
|
+
// Sync uploadedAsset state when entity changes (only when entity.id changes)
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
if (entity?.id && entity.id !== entityIdRef.current) {
|
|
247
|
+
entityIdRef.current = entity.id
|
|
248
|
+
syncedEntityIdRef.current = null // Reset sync tracking when entity changes
|
|
249
|
+
if (entity?.asset) {
|
|
250
|
+
setUploadedAsset({
|
|
251
|
+
id: entity.asset.id,
|
|
252
|
+
name: entity.asset.name,
|
|
253
|
+
preview: entity.asset.preview,
|
|
254
|
+
source: entity.asset.source,
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}, [entity?.id, entity?.asset])
|
|
259
|
+
|
|
260
|
+
// Sync form values when entity loads (for edit mode)
|
|
261
|
+
// Run whenever entity data or availablePositions changes
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
const badge = entity
|
|
264
|
+
if (!creatingNewEntity && badge?.id) {
|
|
265
|
+
const entityPosition = badge.position
|
|
266
|
+
const entityCollectionId = badge.collectionId
|
|
267
|
+
|
|
268
|
+
// Set position - use entity value if available, otherwise keep what's there
|
|
269
|
+
if (entityPosition) {
|
|
270
|
+
if (availablePositions.length > 0 && availablePositions.includes(entityPosition)) {
|
|
271
|
+
form.setValue('position', entityPosition, { shouldDirty: false })
|
|
272
|
+
} else if (availablePositions.length === 0) {
|
|
273
|
+
// If positions not loaded yet, set it anyway (will be validated later)
|
|
274
|
+
form.setValue('position', entityPosition, { shouldDirty: false })
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Always sync collection - set it immediately
|
|
279
|
+
form.setValue('collectionId', entityCollectionId ?? null, { shouldDirty: false })
|
|
280
|
+
form.setValue('assetId', badge.assetId ?? '', { shouldDirty: false })
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Initialize default values for new badges
|
|
284
|
+
if (creatingNewEntity && availablePositions.length > 0) {
|
|
285
|
+
form.setValue('position', availablePositions[0])
|
|
286
|
+
form.setValue('collectionId', null)
|
|
287
|
+
}
|
|
288
|
+
}, [entity, availablePositions, creatingNewEntity, form])
|
|
289
|
+
|
|
290
|
+
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
291
|
+
const file = event.target.files?.[0]
|
|
292
|
+
if (file) {
|
|
293
|
+
if (!file.type.startsWith('image/')) {
|
|
294
|
+
toast.error('Please select an image file')
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
setUploading(true)
|
|
298
|
+
createAssetMutation.mutate(file)
|
|
299
|
+
}
|
|
300
|
+
if (fileInputRef.current) {
|
|
301
|
+
fileInputRef.current.value = ''
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
306
|
+
e.preventDefault()
|
|
307
|
+
|
|
308
|
+
if (creatingNewEntity) {
|
|
309
|
+
if (!uploadedAsset?.id) {
|
|
310
|
+
toast.error('Please upload an image first')
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const formValues = form.getValues()
|
|
315
|
+
try {
|
|
316
|
+
const result = await api.mutate(createBadgeDocument, {
|
|
317
|
+
input: {
|
|
318
|
+
assetId: uploadedAsset.id,
|
|
319
|
+
position: formValues.position || availablePositions[0] || 'top-left',
|
|
320
|
+
collectionId: formValues.collectionId || null,
|
|
321
|
+
},
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
toast.success('Badge created successfully')
|
|
325
|
+
await navigate({ to: `/badges/${result.createBadge.id}` })
|
|
326
|
+
queryClient.invalidateQueries({ queryKey: ['badges'] })
|
|
327
|
+
} catch (error: any) {
|
|
328
|
+
toast.error('Failed to create badge', {
|
|
329
|
+
description: error instanceof Error ? error.message : 'Unknown error',
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
await submitHandler(e)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const currentAsset =
|
|
339
|
+
uploadedAsset ||
|
|
340
|
+
(entity?.asset
|
|
341
|
+
? {
|
|
342
|
+
id: entity.asset.id,
|
|
343
|
+
name: entity.asset.name,
|
|
344
|
+
preview: entity.asset.preview,
|
|
345
|
+
source: entity.asset.source,
|
|
346
|
+
}
|
|
347
|
+
: null)
|
|
348
|
+
const currentAssetForPreview =
|
|
349
|
+
currentAsset == null
|
|
350
|
+
? null
|
|
351
|
+
: (entity?.asset && currentAsset.id === entity.asset.id
|
|
352
|
+
? entity.asset
|
|
353
|
+
: { ...currentAsset, type: 'IMAGE' }) as ComponentProps<typeof VendureImage>['asset']
|
|
354
|
+
const hasAssetChanged = Boolean(
|
|
355
|
+
!creatingNewEntity && uploadedAsset?.id && uploadedAsset.id !== entity?.assetId,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
return (
|
|
359
|
+
<Page pageId="badge-detail" form={form} submitHandler={handleSubmit}>
|
|
360
|
+
<PageTitle>
|
|
361
|
+
{creatingNewEntity ? 'New Badge' : entity?.id ? `Badge ${entity.id}` : 'Edit Badge'}
|
|
362
|
+
</PageTitle>
|
|
363
|
+
<PageActionBar>
|
|
364
|
+
<PageActionBarRight>
|
|
365
|
+
<Button
|
|
366
|
+
type="submit"
|
|
367
|
+
disabled={
|
|
368
|
+
creatingNewEntity
|
|
369
|
+
? !uploadedAsset || !form.formState.isValid || isPending || uploading
|
|
370
|
+
: (!form.formState.isDirty && !hasAssetChanged) || isPending || uploading
|
|
371
|
+
}
|
|
372
|
+
>
|
|
373
|
+
{isPending || uploading ? 'Saving...' : creatingNewEntity ? 'Create' : 'Update'}
|
|
374
|
+
</Button>
|
|
375
|
+
</PageActionBarRight>
|
|
376
|
+
</PageActionBar>
|
|
377
|
+
<PageLayout>
|
|
378
|
+
<PageBlock column="main" blockId="main-form">
|
|
379
|
+
<div className="space-y-4">
|
|
380
|
+
<div className="space-y-2" style={{ maxWidth: '400px' }}>
|
|
381
|
+
<Label>Image</Label>
|
|
382
|
+
<div className="space-y-4">
|
|
383
|
+
{currentAsset ? (
|
|
384
|
+
<div className="space-y-2">
|
|
385
|
+
<VendureImage asset={currentAssetForPreview} alt={currentAsset.name} preset="full" />
|
|
386
|
+
<div className="text-sm text-muted-foreground">{currentAsset.name}</div>
|
|
387
|
+
</div>
|
|
388
|
+
) : (
|
|
389
|
+
<div className="border-2 border-dashed border-muted rounded-lg p-8 text-center">
|
|
390
|
+
<UploadIcon className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
|
|
391
|
+
<p className="text-sm text-muted-foreground mb-4">No image uploaded</p>
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
394
|
+
<div>
|
|
395
|
+
<input
|
|
396
|
+
ref={fileInputRef}
|
|
397
|
+
type="file"
|
|
398
|
+
accept="image/*"
|
|
399
|
+
onChange={handleFileSelect}
|
|
400
|
+
className="hidden"
|
|
401
|
+
/>
|
|
402
|
+
<Button
|
|
403
|
+
type="button"
|
|
404
|
+
variant="outline"
|
|
405
|
+
onClick={() => fileInputRef.current?.click()}
|
|
406
|
+
disabled={uploading}
|
|
407
|
+
className="w-full"
|
|
408
|
+
>
|
|
409
|
+
<UploadIcon className="mr-2 h-4 w-4" />
|
|
410
|
+
{uploading ? 'Uploading...' : currentAsset ? 'Change Image' : 'Upload Image'}
|
|
411
|
+
</Button>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
|
|
416
|
+
<div style={{ maxWidth: '700px' }} className="space-y-4">
|
|
417
|
+
<FormFieldWrapper
|
|
418
|
+
control={form.control}
|
|
419
|
+
name="position"
|
|
420
|
+
label="Position"
|
|
421
|
+
render={({ field }) => {
|
|
422
|
+
// Ensure value is valid - use field.value if it exists and is in availablePositions, otherwise empty string
|
|
423
|
+
const validValue =
|
|
424
|
+
field.value && availablePositions.includes(field.value) ? field.value : ''
|
|
425
|
+
return (
|
|
426
|
+
<Select value={validValue} onValueChange={field.onChange}>
|
|
427
|
+
<SelectTrigger className="w-full">
|
|
428
|
+
<SelectValue placeholder="Select position" />
|
|
429
|
+
</SelectTrigger>
|
|
430
|
+
<SelectContent>
|
|
431
|
+
{availablePositions.map((pos) => (
|
|
432
|
+
<SelectItem key={pos} value={pos}>
|
|
433
|
+
{pos}
|
|
434
|
+
</SelectItem>
|
|
435
|
+
))}
|
|
436
|
+
</SelectContent>
|
|
437
|
+
</Select>
|
|
438
|
+
)
|
|
439
|
+
}}
|
|
440
|
+
/>
|
|
441
|
+
<FormFieldWrapper
|
|
442
|
+
control={form.control}
|
|
443
|
+
name="collectionId"
|
|
444
|
+
label="Collection"
|
|
445
|
+
render={({ field }) => (
|
|
446
|
+
<Select
|
|
447
|
+
value={field.value}
|
|
448
|
+
onValueChange={(val) => field.onChange(val === 'none' ? null : val)}
|
|
449
|
+
>
|
|
450
|
+
<SelectTrigger className="w-full">
|
|
451
|
+
<SelectValue placeholder="No collection" />
|
|
452
|
+
</SelectTrigger>
|
|
453
|
+
<SelectContent>
|
|
454
|
+
<SelectItem value="none">No collection</SelectItem>
|
|
455
|
+
{collections.map((collection) => (
|
|
456
|
+
<SelectItem key={collection.id} value={collection.id}>
|
|
457
|
+
{collection.name}
|
|
458
|
+
</SelectItem>
|
|
459
|
+
))}
|
|
460
|
+
</SelectContent>
|
|
461
|
+
</Select>
|
|
462
|
+
)}
|
|
463
|
+
/>
|
|
464
|
+
</div>
|
|
465
|
+
</div>
|
|
466
|
+
</PageBlock>
|
|
467
|
+
</PageLayout>
|
|
468
|
+
</Page>
|
|
469
|
+
)
|
|
470
|
+
}
|