Fix jwt-token
This commit is contained in:
@@ -259,11 +259,15 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
setBranding(prev => ({ ...prev, logo_url: response.data.data.logo_url }));
|
setBranding(prev => ({ ...prev, logo_url: response.data.data.logo_url }));
|
||||||
setLogoPreview(null);
|
setLogoPreview(null);
|
||||||
|
// Clear the file input to allow selecting the same file again
|
||||||
|
event.target.value = '';
|
||||||
toast.success('Logo uploaded successfully');
|
toast.success('Logo uploaded successfully');
|
||||||
if (onRefresh) onRefresh();
|
if (onRefresh) onRefresh();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to upload logo');
|
toast.error('Failed to upload logo');
|
||||||
|
// Clear the file input on error too
|
||||||
|
event.target.value = '';
|
||||||
} finally {
|
} finally {
|
||||||
setUploading(false);
|
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 (
|
return (
|
||||||
<div className="bg-white shadow rounded-lg">
|
<div className="bg-white shadow rounded-lg">
|
||||||
<div className="px-4 py-5 sm:p-6">
|
<div className="px-4 py-5 sm:p-6">
|
||||||
@@ -299,16 +330,38 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
|
|
||||||
{/* Current logo display */}
|
{/* Current logo display */}
|
||||||
{branding.logo_url && (
|
{branding.logo_url && (
|
||||||
<div className="mb-4">
|
<div className="mb-4 p-4 border border-gray-200 rounded-lg bg-gray-50">
|
||||||
<img
|
<div className="flex items-start justify-between">
|
||||||
src={branding.logo_url.startsWith('http') ? branding.logo_url : `${api.defaults.baseURL.replace('/api', '')}${branding.logo_url}`}
|
<div className="flex-1">
|
||||||
alt="Current logo"
|
<img
|
||||||
className="h-16 w-auto object-contain border border-gray-200 rounded p-2"
|
src={branding.logo_url.startsWith('http') ? branding.logo_url : `${api.defaults.baseURL.replace('/api', '')}${branding.logo_url}`}
|
||||||
onError={(e) => {
|
alt="Current logo"
|
||||||
e.target.style.display = 'none';
|
className="h-16 w-auto object-contain border border-gray-200 rounded p-2 bg-white"
|
||||||
}}
|
onError={(e) => {
|
||||||
/>
|
e.target.style.display = 'none';
|
||||||
<p className="text-xs text-gray-500 mt-1">Current logo</p>
|
}}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-gray-500 mt-1">Current logo</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2 ml-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => document.getElementById('logo-file-input').click()}
|
||||||
|
disabled={!canEdit || uploading}
|
||||||
|
className="px-3 py-1.5 text-xs font-medium text-primary-700 bg-primary-100 rounded hover:bg-primary-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{t('settings.changeLogo')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleLogoRemove}
|
||||||
|
disabled={!canEdit || uploading}
|
||||||
|
className="px-3 py-1.5 text-xs font-medium text-red-700 bg-red-100 rounded hover:bg-red-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{t('settings.removeLogo')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -316,6 +369,7 @@ const BrandingSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<input
|
<input
|
||||||
|
id="logo-file-input"
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
disabled={!canEdit || uploading}
|
disabled={!canEdit || uploading}
|
||||||
@@ -325,7 +379,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"
|
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"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-gray-500 mt-1">PNG, JPG up to 5MB</p>
|
<p className="text-xs text-gray-500 mt-1">PNG, JPG up to 5MB {branding.logo_url ? '• Click "' + t('settings.changeLogo') + '" to replace current logo' : ''}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{uploading && (
|
{uploading && (
|
||||||
|
|||||||
@@ -253,7 +253,12 @@ const translations = {
|
|||||||
brandingUpdated: 'Branding updated successfully',
|
brandingUpdated: 'Branding updated successfully',
|
||||||
brandingUpdateFailed: 'Failed to update branding',
|
brandingUpdateFailed: 'Failed to update branding',
|
||||||
securityUpdated: 'Security settings updated successfully',
|
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: {
|
auth: {
|
||||||
login: 'Login',
|
login: 'Login',
|
||||||
@@ -683,7 +688,12 @@ const translations = {
|
|||||||
brandingUpdated: 'Varumärke uppdaterat framgångsrikt',
|
brandingUpdated: 'Varumärke uppdaterat framgångsrikt',
|
||||||
brandingUpdateFailed: 'Misslyckades med att uppdatera varumärke',
|
brandingUpdateFailed: 'Misslyckades med att uppdatera varumärke',
|
||||||
securityUpdated: 'Säkerhetsinställningar uppdaterade framgångsrikt',
|
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: {
|
auth: {
|
||||||
login: 'Logga in',
|
login: 'Logga in',
|
||||||
|
|||||||
@@ -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
|
* PUT /tenant/branding
|
||||||
* Update tenant branding (branding admin or higher)
|
* Update tenant branding (branding admin or higher)
|
||||||
|
|||||||
Reference in New Issue
Block a user