Browse Source

chore(dashboard): Add missing file

Michael Bromley 5 months ago
parent
commit
8e7574bb4b

+ 368 - 0
packages/dashboard/src/lib/components/layout/manage-languages-dialog.tsx

@@ -0,0 +1,368 @@
+import { LanguageSelector } from '@/vdb/components/shared/language-selector.js';
+import { Button } from '@/vdb/components/ui/button.js';
+import {
+    Dialog,
+    DialogContent,
+    DialogDescription,
+    DialogFooter,
+    DialogHeader,
+    DialogTitle,
+} from '@/vdb/components/ui/dialog.js';
+import { Label } from '@/vdb/components/ui/label.js';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/vdb/components/ui/select.js';
+import { Separator } from '@/vdb/components/ui/separator.js';
+import { api } from '@/vdb/graphql/api.js';
+import { graphql } from '@/vdb/graphql/graphql.js';
+import { useChannel } from '@/vdb/hooks/use-channel.js';
+import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
+import { usePermissions } from '@/vdb/hooks/use-permissions.js';
+import { ChannelCodeLabel } from '@/vdb/components/shared/channel-code-label.js';
+import { Trans } from '@/vdb/lib/trans.js';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { AlertCircle, Lock } from 'lucide-react';
+import { useEffect, useState } from 'react';
+import { toast } from 'sonner';
+
+// GraphQL queries
+const globalSettingsLanguagesDocument = graphql(`
+    query GlobalSettingsLanguages {
+        globalSettings {
+            id
+            availableLanguages
+        }
+    }
+`);
+
+const updateGlobalSettingsLanguagesDocument = graphql(`
+    mutation UpdateGlobalSettingsLanguages($input: UpdateGlobalSettingsInput!) {
+        updateGlobalSettings(input: $input) {
+            __typename
+            ... on GlobalSettings {
+                id
+                availableLanguages
+            }
+            ... on ErrorResult {
+                errorCode
+                message
+            }
+        }
+    }
+`);
+
+const updateChannelDocument = graphql(`
+    mutation UpdateChannelLanguages($input: UpdateChannelInput!) {
+        updateChannel(input: $input) {
+            __typename
+            ... on Channel {
+                id
+                code
+                defaultLanguageCode
+                availableLanguageCodes
+            }
+            ... on ErrorResult {
+                errorCode
+                message
+            }
+        }
+    }
+`);
+
+// All possible language codes for global settings - includes more than what might be globally enabled
+const ALL_LANGUAGE_CODES = [
+    'en', 'es', 'fr', 'de', 'it', 'pt', 'nl', 'pl', 'ru', 'ja', 'zh', 'ko', 'ar', 'hi', 'sv', 'da', 'no', 'fi'
+];
+
+interface ManageLanguagesDialogProps {
+    open: boolean;
+    onClose: () => void;
+}
+
+export function ManageLanguagesDialog({ open, onClose }: ManageLanguagesDialogProps) {
+    const { formatLanguageName } = useLocalFormat();
+    const { activeChannel, selectedChannel } = useChannel();
+    const { hasPermissions } = usePermissions();
+    const queryClient = useQueryClient();
+    
+    const displayChannel = selectedChannel || activeChannel;
+    
+    // Permission checks
+    const canReadGlobalSettings = hasPermissions(['ReadSettings']) || hasPermissions(['ReadGlobalSettings']);
+    const canUpdateGlobalSettings = hasPermissions(['UpdateSettings']) || hasPermissions(['UpdateGlobalSettings']);
+    const canReadChannel = hasPermissions(['ReadChannel']);
+    const canUpdateChannel = hasPermissions(['UpdateChannel']);
+
+    // State for managing changes
+    const [globalLanguages, setGlobalLanguages] = useState<string[]>([]);
+    const [channelLanguages, setChannelLanguages] = useState<string[]>([]);
+    const [channelDefaultLanguage, setChannelDefaultLanguage] = useState<string>('');
+
+    // Queries
+    const { 
+        data: globalSettingsData, 
+        isLoading: globalSettingsLoading,
+        error: globalSettingsError 
+    } = useQuery({
+        queryKey: ['globalSettings', 'languages'],
+        queryFn: () => api.query(globalSettingsLanguagesDocument),
+        enabled: open && canReadGlobalSettings,
+    });
+
+    // Mutations
+    const updateGlobalSettingsMutation = useMutation({
+        mutationFn: (input: { availableLanguages: string[] }) =>
+            api.mutate(updateGlobalSettingsLanguagesDocument, { input }),
+        onSuccess: () => {
+            queryClient.invalidateQueries({ queryKey: ['globalSettings'] });
+            toast.success('Global language settings updated successfully');
+        },
+        onError: (error: any) => {
+            toast.error(`Failed to update global settings: ${error.message}`);
+        },
+    });
+
+    const updateChannelMutation = useMutation({
+        mutationFn: (input: { id: string; availableLanguageCodes?: string[]; defaultLanguageCode?: string }) =>
+            api.mutate(updateChannelDocument, { input }),
+        onSuccess: () => {
+            queryClient.invalidateQueries({ queryKey: ['channels'] });
+            toast.success('Channel language settings updated successfully');
+        },
+        onError: (error: any) => {
+            toast.error(`Failed to update channel settings: ${error.message}`);
+        },
+    });
+
+    // Initialize state when dialog opens
+    useEffect(() => {
+        if (open && globalSettingsData) {
+            setGlobalLanguages(globalSettingsData.globalSettings.availableLanguages || []);
+        }
+        if (open && displayChannel) {
+            setChannelLanguages(displayChannel.availableLanguageCodes || []);
+            setChannelDefaultLanguage(displayChannel.defaultLanguageCode || '');
+        }
+    }, [open, globalSettingsData, displayChannel]);
+
+    const handleGlobalLanguagesChange = (newLanguages: string[]) => {
+        setGlobalLanguages(newLanguages);
+        
+        // Remove channel languages that are no longer in global languages
+        const updatedChannelLanguages = channelLanguages.filter(lang => newLanguages.includes(lang));
+        setChannelLanguages(updatedChannelLanguages);
+        
+        // If the default language is no longer available, reset it
+        if (!newLanguages.includes(channelDefaultLanguage)) {
+            setChannelDefaultLanguage(updatedChannelLanguages[0] || '');
+        }
+    };
+
+    const handleChannelLanguagesChange = (newLanguages: string[]) => {
+        setChannelLanguages(newLanguages);
+        
+        // If the default language is no longer available, reset it
+        if (!newLanguages.includes(channelDefaultLanguage)) {
+            setChannelDefaultLanguage(newLanguages[0] || '');
+        }
+    };
+
+    const handleSave = async () => {
+        const promises = [];
+
+        // Update global settings if changed and permissions allow
+        if (canUpdateGlobalSettings && globalSettingsData) {
+            const currentGlobalLanguages = globalSettingsData.globalSettings.availableLanguages || [];
+            if (JSON.stringify(currentGlobalLanguages.sort()) !== JSON.stringify(globalLanguages.sort())) {
+                promises.push(updateGlobalSettingsMutation.mutateAsync({ availableLanguages: globalLanguages }));
+            }
+        }
+
+        // Update channel settings if changed and permissions allow
+        if (canUpdateChannel && displayChannel) {
+            const currentChannelLanguages = displayChannel.availableLanguageCodes || [];
+            const currentChannelDefault = displayChannel.defaultLanguageCode || '';
+            
+            const languagesChanged = JSON.stringify(currentChannelLanguages.sort()) !== JSON.stringify(channelLanguages.sort());
+            const defaultChanged = currentChannelDefault !== channelDefaultLanguage;
+            
+            if (languagesChanged || defaultChanged) {
+                promises.push(updateChannelMutation.mutateAsync({
+                    id: displayChannel.id,
+                    availableLanguageCodes: channelLanguages,
+                    defaultLanguageCode: channelDefaultLanguage,
+                }));
+            }
+        }
+
+        try {
+            await Promise.all(promises);
+            onClose();
+        } catch (error) {
+            // Error handling is done in mutation callbacks
+        }
+    };
+
+    const hasChanges = () => {
+        if (globalSettingsData && canUpdateGlobalSettings) {
+            const currentGlobal = globalSettingsData.globalSettings.availableLanguages || [];
+            if (JSON.stringify(currentGlobal.sort()) !== JSON.stringify(globalLanguages.sort())) {
+                return true;
+            }
+        }
+        
+        if (displayChannel && canUpdateChannel) {
+            const currentChannelLangs = displayChannel.availableLanguageCodes || [];
+            const currentChannelDefault = displayChannel.defaultLanguageCode || '';
+            
+            return (
+                JSON.stringify(currentChannelLangs.sort()) !== JSON.stringify(channelLanguages.sort()) ||
+                currentChannelDefault !== channelDefaultLanguage
+            );
+        }
+        
+        return false;
+    };
+
+    const isLoading = updateGlobalSettingsMutation.isPending || updateChannelMutation.isPending;
+
+    return (
+        <Dialog open={open} onOpenChange={onClose}>
+            <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
+                <DialogHeader>
+                    <DialogTitle><Trans>Manage Languages</Trans></DialogTitle>
+                    <DialogDescription>
+                        <Trans>Configure available languages for your store and channels</Trans>
+                    </DialogDescription>
+                </DialogHeader>
+
+                <div className="space-y-6">
+                    {/* Global Settings Section */}
+                    <div>
+                        <div className="flex items-center gap-2 mb-3">
+                            <h3 className="text-lg font-semibold"><Trans>Global Languages</Trans></h3>
+                            {!canReadGlobalSettings && <Lock className="h-4 w-4 text-muted-foreground" />}
+                        </div>
+                        
+                        {!canReadGlobalSettings ? (
+                            <div className="flex items-center gap-2 p-3 bg-muted rounded-md">
+                                <AlertCircle className="h-4 w-4 text-muted-foreground" />
+                                <span className="text-sm text-muted-foreground">
+                                    <Trans>You don't have permission to view global language settings</Trans>
+                                </span>
+                            </div>
+                        ) : globalSettingsLoading ? (
+                            <div className="text-sm text-muted-foreground">
+                                <Trans>Loading global settings...</Trans>
+                            </div>
+                        ) : globalSettingsError ? (
+                            <div className="flex items-center gap-2 p-3 bg-destructive/10 rounded-md">
+                                <AlertCircle className="h-4 w-4 text-destructive" />
+                                <span className="text-sm text-destructive">
+                                    <Trans>Failed to load global settings</Trans>
+                                </span>
+                            </div>
+                        ) : (
+                            <div className="space-y-2">
+                                <Label className="text-sm font-medium">
+                                    <Trans>Select Available Languages</Trans>
+                                </Label>
+                                <div className={!canUpdateGlobalSettings ? 'pointer-events-none opacity-50' : ''}>
+                                    <LanguageSelector
+                                        value={globalLanguages}
+                                        onChange={handleGlobalLanguagesChange}
+                                        multiple={true}
+                                        availableLanguageCodes={ALL_LANGUAGE_CODES}
+                                    />
+                                </div>
+                                <p className="text-xs text-muted-foreground">
+                                    <Trans>These languages will be available for all channels to use</Trans>
+                                </p>
+                            </div>
+                        )}
+                    </div>
+
+                    <Separator />
+
+                    {/* Channel Settings Section */}
+                    <div>
+                        <div className="flex items-center gap-2 mb-3">
+                            <h3 className="text-lg font-semibold">
+                                <Trans>Channel Languages</Trans> - <ChannelCodeLabel code={displayChannel?.code} />
+                            </h3>
+                            {!canReadChannel && <Lock className="h-4 w-4 text-muted-foreground" />}
+                        </div>
+
+                        {!canReadChannel ? (
+                            <div className="flex items-center gap-2 p-3 bg-muted rounded-md">
+                                <AlertCircle className="h-4 w-4 text-muted-foreground" />
+                                <span className="text-sm text-muted-foreground">
+                                    <Trans>You don't have permission to view channel settings</Trans>
+                                </span>
+                            </div>
+                        ) : (
+                            <div className="space-y-4">
+                                <div className="space-y-2">
+                                    <Label className="text-sm font-medium">
+                                        <Trans>Available Languages</Trans>
+                                    </Label>
+                                    <div className={!canUpdateChannel ? 'pointer-events-none opacity-50' : ''}>
+                                        <LanguageSelector
+                                            value={channelLanguages}
+                                            onChange={handleChannelLanguagesChange}
+                                            multiple={true}
+                                            availableLanguageCodes={globalLanguages}
+                                        />
+                                    </div>
+                                    {globalLanguages.length === 0 ? (
+                                        <p className="text-xs text-muted-foreground">
+                                            <Trans>No global languages configured</Trans>
+                                        </p>
+                                    ) : (
+                                        <p className="text-xs text-muted-foreground">
+                                            <Trans>Select from globally available languages for this channel</Trans>
+                                        </p>
+                                    )}
+                                </div>
+
+                                {channelLanguages.length > 0 && (
+                                    <div>
+                                        <Label className="text-sm font-medium mb-2 block">
+                                            <Trans>Default Language</Trans>
+                                        </Label>
+                                        <Select
+                                            value={channelDefaultLanguage}
+                                            onValueChange={setChannelDefaultLanguage}
+                                            disabled={!canUpdateChannel}
+                                        >
+                                            <SelectTrigger className="w-[200px]">
+                                                <SelectValue placeholder="Select default language" />
+                                            </SelectTrigger>
+                                            <SelectContent>
+                                                {channelLanguages.map(languageCode => (
+                                                    <SelectItem key={languageCode} value={languageCode}>
+                                                        {formatLanguageName(languageCode)} ({languageCode.toUpperCase()})
+                                                    </SelectItem>
+                                                ))}
+                                            </SelectContent>
+                                        </Select>
+                                    </div>
+                                )}
+                            </div>
+                        )}
+                    </div>
+                </div>
+
+                <DialogFooter>
+                    <Button variant="outline" onClick={onClose} disabled={isLoading}>
+                        <Trans>Cancel</Trans>
+                    </Button>
+                    <Button 
+                        onClick={handleSave} 
+                        disabled={!hasChanges() || isLoading}
+                    >
+                        {isLoading ? <Trans>Saving...</Trans> : <Trans>Save Changes</Trans>}
+                    </Button>
+                </DialogFooter>
+            </DialogContent>
+        </Dialog>
+    );
+}