'use client' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' import { toast } from 'sonner' import { HardDrive, Loader2, Cloud, FolderOpen } from 'lucide-react' import { trpc } from '@/lib/trpc/client' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Checkbox } from '@/components/ui/checkbox' import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' import { Label } from '@/components/ui/label' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' // Note: Storage provider cache is cleared server-side when settings are updated const COMMON_FILE_TYPES = [ { value: 'application/pdf', label: 'PDF Documents (.pdf)' }, { value: 'video/mp4', label: 'MP4 Video (.mp4)' }, { value: 'video/quicktime', label: 'QuickTime Video (.mov)' }, { value: 'image/png', label: 'PNG Images (.png)' }, { value: 'image/jpeg', label: 'JPEG Images (.jpg, .jpeg)' }, { value: 'image/gif', label: 'GIF Images (.gif)' }, { value: 'image/webp', label: 'WebP Images (.webp)' }, { value: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', label: 'Word Documents (.docx)' }, { value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', label: 'Excel Spreadsheets (.xlsx)' }, { value: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', label: 'PowerPoint (.pptx)' }, ] const formSchema = z.object({ storage_provider: z.enum(['s3', 'local']), local_storage_path: z.string().optional(), max_file_size_mb: z.string().regex(/^\d+$/, 'Must be a number'), avatar_max_size_mb: z.string().regex(/^\d+$/, 'Must be a number'), allowed_file_types: z.array(z.string()).min(1, 'Select at least one file type'), }) type FormValues = z.infer interface StorageSettingsFormProps { settings: { storage_provider?: string local_storage_path?: string max_file_size_mb?: string avatar_max_size_mb?: string allowed_file_types?: string } } export function StorageSettingsForm({ settings }: StorageSettingsFormProps) { const utils = trpc.useUtils() // Parse allowed file types from JSON string let allowedTypes: string[] = [] try { allowedTypes = settings.allowed_file_types ? JSON.parse(settings.allowed_file_types) : ['application/pdf', 'video/mp4', 'video/quicktime', 'image/png', 'image/jpeg'] } catch { allowedTypes = ['application/pdf', 'video/mp4', 'video/quicktime', 'image/png', 'image/jpeg'] } const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { storage_provider: (settings.storage_provider as 's3' | 'local') || 's3', local_storage_path: settings.local_storage_path || './uploads', max_file_size_mb: settings.max_file_size_mb || '500', avatar_max_size_mb: settings.avatar_max_size_mb || '5', allowed_file_types: allowedTypes, }, }) const storageProvider = form.watch('storage_provider') const updateSettings = trpc.settings.updateMultiple.useMutation({ onSuccess: () => { toast.success('Storage settings saved successfully') utils.settings.getByCategory.invalidate({ category: 'STORAGE' }) }, onError: (error) => { toast.error(`Failed to save settings: ${error.message}`) }, }) const onSubmit = (data: FormValues) => { updateSettings.mutate({ settings: [ { key: 'storage_provider', value: data.storage_provider }, { key: 'local_storage_path', value: data.local_storage_path || './uploads' }, { key: 'max_file_size_mb', value: data.max_file_size_mb }, { key: 'avatar_max_size_mb', value: data.avatar_max_size_mb }, { key: 'allowed_file_types', value: JSON.stringify(data.allowed_file_types) }, ], }) } return (
{/* Storage Provider Selection */} ( Storage Provider
)} /> {/* Local Storage Path (only shown when local is selected) */} {storageProvider === 'local' && ( ( Local Storage Path Directory path where files will be stored. Relative paths are from the app root. )} /> )} ( Maximum File Size (MB) Maximum allowed file upload size in megabytes. Recommended: 500 MB for video uploads. )} /> ( Maximum Avatar/Logo Size (MB) Maximum size for profile pictures and project logos. )} /> (
Allowed File Types Select which file types can be uploaded to the platform
{COMMON_FILE_TYPES.map((type) => ( { return ( { return checked ? field.onChange([...field.value, type.value]) : field.onChange( field.value?.filter( (value) => value !== type.value ) ) }} /> {type.label} ) }} /> ))}
)} /> {storageProvider === 's3' && (

Note: MinIO connection settings (endpoint, bucket, credentials) are configured via environment variables for security. Set MINIO_ENDPOINT and{' '} MINIO_PUBLIC_ENDPOINT for external MinIO servers.

)} {storageProvider === 'local' && (

Warning: Local storage is not recommended for production deployments with multiple servers, as files will only be accessible from the server that uploaded them.

)} ) }