refactor: Standardize string literals to use double quotes and apply minor formatting across tenant management and settings components.

This commit is contained in:
Yashwin 2026-03-13 18:50:48 +05:30
parent f460a89201
commit 1025504a55
3 changed files with 1968 additions and 1358 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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