refactor: Standardize string literals to use double quotes and apply minor formatting across tenant management and settings components.
This commit is contained in:
parent
f460a89201
commit
1025504a55
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,17 @@
|
||||
import { Layout } from '@/components/layout/Layout';
|
||||
import { ImageIcon, Loader2, X } from 'lucide-react';
|
||||
import { useState, useEffect, type ReactElement } from 'react';
|
||||
import { useAppSelector, useAppDispatch } from '@/hooks/redux-hooks';
|
||||
import { tenantService } from '@/services/tenant-service';
|
||||
import { fileService } from '@/services/file-service';
|
||||
import { showToast } from '@/utils/toast';
|
||||
import { updateTheme } from '@/store/themeSlice';
|
||||
import { PrimaryButton, AuthenticatedImage } from '@/components/shared';
|
||||
import type { Tenant } from '@/types/tenant';
|
||||
import { Layout } from "@/components/layout/Layout";
|
||||
import { ImageIcon, Loader2, X } from "lucide-react";
|
||||
import { useState, useEffect, type ReactElement } from "react";
|
||||
import { useAppSelector, useAppDispatch } from "@/hooks/redux-hooks";
|
||||
import { tenantService } from "@/services/tenant-service";
|
||||
import { fileService } from "@/services/file-service";
|
||||
import { showToast } from "@/utils/toast";
|
||||
import { updateTheme } from "@/store/themeSlice";
|
||||
import { PrimaryButton, AuthenticatedImage } from "@/components/shared";
|
||||
import type { Tenant } from "@/types/tenant";
|
||||
|
||||
// Helper function to get base URL with protocol
|
||||
const getBaseUrlWithProtocol = (): string => {
|
||||
return import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000';
|
||||
return import.meta.env.VITE_API_BASE_URL || "http://localhost:3000";
|
||||
};
|
||||
|
||||
const Settings = (): ReactElement => {
|
||||
@ -26,14 +26,16 @@ const Settings = (): ReactElement => {
|
||||
const [tenant, setTenant] = useState<Tenant | null>(null);
|
||||
|
||||
// Color states
|
||||
const [primaryColor, setPrimaryColor] = useState<string>('#112868');
|
||||
const [secondaryColor, setSecondaryColor] = useState<string>('#23DCE1');
|
||||
const [accentColor, setAccentColor] = useState<string>('#084CC8');
|
||||
const [primaryColor, setPrimaryColor] = useState<string>("#112868");
|
||||
const [secondaryColor, setSecondaryColor] = useState<string>("#23DCE1");
|
||||
const [accentColor, setAccentColor] = useState<string>("#084CC8");
|
||||
|
||||
// Logo states
|
||||
const [logoFile, setLogoFile] = useState<File | null>(null);
|
||||
const [logoFilePath, setLogoFilePath] = useState<string | null>(null);
|
||||
const [logoFileAttachmentUuid, setLogoFileAttachmentUuid] = useState<string | null>(null);
|
||||
const [logoFileAttachmentUuid, setLogoFileAttachmentUuid] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [logoFileUrl, setLogoFileUrl] = useState<string | null>(null);
|
||||
const [logoPreviewUrl, setLogoPreviewUrl] = useState<string | null>(null);
|
||||
const [isUploadingLogo, setIsUploadingLogo] = useState<boolean>(false);
|
||||
@ -41,9 +43,13 @@ const Settings = (): ReactElement => {
|
||||
// Favicon states
|
||||
const [faviconFile, setFaviconFile] = useState<File | null>(null);
|
||||
const [faviconFilePath, setFaviconFilePath] = useState<string | null>(null);
|
||||
const [faviconFileAttachmentUuid, setFaviconFileAttachmentUuid] = useState<string | null>(null);
|
||||
const [faviconFileAttachmentUuid, setFaviconFileAttachmentUuid] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [faviconFileUrl, setFaviconFileUrl] = useState<string | null>(null);
|
||||
const [faviconPreviewUrl, setFaviconPreviewUrl] = useState<string | null>(null);
|
||||
const [faviconPreviewUrl, setFaviconPreviewUrl] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [isUploadingFavicon, setIsUploadingFavicon] = useState<boolean>(false);
|
||||
const [logoError, setLogoError] = useState<string | null>(null);
|
||||
const [faviconError, setFaviconError] = useState<string | null>(null);
|
||||
@ -52,7 +58,7 @@ const Settings = (): ReactElement => {
|
||||
useEffect(() => {
|
||||
const fetchTenant = async (): Promise<void> => {
|
||||
if (!tenantId) {
|
||||
setError('Tenant ID not found');
|
||||
setError("Tenant ID not found");
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
@ -67,15 +73,17 @@ const Settings = (): ReactElement => {
|
||||
setTenant(tenantData);
|
||||
|
||||
// Set colors
|
||||
setPrimaryColor(tenantData.primary_color || '#112868');
|
||||
setSecondaryColor(tenantData.secondary_color || '#23DCE1');
|
||||
setAccentColor(tenantData.accent_color || '#084CC8');
|
||||
setPrimaryColor(tenantData.primary_color || "#112868");
|
||||
setSecondaryColor(tenantData.secondary_color || "#23DCE1");
|
||||
setAccentColor(tenantData.accent_color || "#084CC8");
|
||||
|
||||
// Set logo
|
||||
if (tenantData.logo_file_path) {
|
||||
setLogoFileUrl(tenantData.logo_file_path);
|
||||
setLogoFilePath(tenantData.logo_file_path);
|
||||
setLogoFileAttachmentUuid(tenantData.logo_file_attachment_uuid || null);
|
||||
setLogoFileAttachmentUuid(
|
||||
tenantData.logo_file_attachment_uuid || null,
|
||||
);
|
||||
setLogoError(null);
|
||||
}
|
||||
|
||||
@ -83,7 +91,9 @@ const Settings = (): ReactElement => {
|
||||
if (tenantData.favicon_file_path) {
|
||||
setFaviconFileUrl(tenantData.favicon_file_path);
|
||||
setFaviconFilePath(tenantData.favicon_file_path);
|
||||
setFaviconFileAttachmentUuid(tenantData.favicon_file_attachment_uuid || null);
|
||||
setFaviconFileAttachmentUuid(
|
||||
tenantData.favicon_file_attachment_uuid || null,
|
||||
);
|
||||
setFaviconError(null);
|
||||
}
|
||||
}
|
||||
@ -92,7 +102,7 @@ const Settings = (): ReactElement => {
|
||||
err?.response?.data?.error?.message ||
|
||||
err?.response?.data?.message ||
|
||||
err?.message ||
|
||||
'Failed to load tenant settings';
|
||||
"Failed to load tenant settings";
|
||||
setError(errorMessage);
|
||||
showToast.error(errorMessage);
|
||||
} finally {
|
||||
@ -115,20 +125,27 @@ const Settings = (): ReactElement => {
|
||||
};
|
||||
}, [logoPreviewUrl, faviconPreviewUrl]);
|
||||
|
||||
const handleLogoChange = async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
|
||||
const handleLogoChange = async (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
): Promise<void> => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// Validate file size (2MB max)
|
||||
if (file.size > 2 * 1024 * 1024) {
|
||||
showToast.error('Logo file size must be less than 2MB');
|
||||
showToast.error("Logo file size must be less than 2MB");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file type
|
||||
const validTypes = ['image/png', 'image/svg+xml', 'image/jpeg', 'image/jpg'];
|
||||
const validTypes = [
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
];
|
||||
if (!validTypes.includes(file.type)) {
|
||||
showToast.error('Logo must be PNG, SVG, or JPG format');
|
||||
showToast.error("Logo must be PNG, SVG, or JPG format");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -143,7 +160,12 @@ const Settings = (): ReactElement => {
|
||||
setIsUploadingLogo(true);
|
||||
|
||||
try {
|
||||
const response = await fileService.upload(file, 'tenant', tenantId || undefined);
|
||||
const response = await fileService.upload(
|
||||
file,
|
||||
"tenant",
|
||||
crypto.randomUUID(),
|
||||
tenantId || undefined,
|
||||
);
|
||||
const fileId = response.data.id;
|
||||
setLogoFileAttachmentUuid(fileId);
|
||||
|
||||
@ -153,13 +175,13 @@ const Settings = (): ReactElement => {
|
||||
setLogoFileUrl(formattedUrl);
|
||||
|
||||
setLogoError(null);
|
||||
showToast.success('Logo uploaded successfully');
|
||||
showToast.success("Logo uploaded successfully");
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err?.response?.data?.error?.message ||
|
||||
err?.response?.data?.message ||
|
||||
err?.message ||
|
||||
'Failed to upload logo. Please try again.';
|
||||
"Failed to upload logo. Please try again.";
|
||||
showToast.error(errorMessage);
|
||||
setLogoFile(null);
|
||||
URL.revokeObjectURL(previewUrl);
|
||||
@ -172,20 +194,26 @@ const Settings = (): ReactElement => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleFaviconChange = async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
|
||||
const handleFaviconChange = async (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
): Promise<void> => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// Validate file size (500KB max)
|
||||
if (file.size > 500 * 1024) {
|
||||
showToast.error('Favicon file size must be less than 500KB');
|
||||
showToast.error("Favicon file size must be less than 500KB");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file type
|
||||
const validTypes = ['image/x-icon', 'image/png', 'image/vnd.microsoft.icon'];
|
||||
const validTypes = [
|
||||
"image/x-icon",
|
||||
"image/png",
|
||||
"image/vnd.microsoft.icon",
|
||||
];
|
||||
if (!validTypes.includes(file.type)) {
|
||||
showToast.error('Favicon must be ICO or PNG format');
|
||||
showToast.error("Favicon must be ICO or PNG format");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -200,7 +228,12 @@ const Settings = (): ReactElement => {
|
||||
setIsUploadingFavicon(true);
|
||||
|
||||
try {
|
||||
const response = await fileService.upload(file, 'tenant', tenantId || undefined);
|
||||
const response = await fileService.upload(
|
||||
file,
|
||||
"tenant",
|
||||
crypto.randomUUID(),
|
||||
tenantId || undefined,
|
||||
);
|
||||
const fileId = response.data.id;
|
||||
setFaviconFileAttachmentUuid(fileId);
|
||||
|
||||
@ -210,13 +243,13 @@ const Settings = (): ReactElement => {
|
||||
setFaviconFileUrl(formattedUrl);
|
||||
|
||||
setFaviconError(null);
|
||||
showToast.success('Favicon uploaded successfully');
|
||||
showToast.success("Favicon uploaded successfully");
|
||||
} catch (err: any) {
|
||||
const errorMessage =
|
||||
err?.response?.data?.error?.message ||
|
||||
err?.response?.data?.message ||
|
||||
err?.message ||
|
||||
'Failed to upload favicon. Please try again.';
|
||||
"Failed to upload favicon. Please try again.";
|
||||
showToast.error(errorMessage);
|
||||
setFaviconFile(null);
|
||||
URL.revokeObjectURL(previewUrl);
|
||||
@ -240,9 +273,11 @@ const Settings = (): ReactElement => {
|
||||
setLogoFileAttachmentUuid(null);
|
||||
setLogoError(null);
|
||||
// Reset the file input
|
||||
const fileInput = document.getElementById('logo-upload') as HTMLInputElement;
|
||||
const fileInput = document.getElementById(
|
||||
"logo-upload",
|
||||
) as HTMLInputElement;
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
fileInput.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
@ -257,9 +292,11 @@ const Settings = (): ReactElement => {
|
||||
setFaviconFileAttachmentUuid(null);
|
||||
setFaviconError(null);
|
||||
// Reset the file input
|
||||
const fileInput = document.getElementById('favicon-upload') as HTMLInputElement;
|
||||
const fileInput = document.getElementById(
|
||||
"favicon-upload",
|
||||
) as HTMLInputElement;
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
fileInput.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
@ -274,11 +311,11 @@ const Settings = (): ReactElement => {
|
||||
const isFaviconMissing = !faviconFilePath;
|
||||
|
||||
if (isLogoMissing) {
|
||||
setLogoError('Logo is required');
|
||||
setLogoError("Logo is required");
|
||||
}
|
||||
|
||||
if (isFaviconMissing) {
|
||||
setFaviconError('Favicon is required');
|
||||
setFaviconError("Favicon is required");
|
||||
}
|
||||
|
||||
if (isLogoMissing || isFaviconMissing) {
|
||||
@ -290,8 +327,10 @@ const Settings = (): ReactElement => {
|
||||
setError(null);
|
||||
|
||||
// Build update data matching EditTenantModal format
|
||||
const existingSettings = (tenant.settings as Record<string, unknown>) || {};
|
||||
const existingContact = (existingSettings.contact as Record<string, unknown>) || {};
|
||||
const existingSettings =
|
||||
(tenant.settings as Record<string, unknown>) || {};
|
||||
const existingContact =
|
||||
(existingSettings.contact as Record<string, unknown>) || {};
|
||||
|
||||
const updateData = {
|
||||
name: tenant.name,
|
||||
@ -312,7 +351,8 @@ const Settings = (): ReactElement => {
|
||||
logo_file_path: logoFilePath || undefined,
|
||||
logo_file_attachment_uuid: logoFileAttachmentUuid || undefined,
|
||||
favicon_file_path: faviconFilePath || undefined,
|
||||
favicon_file_attachment_uuid: faviconFileAttachmentUuid || undefined,
|
||||
favicon_file_attachment_uuid:
|
||||
faviconFileAttachmentUuid || undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -320,7 +360,7 @@ const Settings = (): ReactElement => {
|
||||
const response = await tenantService.update(tenantId, updateData);
|
||||
|
||||
if (response.success) {
|
||||
showToast.success('Settings updated successfully');
|
||||
showToast.success("Settings updated successfully");
|
||||
|
||||
// Update theme in Redux
|
||||
dispatch(
|
||||
@ -330,7 +370,7 @@ const Settings = (): ReactElement => {
|
||||
primary_color: primaryColor,
|
||||
secondary_color: secondaryColor,
|
||||
accent_color: accentColor,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Update local tenant state
|
||||
@ -350,7 +390,7 @@ const Settings = (): ReactElement => {
|
||||
err?.response?.data?.error?.message ||
|
||||
err?.response?.data?.message ||
|
||||
err?.message ||
|
||||
'Failed to update settings. Please try again.';
|
||||
"Failed to update settings. Please try again.";
|
||||
setError(errorMessage);
|
||||
showToast.error(errorMessage);
|
||||
} finally {
|
||||
@ -363,8 +403,8 @@ const Settings = (): ReactElement => {
|
||||
<Layout
|
||||
currentPage="Settings"
|
||||
pageHeader={{
|
||||
title: 'Settings',
|
||||
description: 'Manage your tenant settings',
|
||||
title: "Settings",
|
||||
description: "Manage your tenant settings",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-center py-12">
|
||||
@ -379,8 +419,8 @@ const Settings = (): ReactElement => {
|
||||
<Layout
|
||||
currentPage="Settings"
|
||||
pageHeader={{
|
||||
title: 'Settings',
|
||||
description: 'Manage your tenant settings',
|
||||
title: "Settings",
|
||||
description: "Manage your tenant settings",
|
||||
}}
|
||||
>
|
||||
<div className="p-4 bg-[rgba(239,68,68,0.1)] border border-[#ef4444] rounded-md">
|
||||
@ -394,8 +434,8 @@ const Settings = (): ReactElement => {
|
||||
<Layout
|
||||
currentPage="Settings"
|
||||
pageHeader={{
|
||||
title: 'Settings',
|
||||
description: 'Manage your tenant settings',
|
||||
title: "Settings",
|
||||
description: "Manage your tenant settings",
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-6">
|
||||
@ -434,8 +474,12 @@ const Settings = (): ReactElement => {
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5 flex-1 min-w-0">
|
||||
<span className="text-sm font-medium text-[#0f1724]">Upload Logo</span>
|
||||
<span className="text-xs font-normal text-[#9ca3af]">PNG, SVG, JPG up to 2MB.</span>
|
||||
<span className="text-sm font-medium text-[#0f1724]">
|
||||
Upload Logo
|
||||
</span>
|
||||
<span className="text-xs font-normal text-[#9ca3af]">
|
||||
PNG, SVG, JPG up to 2MB.
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
id="logo-upload"
|
||||
@ -453,7 +497,9 @@ const Settings = (): ReactElement => {
|
||||
<div className="flex flex-col gap-2 mt-1">
|
||||
{logoFile && (
|
||||
<div className="text-xs text-[#6b7280]">
|
||||
{isUploadingLogo ? 'Uploading...' : `Selected: ${logoFile.name}`}
|
||||
{isUploadingLogo
|
||||
? "Uploading..."
|
||||
: `Selected: ${logoFile.name}`}
|
||||
</div>
|
||||
)}
|
||||
{(logoPreviewUrl || logoFileUrl) && (
|
||||
@ -463,7 +509,7 @@ const Settings = (): ReactElement => {
|
||||
src={logoPreviewUrl || logoFileUrl}
|
||||
alt="Logo preview"
|
||||
className="max-w-full h-20 object-contain border border-[#d1d5db] rounded-md p-2 bg-white"
|
||||
style={{ display: 'block', maxHeight: '80px' }}
|
||||
style={{ display: "block", maxHeight: "80px" }}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@ -496,8 +542,12 @@ const Settings = (): ReactElement => {
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5 flex-1 min-w-0">
|
||||
<span className="text-sm font-medium text-[#0f1724]">Upload Favicon</span>
|
||||
<span className="text-xs font-normal text-[#9ca3af]">ICO or PNG up to 500KB.</span>
|
||||
<span className="text-sm font-medium text-[#0f1724]">
|
||||
Upload Favicon
|
||||
</span>
|
||||
<span className="text-xs font-normal text-[#9ca3af]">
|
||||
ICO or PNG up to 500KB.
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
id="favicon-upload"
|
||||
@ -515,7 +565,9 @@ const Settings = (): ReactElement => {
|
||||
<div className="flex flex-col gap-2 mt-1">
|
||||
{faviconFile && (
|
||||
<div className="text-xs text-[#6b7280]">
|
||||
{isUploadingFavicon ? 'Uploading...' : `Selected: ${faviconFile.name}`}
|
||||
{isUploadingFavicon
|
||||
? "Uploading..."
|
||||
: `Selected: ${faviconFile.name}`}
|
||||
</div>
|
||||
)}
|
||||
{(faviconPreviewUrl || faviconFileUrl) && (
|
||||
@ -525,7 +577,11 @@ const Settings = (): ReactElement => {
|
||||
src={faviconPreviewUrl || faviconFileUrl}
|
||||
alt="Favicon preview"
|
||||
className="w-16 h-16 object-contain border border-[#d1d5db] rounded-md p-2 bg-white"
|
||||
style={{ display: 'block', width: '64px', height: '64px' }}
|
||||
style={{
|
||||
display: "block",
|
||||
width: "64px",
|
||||
height: "64px",
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@ -544,7 +600,9 @@ const Settings = (): ReactElement => {
|
||||
|
||||
{/* Primary Color */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-[#0f1724]">Primary Color</label>
|
||||
<label className="text-sm font-medium text-[#0f1724]">
|
||||
Primary Color
|
||||
</label>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div
|
||||
className="border border-[#d1d5db] rounded-md size-10 shrink-0"
|
||||
@ -575,7 +633,9 @@ const Settings = (): ReactElement => {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
{/* Secondary Color */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-[#0f1724]">Secondary Color</label>
|
||||
<label className="text-sm font-medium text-[#0f1724]">
|
||||
Secondary Color
|
||||
</label>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div
|
||||
className="border border-[#d1d5db] rounded-md size-10 shrink-0"
|
||||
@ -604,7 +664,9 @@ const Settings = (): ReactElement => {
|
||||
|
||||
{/* Accent Color */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-[#0f1724]">Accent Color</label>
|
||||
<label className="text-sm font-medium text-[#0f1724]">
|
||||
Accent Color
|
||||
</label>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div
|
||||
className="border border-[#d1d5db] rounded-md size-10 shrink-0"
|
||||
@ -639,7 +701,7 @@ const Settings = (): ReactElement => {
|
||||
disabled={isSaving || isUploadingLogo || isUploadingFavicon}
|
||||
className="px-6 py-2.5"
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Changes'}
|
||||
{isSaving ? "Saving..." : "Save Changes"}
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user