@vendure/dashboard 3.3.8-master-202507310242 → 3.3.8

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.3.8-master-202507310242",
4
+ "version": "3.3.8",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -95,8 +95,8 @@
95
95
  "@types/react-dom": "^19.0.4",
96
96
  "@types/react-grid-layout": "^1.3.5",
97
97
  "@uidotdev/usehooks": "^2.4.1",
98
- "@vendure/common": "^3.3.8-master-202507310242",
99
- "@vendure/core": "^3.3.8-master-202507310242",
98
+ "@vendure/common": "3.3.8",
99
+ "@vendure/core": "3.3.8",
100
100
  "@vitejs/plugin-react": "^4.3.4",
101
101
  "acorn": "^8.11.3",
102
102
  "acorn-walk": "^8.3.2",
@@ -146,5 +146,5 @@
146
146
  "lightningcss-linux-arm64-musl": "^1.29.3",
147
147
  "lightningcss-linux-x64-musl": "^1.29.1"
148
148
  },
149
- "gitHead": "146703259a0efc748d92322ed5713a15503a015d"
149
+ "gitHead": "8623b43cf39ca82d24a106deffc1b2648c8110c0"
150
150
  }
@@ -0,0 +1,368 @@
1
+ import { LanguageSelector } from '@/vdb/components/shared/language-selector.js';
2
+ import { Button } from '@/vdb/components/ui/button.js';
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogFooter,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from '@/vdb/components/ui/dialog.js';
11
+ import { Label } from '@/vdb/components/ui/label.js';
12
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
13
+ import { Separator } from '@/vdb/components/ui/separator.js';
14
+ import { api } from '@/vdb/graphql/api.js';
15
+ import { graphql } from '@/vdb/graphql/graphql.js';
16
+ import { useChannel } from '@/vdb/hooks/use-channel.js';
17
+ import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
18
+ import { usePermissions } from '@/vdb/hooks/use-permissions.js';
19
+ import { ChannelCodeLabel } from '@/vdb/components/shared/channel-code-label.js';
20
+ import { Trans } from '@/vdb/lib/trans.js';
21
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
22
+ import { AlertCircle, Lock } from 'lucide-react';
23
+ import { useEffect, useState } from 'react';
24
+ import { toast } from 'sonner';
25
+
26
+ // GraphQL queries
27
+ const globalSettingsLanguagesDocument = graphql(`
28
+ query GlobalSettingsLanguages {
29
+ globalSettings {
30
+ id
31
+ availableLanguages
32
+ }
33
+ }
34
+ `);
35
+
36
+ const updateGlobalSettingsLanguagesDocument = graphql(`
37
+ mutation UpdateGlobalSettingsLanguages($input: UpdateGlobalSettingsInput!) {
38
+ updateGlobalSettings(input: $input) {
39
+ __typename
40
+ ... on GlobalSettings {
41
+ id
42
+ availableLanguages
43
+ }
44
+ ... on ErrorResult {
45
+ errorCode
46
+ message
47
+ }
48
+ }
49
+ }
50
+ `);
51
+
52
+ const updateChannelDocument = graphql(`
53
+ mutation UpdateChannelLanguages($input: UpdateChannelInput!) {
54
+ updateChannel(input: $input) {
55
+ __typename
56
+ ... on Channel {
57
+ id
58
+ code
59
+ defaultLanguageCode
60
+ availableLanguageCodes
61
+ }
62
+ ... on ErrorResult {
63
+ errorCode
64
+ message
65
+ }
66
+ }
67
+ }
68
+ `);
69
+
70
+ // All possible language codes for global settings - includes more than what might be globally enabled
71
+ const ALL_LANGUAGE_CODES = [
72
+ 'en', 'es', 'fr', 'de', 'it', 'pt', 'nl', 'pl', 'ru', 'ja', 'zh', 'ko', 'ar', 'hi', 'sv', 'da', 'no', 'fi'
73
+ ];
74
+
75
+ interface ManageLanguagesDialogProps {
76
+ open: boolean;
77
+ onClose: () => void;
78
+ }
79
+
80
+ export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogProps) {
81
+ const { formatLanguageName } = useLocalFormat();
82
+ const { activeChannel, selectedChannel } = useChannel();
83
+ const { hasPermissions } = usePermissions();
84
+ const queryClient = useQueryClient();
85
+
86
+ const displayChannel = selectedChannel || activeChannel;
87
+
88
+ // Permission checks
89
+ const canReadGlobalSettings = hasPermissions(['ReadSettings']) || hasPermissions(['ReadGlobalSettings']);
90
+ const canUpdateGlobalSettings = hasPermissions(['UpdateSettings']) || hasPermissions(['UpdateGlobalSettings']);
91
+ const canReadChannel = hasPermissions(['ReadChannel']);
92
+ const canUpdateChannel = hasPermissions(['UpdateChannel']);
93
+
94
+ // State for managing changes
95
+ const [globalLanguages, setGlobalLanguages] = useState<string[]>([]);
96
+ const [channelLanguages, setChannelLanguages] = useState<string[]>([]);
97
+ const [channelDefaultLanguage, setChannelDefaultLanguage] = useState<string>('');
98
+
99
+ // Queries
100
+ const {
101
+ data: globalSettingsData,
102
+ isLoading: globalSettingsLoading,
103
+ error: globalSettingsError
104
+ } = useQuery({
105
+ queryKey: ['globalSettings', 'languages'],
106
+ queryFn: () => api.query(globalSettingsLanguagesDocument),
107
+ enabled: open && canReadGlobalSettings,
108
+ });
109
+
110
+ // Mutations
111
+ const updateGlobalSettingsMutation = useMutation({
112
+ mutationFn: (input: { availableLanguages: string[] }) =>
113
+ api.mutate(updateGlobalSettingsLanguagesDocument, { input }),
114
+ onSuccess: () => {
115
+ queryClient.invalidateQueries({ queryKey: ['globalSettings'] });
116
+ toast.success('Global language settings updated successfully');
117
+ },
118
+ onError: (error: any) => {
119
+ toast.error(`Failed to update global settings: ${error.message}`);
120
+ },
121
+ });
122
+
123
+ const updateChannelMutation = useMutation({
124
+ mutationFn: (input: { id: string; availableLanguageCodes?: string[]; defaultLanguageCode?: string }) =>
125
+ api.mutate(updateChannelDocument, { input }),
126
+ onSuccess: () => {
127
+ queryClient.invalidateQueries({ queryKey: ['channels'] });
128
+ toast.success('Channel language settings updated successfully');
129
+ },
130
+ onError: (error: any) => {
131
+ toast.error(`Failed to update channel settings: ${error.message}`);
132
+ },
133
+ });
134
+
135
+ // Initialize state when dialog opens
136
+ useEffect(() => {
137
+ if (open && globalSettingsData) {
138
+ setGlobalLanguages(globalSettingsData.globalSettings.availableLanguages || []);
139
+ }
140
+ if (open && displayChannel) {
141
+ setChannelLanguages(displayChannel.availableLanguageCodes || []);
142
+ setChannelDefaultLanguage(displayChannel.defaultLanguageCode || '');
143
+ }
144
+ }, [open, globalSettingsData, displayChannel]);
145
+
146
+ const handleGlobalLanguagesChange = (newLanguages: string[]) => {
147
+ setGlobalLanguages(newLanguages);
148
+
149
+ // Remove channel languages that are no longer in global languages
150
+ const updatedChannelLanguages = channelLanguages.filter(lang => newLanguages.includes(lang));
151
+ setChannelLanguages(updatedChannelLanguages);
152
+
153
+ // If the default language is no longer available, reset it
154
+ if (!newLanguages.includes(channelDefaultLanguage)) {
155
+ setChannelDefaultLanguage(updatedChannelLanguages[0] || '');
156
+ }
157
+ };
158
+
159
+ const handleChannelLanguagesChange = (newLanguages: string[]) => {
160
+ setChannelLanguages(newLanguages);
161
+
162
+ // If the default language is no longer available, reset it
163
+ if (!newLanguages.includes(channelDefaultLanguage)) {
164
+ setChannelDefaultLanguage(newLanguages[0] || '');
165
+ }
166
+ };
167
+
168
+ const handleSave = async () => {
169
+ const promises = [];
170
+
171
+ // Update global settings if changed and permissions allow
172
+ if (canUpdateGlobalSettings && globalSettingsData) {
173
+ const currentGlobalLanguages = globalSettingsData.globalSettings.availableLanguages || [];
174
+ if (JSON.stringify(currentGlobalLanguages.sort()) !== JSON.stringify(globalLanguages.sort())) {
175
+ promises.push(updateGlobalSettingsMutation.mutateAsync({ availableLanguages: globalLanguages }));
176
+ }
177
+ }
178
+
179
+ // Update channel settings if changed and permissions allow
180
+ if (canUpdateChannel && displayChannel) {
181
+ const currentChannelLanguages = displayChannel.availableLanguageCodes || [];
182
+ const currentChannelDefault = displayChannel.defaultLanguageCode || '';
183
+
184
+ const languagesChanged = JSON.stringify(currentChannelLanguages.sort()) !== JSON.stringify(channelLanguages.sort());
185
+ const defaultChanged = currentChannelDefault !== channelDefaultLanguage;
186
+
187
+ if (languagesChanged || defaultChanged) {
188
+ promises.push(updateChannelMutation.mutateAsync({
189
+ id: displayChannel.id,
190
+ availableLanguageCodes: channelLanguages,
191
+ defaultLanguageCode: channelDefaultLanguage,
192
+ }));
193
+ }
194
+ }
195
+
196
+ try {
197
+ await Promise.all(promises);
198
+ onClose();
199
+ } catch (error) {
200
+ // Error handling is done in mutation callbacks
201
+ }
202
+ };
203
+
204
+ const hasChanges = () => {
205
+ if (globalSettingsData && canUpdateGlobalSettings) {
206
+ const currentGlobal = globalSettingsData.globalSettings.availableLanguages || [];
207
+ if (JSON.stringify(currentGlobal.sort()) !== JSON.stringify(globalLanguages.sort())) {
208
+ return true;
209
+ }
210
+ }
211
+
212
+ if (displayChannel && canUpdateChannel) {
213
+ const currentChannelLangs = displayChannel.availableLanguageCodes || [];
214
+ const currentChannelDefault = displayChannel.defaultLanguageCode || '';
215
+
216
+ return (
217
+ JSON.stringify(currentChannelLangs.sort()) !== JSON.stringify(channelLanguages.sort()) ||
218
+ currentChannelDefault !== channelDefaultLanguage
219
+ );
220
+ }
221
+
222
+ return false;
223
+ };
224
+
225
+ const isLoading = updateGlobalSettingsMutation.isPending || updateChannelMutation.isPending;
226
+
227
+ return (
228
+ <Dialog open={open} onOpenChange={onClose}>
229
+ <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
230
+ <DialogHeader>
231
+ <DialogTitle><Trans>Manage Languages</Trans></DialogTitle>
232
+ <DialogDescription>
233
+ <Trans>Configure available languages for your store and channels</Trans>
234
+ </DialogDescription>
235
+ </DialogHeader>
236
+
237
+ <div className="space-y-6">
238
+ {/* Global Settings Section */}
239
+ <div>
240
+ <div className="flex items-center gap-2 mb-3">
241
+ <h3 className="text-lg font-semibold"><Trans>Global Languages</Trans></h3>
242
+ {!canReadGlobalSettings && <Lock className="h-4 w-4 text-muted-foreground" />}
243
+ </div>
244
+
245
+ {!canReadGlobalSettings ? (
246
+ <div className="flex items-center gap-2 p-3 bg-muted rounded-md">
247
+ <AlertCircle className="h-4 w-4 text-muted-foreground" />
248
+ <span className="text-sm text-muted-foreground">
249
+ <Trans>You don't have permission to view global language settings</Trans>
250
+ </span>
251
+ </div>
252
+ ) : globalSettingsLoading ? (
253
+ <div className="text-sm text-muted-foreground">
254
+ <Trans>Loading global settings...</Trans>
255
+ </div>
256
+ ) : globalSettingsError ? (
257
+ <div className="flex items-center gap-2 p-3 bg-destructive/10 rounded-md">
258
+ <AlertCircle className="h-4 w-4 text-destructive" />
259
+ <span className="text-sm text-destructive">
260
+ <Trans>Failed to load global settings</Trans>
261
+ </span>
262
+ </div>
263
+ ) : (
264
+ <div className="space-y-2">
265
+ <Label className="text-sm font-medium">
266
+ <Trans>Select Available Languages</Trans>
267
+ </Label>
268
+ <div className={!canUpdateGlobalSettings ? 'pointer-events-none opacity-50' : ''}>
269
+ <LanguageSelector
270
+ value={globalLanguages}
271
+ onChange={handleGlobalLanguagesChange}
272
+ multiple={true}
273
+ availableLanguageCodes={ALL_LANGUAGE_CODES}
274
+ />
275
+ </div>
276
+ <p className="text-xs text-muted-foreground">
277
+ <Trans>These languages will be available for all channels to use</Trans>
278
+ </p>
279
+ </div>
280
+ )}
281
+ </div>
282
+
283
+ <Separator />
284
+
285
+ {/* Channel Settings Section */}
286
+ <div>
287
+ <div className="flex items-center gap-2 mb-3">
288
+ <h3 className="text-lg font-semibold">
289
+ <Trans>Channel Languages</Trans> - <ChannelCodeLabel code={displayChannel?.code} />
290
+ </h3>
291
+ {!canReadChannel && <Lock className="h-4 w-4 text-muted-foreground" />}
292
+ </div>
293
+
294
+ {!canReadChannel ? (
295
+ <div className="flex items-center gap-2 p-3 bg-muted rounded-md">
296
+ <AlertCircle className="h-4 w-4 text-muted-foreground" />
297
+ <span className="text-sm text-muted-foreground">
298
+ <Trans>You don't have permission to view channel settings</Trans>
299
+ </span>
300
+ </div>
301
+ ) : (
302
+ <div className="space-y-4">
303
+ <div className="space-y-2">
304
+ <Label className="text-sm font-medium">
305
+ <Trans>Available Languages</Trans>
306
+ </Label>
307
+ <div className={!canUpdateChannel ? 'pointer-events-none opacity-50' : ''}>
308
+ <LanguageSelector
309
+ value={channelLanguages}
310
+ onChange={handleChannelLanguagesChange}
311
+ multiple={true}
312
+ availableLanguageCodes={globalLanguages}
313
+ />
314
+ </div>
315
+ {globalLanguages.length === 0 ? (
316
+ <p className="text-xs text-muted-foreground">
317
+ <Trans>No global languages configured</Trans>
318
+ </p>
319
+ ) : (
320
+ <p className="text-xs text-muted-foreground">
321
+ <Trans>Select from globally available languages for this channel</Trans>
322
+ </p>
323
+ )}
324
+ </div>
325
+
326
+ {channelLanguages.length > 0 && (
327
+ <div>
328
+ <Label className="text-sm font-medium mb-2 block">
329
+ <Trans>Default Language</Trans>
330
+ </Label>
331
+ <Select
332
+ value={channelDefaultLanguage}
333
+ onValueChange={setChannelDefaultLanguage}
334
+ disabled={!canUpdateChannel}
335
+ >
336
+ <SelectTrigger className="w-[200px]">
337
+ <SelectValue placeholder="Select default language" />
338
+ </SelectTrigger>
339
+ <SelectContent>
340
+ {channelLanguages.map(languageCode => (
341
+ <SelectItem key={languageCode} value={languageCode}>
342
+ {formatLanguageName(languageCode)} ({languageCode.toUpperCase()})
343
+ </SelectItem>
344
+ ))}
345
+ </SelectContent>
346
+ </Select>
347
+ </div>
348
+ )}
349
+ </div>
350
+ )}
351
+ </div>
352
+ </div>
353
+
354
+ <DialogFooter>
355
+ <Button variant="outline" onClick={onClose} disabled={isLoading}>
356
+ <Trans>Cancel</Trans>
357
+ </Button>
358
+ <Button
359
+ onClick={handleSave}
360
+ disabled={!hasChanges() || isLoading}
361
+ >
362
+ {isLoading ? <Trans>Saving...</Trans> : <Trans>Save Changes</Trans>}
363
+ </Button>
364
+ </DialogFooter>
365
+ </DialogContent>
366
+ </Dialog>
367
+ );
368
+ }