diff --git a/client/src/pages/Settings.jsx b/client/src/pages/Settings.jsx index 89e38af..748705a 100644 --- a/client/src/pages/Settings.jsx +++ b/client/src/pages/Settings.jsx @@ -259,11 +259,15 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => { if (response.data.success) { setBranding(prev => ({ ...prev, logo_url: response.data.data.logo_url })); setLogoPreview(null); + // Clear the file input to allow selecting the same file again + event.target.value = ''; toast.success('Logo uploaded successfully'); if (onRefresh) onRefresh(); } } catch (error) { toast.error('Failed to upload logo'); + // Clear the file input on error too + event.target.value = ''; } finally { setUploading(false); } @@ -278,6 +282,33 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => { } }; + const handleLogoRemove = async () => { + if (!branding.logo_url) return; + + // Confirm removal + if (!window.confirm(t('settings.confirmRemoveLogo'))) { + return; + } + + setUploading(true); + + try { + const response = await api.delete('/tenant/logo'); + + if (response.data.success) { + setBranding(prev => ({ ...prev, logo_url: null })); + setLogoPreview(null); + toast.success(t('settings.logoRemoved')); + if (onRefresh) onRefresh(); + } + } catch (error) { + console.error('Error removing logo:', error); + toast.error(t('settings.logoRemoveFailed')); + } finally { + setUploading(false); + } + }; + return (
@@ -299,16 +330,38 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => { {/* Current logo display */} {branding.logo_url && ( -
- Current logo { - e.target.style.display = 'none'; - }} - /> -

Current logo

+
+
+
+ Current logo { + e.target.style.display = 'none'; + }} + /> +

Current logo

+
+
+ + +
+
)} @@ -316,6 +369,7 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => {
{ }} className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-medium file:bg-primary-50 file:text-primary-700 hover:file:bg-primary-100 disabled:opacity-50" /> -

PNG, JPG up to 5MB

+

PNG, JPG up to 5MB {branding.logo_url ? '• Click "' + t('settings.changeLogo') + '" to replace current logo' : ''}

{uploading && ( diff --git a/client/src/utils/tempTranslations.js b/client/src/utils/tempTranslations.js index cc30139..d24db71 100644 --- a/client/src/utils/tempTranslations.js +++ b/client/src/utils/tempTranslations.js @@ -253,7 +253,12 @@ const translations = { brandingUpdated: 'Branding updated successfully', brandingUpdateFailed: 'Failed to update branding', securityUpdated: 'Security settings updated successfully', - securityUpdateFailed: 'Failed to update security settings' + securityUpdateFailed: 'Failed to update security settings', + removeLogo: 'Remove Logo', + changeLogo: 'Change Logo', + confirmRemoveLogo: 'Are you sure you want to remove the current logo?', + logoRemoved: 'Logo removed successfully', + logoRemoveFailed: 'Failed to remove logo' }, auth: { login: 'Login', @@ -683,7 +688,12 @@ const translations = { brandingUpdated: 'Varumärke uppdaterat framgångsrikt', brandingUpdateFailed: 'Misslyckades med att uppdatera varumärke', securityUpdated: 'Säkerhetsinställningar uppdaterade framgångsrikt', - securityUpdateFailed: 'Misslyckades med att uppdatera säkerhetsinställningar' + securityUpdateFailed: 'Misslyckades med att uppdatera säkerhetsinställningar', + removeLogo: 'Ta bort logotyp', + changeLogo: 'Ändra logotyp', + confirmRemoveLogo: 'Är du säker på att du vill ta bort den aktuella logotypen?', + logoRemoved: 'Logotyp borttagen framgångsrikt', + logoRemoveFailed: 'Misslyckades med att ta bort logotyp' }, auth: { login: 'Logga in', diff --git a/server/routes/tenant.js b/server/routes/tenant.js index ee39fed..1ecb889 100644 --- a/server/routes/tenant.js +++ b/server/routes/tenant.js @@ -185,6 +185,72 @@ router.post('/logo-upload', authenticateToken, requirePermissions(['branding.edi } }); +/** + * DELETE /tenant/logo + * Remove tenant logo (branding admin or higher) + */ +router.delete('/logo', authenticateToken, requirePermissions(['branding.edit']), async (req, res) => { + try { + // Determine tenant from request + const tenantId = await multiAuth.determineTenant(req); + if (!tenantId) { + return res.status(400).json({ + success: false, + message: 'Unable to determine tenant from request' + }); + } + + // Get tenant + const tenant = await Tenant.findOne({ where: { slug: tenantId } }); + if (!tenant) { + return res.status(404).json({ + success: false, + message: 'Tenant not found' + }); + } + + // Check if tenant has a logo to remove + if (!tenant.branding?.logo_url) { + return res.status(400).json({ + success: false, + message: 'No logo to remove' + }); + } + + // Delete the physical logo file + const logoPath = tenant.branding.logo_url.replace('/uploads/logos/', ''); + const filePath = path.join(__dirname, '../uploads/logos', logoPath); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + + // Update tenant branding to remove logo + const updatedBranding = { + ...tenant.branding, + logo_url: null + }; + + await tenant.update({ branding: updatedBranding }); + + console.log(`✅ Tenant "${tenantId}" logo removed by user "${req.user.username}"`); + + res.json({ + success: true, + message: 'Logo removed successfully', + data: { + branding: updatedBranding + } + }); + + } catch (error) { + console.error('Error removing logo:', error); + res.status(500).json({ + success: false, + message: 'Failed to remove logo' + }); + } +}); + /** * PUT /tenant/branding * Update tenant branding (branding admin or higher)