Compare commits

...

2 Commits

5 changed files with 67 additions and 31 deletions

View File

@ -58,11 +58,14 @@ export async function PUT(req:Request)
// Validate logo if provided // Validate logo if provided
if (logo && typeof logo === 'string') { if (logo && typeof logo === 'string') {
// Check if it's a valid SVG const isSvg = logo.includes('<svg') && logo.includes('</svg>');
if (!logo.includes('<svg') || !logo.includes('</svg>')) { const isPng = logo.startsWith('data:image/png;base64,');
// Check if it's a valid SVG or PNG data URL
if (!isSvg && !isPng) {
return NextResponse.json({ return NextResponse.json({
success: false, success: false,
message: "invalidSVGFile", message: "invalidLogoFile",
}, { }, {
status: 400, status: 400,
headers: { headers: {
@ -71,11 +74,13 @@ export async function PUT(req:Request)
}) })
} }
// Remove any script tags for security // For SVG files, remove any script tags for security
if (isSvg) {
const cleanLogo = logo.replace(/<script[^>]*>.*?<\/script>/gi, ''); const cleanLogo = logo.replace(/<script[^>]*>.*?<\/script>/gi, '');
// Update the logo variable with cleaned content
logo = cleanLogo; logo = cleanLogo;
} }
// PNG data URLs are already safe as they're base64 encoded
}
// update the doc // update the doc
let updated_doc = await userModel.updateMany({} , { let updated_doc = await userModel.updateMany({} , {
$set: { $set: {

View File

@ -35,8 +35,8 @@ export default function GeneralSettings()
{ value: true, label: t('show') }, { value: true, label: t('show') },
{ value: false, label: t('dontShow') }, { value: false, label: t('dontShow') },
] ]
// extract svg from svg file // extract content from svg or png file
const readFile = (file : Blob) => { const readFile = (file : Blob, fileType: string) => {
// this function return a promise // this function return a promise
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// read the file // read the file
@ -50,25 +50,34 @@ export default function GeneralSettings()
reader.onerror = (error) => { reader.onerror = (error) => {
reject(error); reject(error);
}; };
// read the file as a text thene fire onload listener // read the file based on type
if (fileType === 'svg') {
reader.readAsText(file); reader.readAsText(file);
} else {
reader.readAsDataURL(file);
}
}); });
}; };
// handle the changes in the file input // handle the changes in the file input
const handleFileChange = async (event : any , setFieldValue : any) => { const handleFileChange = async (event : any , setFieldValue : any) => {
const file = event.target.files[0]; const file = event.target.files[0];
if (file) { if (file) {
// Validate file type // Validate file type - allow both SVG and PNG
if (!file.type.includes('svg') && !file.name.toLowerCase().endsWith('.svg')) { const isSvg = file.type.includes('svg') || file.name.toLowerCase().endsWith('.svg');
alert(t('onlySVGFilesAllowed') || 'Only SVG files are allowed'); const isPng = file.type.includes('png') || file.name.toLowerCase().endsWith('.png');
if (!isSvg && !isPng) {
alert(t('onlySVGAndPNGFilesAllowed') || 'Only SVG and PNG files are allowed');
event.target.value = ''; // Clear the input event.target.value = ''; // Clear the input
return; return;
} }
try { try {
const fileType = isSvg ? 'svg' : 'png';
// get the file content // get the file content
const content = await readFile(file) as string; const content = await readFile(file, fileType) as string;
if (fileType === 'svg') {
// Validate SVG content // Validate SVG content
if (!content.includes('<svg') || !content.includes('</svg>')) { if (!content.includes('<svg') || !content.includes('</svg>')) {
alert(t('invalidSVGFile') || 'Invalid SVG file format'); alert(t('invalidSVGFile') || 'Invalid SVG file format');
@ -78,9 +87,11 @@ export default function GeneralSettings()
// Additional security check - remove any script tags // Additional security check - remove any script tags
const cleanContent = content.replace(/<script[^>]*>.*?<\/script>/gi, ''); const cleanContent = content.replace(/<script[^>]*>.*?<\/script>/gi, '');
setFieldValue('logo' , cleanContent);
// set the field value with with new file content } else {
setFieldValue('logo' , cleanContent) // For PNG files, store the data URL directly
setFieldValue('logo' , content);
}
} catch (error) { } catch (error) {
console.error('Error reading file:', error); console.error('Error reading file:', error);
alert(t('errorReadingFile') || 'Error reading file'); alert(t('errorReadingFile') || 'Error reading file');
@ -343,13 +354,21 @@ export default function GeneralSettings()
dark:border-primary-dark rounded-md dark:border-primary-dark rounded-md
overflow-hidden flex items-center justify-center overflow-hidden flex items-center justify-center
`}> `}>
{values.logo && values.logo.startsWith('data:image/png') ? (
<img
src={values.logo}
alt="Logo"
className="max-w-[100px] max-h-[100px] object-contain"
/>
) : (
<div className="overflow-hidden [&_svg]:max-w-[100px] [&_svg]:max-h-[100px]" dangerouslySetInnerHTML={{ __html: values.logo }} /> <div className="overflow-hidden [&_svg]:max-w-[100px] [&_svg]:max-h-[100px]" dangerouslySetInnerHTML={{ __html: values.logo }} />
)}
</label> </label>
<input <input
onChange={((e) => { onChange={((e) => {
handleFileChange(e , setFieldValue) handleFileChange(e , setFieldValue)
})} })}
accept=".svg" accept=".svg,.png"
disabled={!edite} disabled={!edite}
className="invisible w-0 h-0 hidden" className="invisible w-0 h-0 hidden"
type="file" type="file"

View File

@ -46,7 +46,17 @@ export default function Sidebar () {
<nav className={`fixed top-0 ltr:left-0 rtl:right-0 lg:relative shadow-[5px_0_25px_0_rgba(94,92,154,0.1)] z-[99] h-screen bg-primary-dark dark:bg-primary-dark duration-300 ${sidebarState ? 'min-w-[260px] max-w-[260px] ' : 'ltr:translate-x-[-260px] rtl:translate-x-[260px] w-0'}`}> <nav className={`fixed top-0 ltr:left-0 rtl:right-0 lg:relative shadow-[5px_0_25px_0_rgba(94,92,154,0.1)] z-[99] h-screen bg-primary-dark dark:bg-primary-dark duration-300 ${sidebarState ? 'min-w-[260px] max-w-[260px] ' : 'ltr:translate-x-[-260px] rtl:translate-x-[260px] w-0'}`}>
<header className="w-full max-h-[64px] flex justify-between items-center px-[18px] py-[14px] [&_*]:text-text-light bg-secondary-dark"> <header className="w-full max-h-[64px] flex justify-between items-center px-[18px] py-[14px] [&_*]:text-text-light bg-secondary-dark">
<div className="flex justify-center items-center flex gap-[15px]"> <div className="flex justify-center items-center flex gap-[15px]">
<div className={`w-12 h-16 flex items-center justify-center relative ${appGeneralSettings.showLogo ? '' : 'hidden'}`} dangerouslySetInnerHTML={{ __html: appGeneralSettings.logo || '' }} /> <div className={`w-12 h-16 flex items-center justify-center relative ${appGeneralSettings.showLogo ? '' : 'hidden'}`}>
{appGeneralSettings.logo && appGeneralSettings.logo.startsWith('data:image/png') ? (
<img
src={appGeneralSettings.logo}
alt="Logo"
className="max-w-full max-h-full object-contain"
/>
) : (
<div dangerouslySetInnerHTML={{ __html: appGeneralSettings.logo || '' }} />
)}
</div>
<h1 className="text-[25px] font-light">{local == 'ar' ? appGeneralSettings.appName : appGeneralSettings.appNameEN}</h1> <h1 className="text-[25px] font-light">{local == 'ar' ? appGeneralSettings.appName : appGeneralSettings.appNameEN}</h1>
</div> </div>
<button onClick={() => {dispatch(toggleSidebar())}} className="rounded-full rtl:rotate-180 hover:bg-primary/5 p-[6px]"> <button onClick={() => {dispatch(toggleSidebar())}} className="rounded-full rtl:rotate-180 hover:bg-primary/5 p-[6px]">

View File

@ -365,8 +365,9 @@
"save": "الحفظ", "save": "الحفظ",
"ignore": "تجاهل", "ignore": "تجاهل",
"appNameEN": "اسم التطبيق ( en )", "appNameEN": "اسم التطبيق ( en )",
"onlySVGFilesAllowed": "يُسمح فقط بملفات SVG", "onlySVGAndPNGFilesAllowed": "يُسمح فقط بملفات SVG و PNG",
"invalidSVGFile": "تنسيق ملف SVG غير صالح", "invalidSVGFile": "تنسيق ملف SVG غير صالح",
"invalidLogoFile": "تنسيق ملف الشعار غير صالح. يُسمح فقط بملفات SVG و PNG",
"errorReadingFile": "خطأ في قراءة الملف" "errorReadingFile": "خطأ في قراءة الملف"
}, },
"statistics": { "statistics": {

View File

@ -369,8 +369,9 @@
"edite": "Edite", "edite": "Edite",
"save": "Save", "save": "Save",
"ignore": "Ignore", "ignore": "Ignore",
"onlySVGFilesAllowed": "Only SVG files are allowed", "onlySVGAndPNGFilesAllowed": "Only SVG and PNG files are allowed",
"invalidSVGFile": "Invalid SVG file format", "invalidSVGFile": "Invalid SVG file format",
"invalidLogoFile": "Invalid logo file format. Only SVG and PNG files are allowed",
"errorReadingFile": "Error reading file" "errorReadingFile": "Error reading file"
}, },
"statistics": { "statistics": {