diff --git a/src/components/shared/AuthenticatedImage.tsx b/src/components/shared/AuthenticatedImage.tsx index 7518d56..bf97227 100644 --- a/src/components/shared/AuthenticatedImage.tsx +++ b/src/components/shared/AuthenticatedImage.tsx @@ -1,104 +1,120 @@ -import { useState, useEffect, type ImgHTMLAttributes, type ReactElement } from 'react'; -import { fileService } from '@/services/file-service'; -import apiClient from '@/services/api-client'; -import { Loader2, ImageIcon } from 'lucide-react'; +import { + useState, + useEffect, + type ImgHTMLAttributes, + type ReactElement, +} from "react"; +import { fileService } from "@/services/file-service"; +import apiClient from "@/services/api-client"; +import { Loader2, ImageIcon } from "lucide-react"; -interface AuthenticatedImageProps extends Omit, 'src'> { - fileId?: string | null; - src?: string | null; - fallback?: ReactElement; +interface AuthenticatedImageProps extends Omit< + ImgHTMLAttributes, + "src" +> { + fileId?: string | null; + src?: string | null; + fallback?: ReactElement; } export const AuthenticatedImage = ({ - fileId, - src, - fallback, - className, - alt = 'Image', - ...props + fileId, + src, + fallback, + className, + alt = "Image", + ...props }: AuthenticatedImageProps): ReactElement => { - const [blobUrl, setBlobUrl] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(false); + const [blobUrl, setBlobUrl] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(false); - useEffect(() => { - // If it's already a blob URL (local preview) or a data URL, use it directly - if (src && (src.startsWith('blob:') || src.startsWith('data:'))) { - setBlobUrl(src); + useEffect(() => { + // If it's already a blob URL (local preview) or a data URL, use it directly + if (src && (src.startsWith("blob:") || src.startsWith("data:"))) { + setBlobUrl(src); + return; + } + + const baseUrl = + import.meta.env.VITE_API_BASE_URL || "http://localhost:3000"; + const isBackendUrl = + src && src.includes(`${baseUrl}/files/`) && src.includes("/preview"); + + // If we have a fileId or a backend URL, fetch it via authenticated request + if (fileId || isBackendUrl) { + let isMounted = true; + const fetchImage = async () => { + setIsLoading(true); + setError(false); + try { + let url: string; + if (fileId) { + url = await fileService.getPreview(fileId); + } else if (src) { + const response = await apiClient.get(src, { responseType: "blob" }); + url = URL.createObjectURL(response.data); + } else { return; + } + + if (isMounted) { + setBlobUrl(url); + } + } catch (err) { + console.error("Failed to fetch authenticated image:", err); + if (isMounted) { + setError(true); + } + } finally { + if (isMounted) { + setIsLoading(false); + } } + }; - const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000'; - const isBackendUrl = src && src.includes(`${baseUrl}/files/`) && src.includes('/preview'); + fetchImage(); - // If we have a fileId or a backend URL, fetch it via authenticated request - if (fileId || isBackendUrl) { - let isMounted = true; - const fetchImage = async () => { - setIsLoading(true); - setError(false); - try { - let url: string; - if (fileId) { - url = await fileService.getPreview(fileId); - } else if (src) { - const response = await apiClient.get(src, { responseType: 'blob' }); - url = URL.createObjectURL(response.data); - } else { - return; - } - - if (isMounted) { - setBlobUrl(url); - } - } catch (err) { - console.error('Failed to fetch authenticated image:', err); - if (isMounted) { - setError(true); - } - } finally { - if (isMounted) { - setIsLoading(false); - } - } - }; - - fetchImage(); - - return () => { - isMounted = false; - if (blobUrl && blobUrl.startsWith('blob:')) { - URL.revokeObjectURL(blobUrl); - } - }; - } else if (src) { - // For other external URLs, use them directly - setBlobUrl(src); + return () => { + isMounted = false; + if (blobUrl && blobUrl.startsWith("blob:")) { + URL.revokeObjectURL(blobUrl); } - }, [fileId, src]); - - if (isLoading) { - return ( -
- -
- ); - } - - if (error || (!blobUrl && !src)) { - return fallback || ( -
- -
- ); + }; + } else if (src) { + // For other external URLs, use them directly + setBlobUrl(src); } + }, [fileId, src]); + if (isLoading) { return ( - {alt} +
+ +
); + } + + if (error || (!blobUrl && !src)) { + return ( + fallback || ( +
+ +
+ ) + ); + } + + return ( + {alt} + ); }; diff --git a/src/components/shared/EditUserModal.tsx b/src/components/shared/EditUserModal.tsx index 6130219..1d5d73b 100644 --- a/src/components/shared/EditUserModal.tsx +++ b/src/components/shared/EditUserModal.tsx @@ -345,6 +345,17 @@ export const EditUserModal = ({ if (defaultTenantId) { data.tenant_id = defaultTenantId; } + // Normalize empty strings to null for optional UUID fields + if (!data.department_id) data.department_id = undefined; + if (!data.designation_id) data.designation_id = undefined; + + // If category is tenant_user, supplier_id should always be null + if (data.category === "tenant_user") { + data.supplier_id = null; + } else if (!data.supplier_id) { + data.supplier_id = null; + } + await onSubmit(userId, data); } catch (error: any) { if ( @@ -491,13 +502,13 @@ export const EditUserModal = ({ { value: "supplier_user", label: "Supplier User" }, ]} value={categoryValue || "tenant_user"} - onValueChange={(value) => - setValue( - "category", - value as "tenant_user" | "supplier_user", - { shouldValidate: true }, - ) - } + onValueChange={(value) => { + const category = value as "tenant_user" | "supplier_user"; + setValue("category", category, { shouldValidate: true }); + if (category === "tenant_user") { + setValue("supplier_id", null); + } + }} error={errors.category?.message} /> diff --git a/src/components/shared/NewUserModal.tsx b/src/components/shared/NewUserModal.tsx index 5e5b4bd..59e6a92 100644 --- a/src/components/shared/NewUserModal.tsx +++ b/src/components/shared/NewUserModal.tsx @@ -216,6 +216,17 @@ export const NewUserModal = ({ clearErrors(); try { const { confirmPassword, ...submitData } = data; + // Normalize empty strings to null for optional UUID fields + if (!submitData.department_id) submitData.department_id = undefined; + if (!submitData.designation_id) submitData.designation_id = undefined; + + // If category is tenant_user, supplier_id should always be null + if (submitData.category === "tenant_user") { + submitData.supplier_id = null; + } else if (!submitData.supplier_id) { + submitData.supplier_id = null; + } + await onSubmit(submitData); } catch (error: any) { // Handle validation errors from API @@ -387,11 +398,13 @@ export const NewUserModal = ({ { value: "supplier_user", label: "Supplier User" }, ]} value={categoryValue || "tenant_user"} - onValueChange={(value) => - setValue("category", value as "tenant_user" | "supplier_user", { - shouldValidate: true, - }) - } + onValueChange={(value) => { + const category = value as "tenant_user" | "supplier_user"; + setValue("category", category, { shouldValidate: true }); + if (category === "tenant_user") { + setValue("supplier_id", null); + } + }} error={errors.category?.message} />