From 9f7f47b5d1be2697c2c612bfddb6119c63a3d517 Mon Sep 17 00:00:00 2001 From: Miguel Ribeiro Date: Tue, 9 Jul 2024 18:45:23 +0200 Subject: [PATCH] feat: add maintenance tasks to admin page feat: add support to upload svg logos --- admin.php | 70 +++++ endpoints/admin/deleteunusedlogos.php | 76 +++++ includes/i18n/de.php | 2 + includes/i18n/el.php | 2 + includes/i18n/en.php | 2 + includes/i18n/es.php | 2 + includes/i18n/fr.php | 2 + includes/i18n/it.php | 2 + includes/i18n/jp.php | 2 + includes/i18n/ko.php | 2 + includes/i18n/pl.php | 2 + includes/i18n/pt.php | 2 + includes/i18n/pt_br.php | 2 + includes/i18n/ru.php | 2 + includes/i18n/sl.php | 2 + includes/i18n/sr.php | 2 + includes/i18n/sr_lat.php | 2 + includes/i18n/tr.php | 2 + includes/i18n/zh_cn.php | 2 + includes/i18n/zh_tw.php | 2 + includes/version.php | 2 +- index.php | 2 +- scripts/admin.js | 22 ++ scripts/dashboard.js | 395 +++++++++++++++----------- styles/styles.css | 10 +- 25 files changed, 435 insertions(+), 178 deletions(-) create mode 100644 endpoints/admin/deleteunusedlogos.php diff --git a/admin.php b/admin.php index 0ae37fd..01ea806 100644 --- a/admin.php +++ b/admin.php @@ -224,6 +224,76 @@ $loginDisabledAllowed = $userCount == 1 && $settings['registrations_open'] == 0; + prepare($query); + $result = $stmt->execute(); + + $logosOnDB = []; + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $logosOnDB[] = $row['logo']; + } + + // Get all logos in the payment_methods table + $query = 'SELECT icon FROM payment_methods'; + $stmt = $db->prepare($query); + $result = $stmt->execute(); + + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + if (!strstr($row['icon'], "images/uploads/icons/")) { + $logosOnDB[] = $row['icon']; + } + } + + $logosOnDB = array_unique($logosOnDB); + + // Get all logos in the uploads folder + $uploadDir = 'images/uploads/logos/'; + $uploadFiles = scandir($uploadDir); + + foreach ($uploadFiles as $file) { + if ($file != '.'&& $file != '..' && $file != 'avatars') { + $logosOnDisk[] = ['logo' => $file]; + } + } + + // Find unused logos + $unusedLogos = []; + foreach ($logosOnDisk as $disk) { + $found = false; + foreach ($logosOnDB as $dbLogo) { + if ($disk['logo'] == $dbLogo) { + $found = true; + break; + } + } + if (!$found) { + $unusedLogos[] = $disk; + } + } + + $logosToDelete = count($unusedLogos); + + ?> + +
+
+

+ +

+
+
+

+
+ /> + +
+
+
diff --git a/endpoints/admin/deleteunusedlogos.php b/endpoints/admin/deleteunusedlogos.php new file mode 100644 index 0000000..2fba6eb --- /dev/null +++ b/endpoints/admin/deleteunusedlogos.php @@ -0,0 +1,76 @@ + false, + "message" => translate('session_expired', $i18n) + ])); +} + +// Check that user is an admin +if ($userId !== 1) { + die(json_encode([ + "success" => false, + "message" => translate('error', $i18n) + ])); +} + +$query = 'SELECT logo FROM subscriptions'; +$stmt = $db->prepare($query); +$result = $stmt->execute(); + +$logosOnDB = []; +while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $logosOnDB[] = $row['logo']; +} + +$logosOnDB = array_unique($logosOnDB); + +$uploadDir = '../../images/uploads/logos/'; +$uploadFiles = scandir($uploadDir); + +foreach ($uploadFiles as $file) { + if ($file != '.' && $file != '..' && $file != 'avatars') { + $logosOnDisk[] = ['logo' => $file]; + } +} + + // Get all logos in the payment_methods table + $query = 'SELECT icon FROM payment_methods'; + $stmt = $db->prepare($query); + $result = $stmt->execute(); + + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + if (!strstr($row['icon'], "images/uploads/icons/")) { + $logosOnDB[] = $row['icon']; + } + } + + $logosOnDB = array_unique($logosOnDB); + +// Find and delete unused logos +$count = 0; +foreach ($logosOnDisk as $disk) { + foreach ($logosOnDB as $db) { + $found = false; + if ($disk['logo'] == $db) { + $found = true; + break; + } + } + if (!$found) { + unlink($uploadDir . $disk['logo']); + $count++; + } +} + +echo json_encode([ + "success" => true, + "message" => translate('success', $i18n), + 'count' => $count +]); + + +?> \ No newline at end of file diff --git a/includes/i18n/de.php b/includes/i18n/de.php index aeb1e05..3c8c8a5 100644 --- a/includes/i18n/de.php +++ b/includes/i18n/de.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Benutzer erstellen", "smtp_settings" => "SMTP Einstellungen", "smtp_usage_info" => "Wird für die Passwortwiederherstellung und andere System-E-Mails verwendet", + "maintenance_tasks" => "Wartungsaufgaben", + "orphaned_logos" => "Verwaiste Logos", // Email Verification "email_verified" => "E-Mail verifiziert", "email_verification_failed" => "E-Mail konnte nicht verifiziert werden", diff --git a/includes/i18n/el.php b/includes/i18n/el.php index 8a123ab..12e277a 100644 --- a/includes/i18n/el.php +++ b/includes/i18n/el.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Δημιουργία χρήστη", "smtp_settings" => "SMTP ρυθμίσεις", "smtp_usage_info" => "Θα χρησιμοποιηθεί για ανάκτηση κωδικού πρόσβασης και άλλα μηνύματα ηλεκτρονικού ταχυδρομείου συστήματος.", + "maintenance_tasks" => "Εργασίες συντήρησης", + "orphaned_logos" => "Ορφανά λογότυπα", // Email Verification "email_verified" => "Το email επιβεβαιώθηκε", "email_verification_failed" => "Η επαλήθευση email απέτυχε", diff --git a/includes/i18n/en.php b/includes/i18n/en.php index 8e53989..a085be2 100644 --- a/includes/i18n/en.php +++ b/includes/i18n/en.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Create User", "smtp_settings" => "SMTP Settings", "smtp_usage_info" => "Will be used for password recovery and other system emails.", + "maintenance_tasks" => "Maintenance Tasks", + "orphaned_logos" => "Orphaned Logos", // Email Verification "email_verified" => "Email verified successfully", "email_verification_failed" => "Email verification failed", diff --git a/includes/i18n/es.php b/includes/i18n/es.php index d0267c9..bbd8828 100644 --- a/includes/i18n/es.php +++ b/includes/i18n/es.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Crear Usuario", "smtp_settings" => "Configuración SMTP", "smtp_usage_info" => "Se utilizará para recuperar contraseñas y otros correos electrónicos del sistema.", + "maintenance_tasks" => "Tareas de Mantenimiento", + "orphaned_logos" => "Logotipos huérfanos", // Email Verification "email_verified" => "Correo electrónico verificado", "email_verification_failed" => "Error al verificar el correo electrónico", diff --git a/includes/i18n/fr.php b/includes/i18n/fr.php index 307e50b..8d47de3 100644 --- a/includes/i18n/fr.php +++ b/includes/i18n/fr.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Créer un utilisateur", "smtp_settings" => "Paramètres SMTP", "smtp_usage_info" => "Sera utilisé pour la récupération du mot de passe et d'autres e-mails système.", + "maintenance_tasks" => "Tâches de maintenance", + "orphaned_logos" => "Logos orphelins", // Email Verification "email_verified" => "Votre adresse courriel a été vérifiée avec succès", "email_verification_failed" => "La vérification de l'adresse courriel a échoué", diff --git a/includes/i18n/it.php b/includes/i18n/it.php index 5b76206..42d00ff 100644 --- a/includes/i18n/it.php +++ b/includes/i18n/it.php @@ -315,6 +315,8 @@ $i18n = [ "create_user" => "Crea utente", "smtp_settings" => "Impostazioni SMTP", "smtp_usage_info" => "Verrà utilizzato per il recupero della password e altre e-mail di sistema.", + "maintenance_tasks" => "Compiti di manutenzione", + "orphaned_logos" => "Loghi orfani", // Email Verification "email_verified" => "L'indirizzo email è stato verificato con successo", diff --git a/includes/i18n/jp.php b/includes/i18n/jp.php index 138adad..88441f1 100644 --- a/includes/i18n/jp.php +++ b/includes/i18n/jp.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "ユーザーを作成", "smtp_settings" => "SMTP設定", "smtp_usage_info" => "パスワードの回復やその他のシステム電子メールに使用されます。", + "maintenance_tasks" => "メンテナンスタスク", + "orphaned_logos" => "孤立したロゴ", // Email Verification "email_verified" => "メールアドレスが確認されました", "email_verification_failed" => "メールアドレスの確認に失敗しました", diff --git a/includes/i18n/ko.php b/includes/i18n/ko.php index cbed404..c58b2cb 100644 --- a/includes/i18n/ko.php +++ b/includes/i18n/ko.php @@ -298,6 +298,8 @@ $i18n = [ "create_user" => "유저 생성", "smtp_settings" => "SMTP 설정", "smtp_usage_info" => "비밀번호 복구 및 기타 시스템 이메일에 사용됩니다.", + "maintenance_tasks" => "유지보수 작업", + "orphaned_logos" => "고아 로고", // Email Verification "email_verified" => "이메일 인증 완료", "email_verification_failed" => "이메일 인증 실패", diff --git a/includes/i18n/pl.php b/includes/i18n/pl.php index d34c561..01c6aaf 100644 --- a/includes/i18n/pl.php +++ b/includes/i18n/pl.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Utwórz użytkownika", "smtp_settings" => "Ustawienia SMTP", "smtp_usage_info" => "Będzie używany do odzyskiwania hasła i innych e-maili systemowych.", + "maintenance_tasks" => "Zadania konserwacyjne", + "orphaned_logos" => "Osierocone logo", // Email Verification "email_verified" => "E-mail został zweryfikowany", "email_verification_failed" => "Weryfikacja e-maila nie powiodła się", diff --git a/includes/i18n/pt.php b/includes/i18n/pt.php index 09b1fd0..2072f3b 100644 --- a/includes/i18n/pt.php +++ b/includes/i18n/pt.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Criar Utilizador", "smtp_settings" => "Definições SMTP", "smtp_usage_info" => "Será usado para recuperações de password e outros emails do sistema.", + "maintenance_tasks" => "Tarefas de Manutenção", + "orphaned_logos" => "Logos Órfãos", // Email Verification "email_verified" => "Email verificado", "email_verification_failed" => "Verificação de email falhou", diff --git a/includes/i18n/pt_br.php b/includes/i18n/pt_br.php index e1a3f5d..bf57bf7 100644 --- a/includes/i18n/pt_br.php +++ b/includes/i18n/pt_br.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Criar usuário", "smtp_settings" => "Configurações SMTP", "smtp_usage_info" => "Será usado para recuperação de senha e outros e-mails do sistema.", + "maintenance_tasks" => "Tarefas de manutenção", + "orphaned_logos" => "Logos órfãos", // Email Verification "email_verified" => "Email verificado", "email_verification_failed" => "Falha na verificação do email", diff --git a/includes/i18n/ru.php b/includes/i18n/ru.php index d84bff6..0187f1a 100644 --- a/includes/i18n/ru.php +++ b/includes/i18n/ru.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Создать пользователя", "smtp_settings" => "Настройки SMTP", "smtp_usage_info" => "Будет использоваться для восстановления пароля и других системных писем.", + "maintenance_tasks" => "Задачи обслуживания", + "orphaned_logos" => "Одинокие логотипы", // Email Verification "email_verified" => "Ваш адрес электронной почты подтвержден. Теперь вы можете войти.", "email_verification_failed" => "Не удалось подтвердить ваш адрес электронной почты.", diff --git a/includes/i18n/sl.php b/includes/i18n/sl.php index e11e288..3bdaf27 100644 --- a/includes/i18n/sl.php +++ b/includes/i18n/sl.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Ustvari uporabnika", "smtp_settings" => "Nastavitve SMTP", "smtp_usage_info" => "Uporabljeno bo za obnovitev gesla in druge sistemske e-pošte.", + "maintenance_tasks" => "Vzdrževalne naloge", + "orphaned_logos" => "Osamljeni logotipi", // Email Verification "email_verified" => "E-pošta je bila uspešno preverjena", "email_verification_failed" => "Preverjanje e-pošte ni uspelo", diff --git a/includes/i18n/sr.php b/includes/i18n/sr.php index 0c0a062..34ee487 100644 --- a/includes/i18n/sr.php +++ b/includes/i18n/sr.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Креирај корисника", "smtp_settings" => "SMTP подешавања", "smtp_usage_info" => "SMTP се користи за слање е-поште за обавештења.", + "maintenance_tasks" => "Одржавање", + "orphaned_logos" => "Породични логотипови", // Email Verification "email_verified" => "Е-пошта је верификована", "email_verification_failed" => "Верификација е-поште није успела", diff --git a/includes/i18n/sr_lat.php b/includes/i18n/sr_lat.php index 78b2ab0..601ffed 100644 --- a/includes/i18n/sr_lat.php +++ b/includes/i18n/sr_lat.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Kreiraj korisnika", "smtp_settings" => "SMTP podešavanja", "smtp_usage_info" => "Koristiće se za oporavak lozinke i druge sistemske e-poruke.", + "maintenance_tasks" => "Održavanje", + "orphaned_logos" => "Nepovezani logotipi", // Email Verification "email_verified" => "E-pošta je uspešno verifikovana", "email_verification_failed" => "Verifikacija e-pošte nije uspela", diff --git a/includes/i18n/tr.php b/includes/i18n/tr.php index 433ec43..ddb1f48 100644 --- a/includes/i18n/tr.php +++ b/includes/i18n/tr.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "Kullanıcı Oluştur", "smtp_settings" => "SMTP Ayarları", "smtp_usage_info" => "Şifre kurtarma ve diğer sistem e-postaları için kullanılacaktır.", + "maintenance_tasks" => "Bakım Görevleri", + "orphaned_logos" => "Yetim Logolar", // Email Verification "email_verified" => "E-posta doğrulandı", "email_verification_failed" => "E-posta doğrulaması başarısız oldu", diff --git a/includes/i18n/zh_cn.php b/includes/i18n/zh_cn.php index 86c4a0d..5c398f0 100644 --- a/includes/i18n/zh_cn.php +++ b/includes/i18n/zh_cn.php @@ -315,6 +315,8 @@ $i18n = [ "create_user" => "创建用户", "smtp_settings" => "SMTP 设置", "smtp_usage_info" => "将用于密码恢复和其他系统电子邮件。", + "maintenance_tasks" => "维护任务", + "orphaned_logos" => "孤立的 Logo", // Email Verification "email_verified" => "电子邮件已验证", diff --git a/includes/i18n/zh_tw.php b/includes/i18n/zh_tw.php index 7a7a63c..a40829d 100644 --- a/includes/i18n/zh_tw.php +++ b/includes/i18n/zh_tw.php @@ -297,6 +297,8 @@ $i18n = [ "create_user" => "建立使用者", "smtp_settings" => "SMTP 設定", "smtp_usage_info" => "將用於密碼恢復和其他系統電子郵件。", + "maintenance_tasks" => "維護任務", + "orphaned_logos" => "孤立的圖示", // Email Verification "email_verified" => "電子郵件已驗證", "email_verification_failed" => "電子郵件驗證失敗", diff --git a/includes/version.php b/includes/version.php index f54ba72..7d2339a 100644 --- a/includes/version.php +++ b/includes/version.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/index.php b/index.php index 87168b4..f9d5aec 100644 --- a/index.php +++ b/index.php @@ -237,7 +237,7 @@ $headerClass = count($subscriptions) > 0 ? "main-actions" : "main-actions hidden -
response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + const numberOfLogos = document.querySelector('.number-of-logos'); + numberOfLogos.innerText = '0'; + } else { + showErrorMessage(data.message); + button.disabled = false; + } + }) + .catch(error => { + showErrorMessage(error); + button.disabled = false; + }); } \ No newline at end of file diff --git a/scripts/dashboard.js b/scripts/dashboard.js index c7b381e..d5a98b0 100644 --- a/scripts/dashboard.js +++ b/scripts/dashboard.js @@ -20,27 +20,27 @@ function toggleNotificationDays() { } function resetForm() { - const id = document.querySelector("#id"); - id.value = ""; - const formTitle = document.querySelector("#form-title"); - formTitle.textContent = translate('add_subscription'); - const logo = document.querySelector("#form-logo"); - logo.src = ""; - logo.style = 'display: none'; - const logoUrl = document.querySelector("#logo-url"); - logoUrl.value = ""; - const logoSearchButton = document.querySelector("#logo-search-button"); - logoSearchButton.classList.add("disabled"); - const submitButton = document.querySelector("#save-button"); - submitButton.disabled = false; - const notifyDaysBefore = document.querySelector("#notify_days_before"); - notifyDaysBefore.disabled = true; - const form = document.querySelector("#subs-form"); - form.reset(); - closeLogoSearch(); - const deleteButton = document.querySelector("#deletesub"); - deleteButton.style = 'display: none'; - deleteButton.removeAttribute("onClick"); + const id = document.querySelector("#id"); + id.value = ""; + const formTitle = document.querySelector("#form-title"); + formTitle.textContent = translate('add_subscription'); + const logo = document.querySelector("#form-logo"); + logo.src = ""; + logo.style = 'display: none'; + const logoUrl = document.querySelector("#logo-url"); + logoUrl.value = ""; + const logoSearchButton = document.querySelector("#logo-search-button"); + logoSearchButton.classList.add("disabled"); + const submitButton = document.querySelector("#save-button"); + submitButton.disabled = false; + const notifyDaysBefore = document.querySelector("#notify_days_before"); + notifyDaysBefore.disabled = true; + const form = document.querySelector("#subs-form"); + form.reset(); + closeLogoSearch(); + const deleteButton = document.querySelector("#deletesub"); + deleteButton.style = 'display: none'; + deleteButton.removeAttribute("onClick"); } function fillEditFormFields(subscription) { @@ -106,12 +106,12 @@ function fillEditFormFields(subscription) { } function openEditSubscription(event, id) { - event.stopPropagation(); - scrollTopBeforeOpening = window.scrollY; - const body = document.querySelector('body'); - body.classList.add('no-scroll'); - const url = `endpoints/subscription/get.php?id=${id}`; - fetch(url) + event.stopPropagation(); + scrollTopBeforeOpening = window.scrollY; + const body = document.querySelector('body'); + body.classList.add('no-scroll'); + const url = `endpoints/subscription/get.php?id=${id}`; + fetch(url) .then((response) => { if (response.ok) { return response.json(); @@ -133,41 +133,41 @@ function openEditSubscription(event, id) { } function addSubscription() { - resetForm(); - const modal = document.getElementById('subscription-form'); - modal.classList.add("is-open"); - const body = document.querySelector('body'); - body.classList.add('no-scroll'); + resetForm(); + const modal = document.getElementById('subscription-form'); + modal.classList.add("is-open"); + const body = document.querySelector('body'); + body.classList.add('no-scroll'); } function closeAddSubscription() { - const modal = document.getElementById('subscription-form'); - modal.classList.remove("is-open"); - const body = document.querySelector('body'); - body.classList.remove('no-scroll'); - if (shouldScroll) { - window.scrollTo(0, scrollTopBeforeOpening); - } - resetForm(); + const modal = document.getElementById('subscription-form'); + modal.classList.remove("is-open"); + const body = document.querySelector('body'); + body.classList.remove('no-scroll'); + if (shouldScroll) { + window.scrollTo(0, scrollTopBeforeOpening); + } + resetForm(); } function handleFileSelect(event) { - const fileInput = event.target; - const logoPreview = document.querySelector('.logo-preview'); - const logoImg = logoPreview.querySelector('img'); - const logoUrl = document.querySelector("#logo-url"); - logoUrl.value = ""; + const fileInput = event.target; + const logoPreview = document.querySelector('.logo-preview'); + const logoImg = logoPreview.querySelector('img'); + const logoUrl = document.querySelector("#logo-url"); + logoUrl.value = ""; - if (fileInput.files && fileInput.files[0]) { - const reader = new FileReader(); + if (fileInput.files && fileInput.files[0]) { + const reader = new FileReader(); - reader.onload = function (e) { - logoImg.src = e.target.result; - logoImg.style.display = 'block'; - }; + reader.onload = function (e) { + logoImg.src = e.target.result; + logoImg.style.display = 'block'; + }; - reader.readAsDataURL(fileInput.files[0]); - } + reader.readAsDataURL(fileInput.files[0]); + } } function deleteSubscription(event, id) { @@ -177,7 +177,7 @@ function deleteSubscription(event, id) { fetch(`endpoints/subscription/delete.php?id=${id}`, { method: 'DELETE', }) - .then(response => { + .then(response => { if (response.ok) { showSuccessMessage(translate('subscription_deleted')); fetchSubscriptions(); @@ -185,10 +185,10 @@ function deleteSubscription(event, id) { } else { showErrorMessage(translate('error_deleting_subscription')); } - }) - .catch(error => { + }) + .catch(error => { console.error('Error:', error); - }); + }); } } @@ -216,7 +216,7 @@ function cloneSubscription(event, id) { .catch(error => { showErrorMessage(error.message || translate('error')); }); - } +} function setSearchButtonStatus() { @@ -239,17 +239,17 @@ function searchLogo() { logoSearchPopup.classList.add("is-open"); const imageSearchUrl = `endpoints/logos/search.php?search=${searchTerm}`; fetch(imageSearchUrl) - .then(response => response.json()) - .then(data => { - if (data.imageUrls) { - displayImageResults(data.imageUrls); - } else if (data.error) { - console.error(data.error); - } - }) - .catch(error => { - console.error(translate('error_fetching_image_results'), error); - }); + .then(response => response.json()) + .then(data => { + if (data.imageUrls) { + displayImageResults(data.imageUrls); + } else if (data.error) { + console.error(data.error); + } + }) + .catch(error => { + console.error(translate('error_fetching_image_results'), error); + }); } else { nameInput.focus(); } @@ -260,15 +260,15 @@ function displayImageResults(imageSources) { logoResults.innerHTML = ""; imageSources.forEach(src => { - const img = document.createElement("img"); - img.src = src; - img.onclick = function() { - selectWebLogo(src); - }; - img.onerror = function() { - this.parentNode.removeChild(this); - }; - logoResults.appendChild(img); + const img = document.createElement("img"); + img.src = src; + img.onclick = function () { + selectWebLogo(src); + }; + img.onerror = function () { + this.parentNode.removeChild(this); + }; + logoResults.appendChild(img); }); } @@ -323,7 +323,7 @@ function fetchSubscriptions() { function setSortOption(sortOption) { const sortOptionsContainer = document.querySelector("#sort-options"); const sortOptionsList = sortOptionsContainer.querySelectorAll("li"); - sortOptionsList.forEach((option) => { + sortOptionsList.forEach((option) => { if (option.getAttribute("id") === "sort-" + sortOption) { option.classList.add("selected"); } else { @@ -339,21 +339,46 @@ function setSortOption(sortOption) { toggleSortOptions(); } -document.addEventListener('DOMContentLoaded', function() { - const subscriptionForm = document.querySelector("#subs-form"); - const submitButton = document.querySelector("#save-button"); - const endpoint = "endpoints/subscription/add.php"; +function convertSvgToPng(file, callback) { + const reader = new FileReader(); - subscriptionForm.addEventListener("submit", function (e) { - e.preventDefault(); - - submitButton.disabled = true; - const formData = new FormData(subscriptionForm); + reader.onload = function (e) { + const img = new Image(); + img.src = e.target.result; + img.onload = function() { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + const pngDataUrl = canvas.toDataURL('image/png'); + const pngFile = dataURLtoFile(pngDataUrl, file.name.replace(".svg", ".png")); + callback(pngFile); + }; + }; - fetch(endpoint, { - method: "POST", - body: formData, - }) + reader.readAsDataURL(file); +} + +function dataURLtoFile(dataurl, filename) { + let arr = dataurl.split(','), + mime = arr[0].match(/:(.*?);/)[1], + bstr = atob(arr[1]), + n = bstr.length, + u8arr = new Uint8Array(n); + + while(n--){ + u8arr[n] = bstr.charCodeAt(n); + } + + return new File([u8arr], filename, {type:mime}); +} + +function submitFormData(formData, submitButton, endpoint) { + fetch(endpoint, { + method: "POST", + body: formData, + }) .then((response) => response.json()) .then((data) => { if (data.status === "Success") { @@ -363,45 +388,69 @@ document.addEventListener('DOMContentLoaded', function() { } }) .catch((error) => { - showErrorMessage(error); - submitButton.disabled = false; - }); + showErrorMessage(error); + submitButton.disabled = false; }); +} - document.addEventListener('mousedown', function(event) { - const sortOptions = document.querySelector('#sort-options'); - const sortButton = document.querySelector("#sort-button"); +document.addEventListener('DOMContentLoaded', function () { + const subscriptionForm = document.querySelector("#subs-form"); + const submitButton = document.querySelector("#save-button"); + const endpoint = "endpoints/subscription/add.php"; - if (!sortOptions.contains(event.target) && !sortButton.contains(event.target) && isSortOptionsOpen) { - sortOptions.classList.remove('is-open'); - isSortOptionsOpen = false; - } - }); + subscriptionForm.addEventListener("submit", function (e) { + e.preventDefault(); + + submitButton.disabled = true; + const formData = new FormData(subscriptionForm); - document.querySelector('#sort-options').addEventListener('focus', function() { - isSortOptionsOpen = true; - }); + const fileInput = document.querySelector("#logo"); + const file = fileInput.files[0]; + + if (file && file.type === "image/svg+xml") { + convertSvgToPng(file, function(pngFile) { + formData.set("logo", pngFile); + submitFormData(formData, submitButton, endpoint); + }); + } else { + submitFormData(formData, submitButton, endpoint); + } + }); + + document.addEventListener('mousedown', function (event) { + const sortOptions = document.querySelector('#sort-options'); + const sortButton = document.querySelector("#sort-button"); + + if (!sortOptions.contains(event.target) && !sortButton.contains(event.target) && isSortOptionsOpen) { + sortOptions.classList.remove('is-open'); + isSortOptionsOpen = false; + } + }); + + document.querySelector('#sort-options').addEventListener('focus', function () { + isSortOptionsOpen = true; + }); }); function searchSubscriptions() { - const searchInput = document.querySelector("#search"); - const searchTerm = searchInput.value.trim().toLowerCase(); + const searchInput = document.querySelector("#search"); + const searchTerm = searchInput.value.trim().toLowerCase(); - const subscriptions = document.querySelectorAll(".subscription"); - subscriptions.forEach(subscription => { - const name = subscription.getAttribute('data-name').toLowerCase(); - if (!name.includes(searchTerm)) { - subscription.classList.add("hide"); - } else { - subscription.classList.remove("hide"); - } - }); + const subscriptions = document.querySelectorAll(".subscription"); + subscriptions.forEach(subscription => { + const name = subscription.getAttribute('data-name').toLowerCase(); + if (!name.includes(searchTerm)) { + subscription.classList.add("hide"); + } else { + subscription.classList.remove("hide"); + } + }); } function closeSubMenus() { var subMenus = document.querySelectorAll('.filtermenu-submenu-content'); subMenus.forEach(subMenu => { - subMenu.classList.remove('is-open'); + subMenu.classList.remove('is-open'); }); } @@ -411,84 +460,84 @@ activeFilters['category'] = ""; activeFilters['member'] = ""; activeFilters['payment'] = ""; -document.addEventListener("DOMContentLoaded", function() { +document.addEventListener("DOMContentLoaded", function () { var filtermenu = document.querySelector('#filtermenu-button'); - filtermenu.addEventListener('click', function() { - this.parentElement.querySelector('.filtermenu-content').classList.toggle('is-open'); - closeSubMenus(); + filtermenu.addEventListener('click', function () { + this.parentElement.querySelector('.filtermenu-content').classList.toggle('is-open'); + closeSubMenus(); }); - document.addEventListener('click', function(e) { - var filtermenuContent = document.querySelector('.filtermenu-content'); - if (filtermenuContent.classList.contains('is-open')) { - var subMenus = document.querySelectorAll('.filtermenu-submenu'); - var clickedInsideSubmenu = Array.from(subMenus).some(subMenu => subMenu.contains(e.target) || subMenu === e.target); + document.addEventListener('click', function (e) { + var filtermenuContent = document.querySelector('.filtermenu-content'); + if (filtermenuContent.classList.contains('is-open')) { + var subMenus = document.querySelectorAll('.filtermenu-submenu'); + var clickedInsideSubmenu = Array.from(subMenus).some(subMenu => subMenu.contains(e.target) || subMenu === e.target); - if (!filtermenu.contains(e.target) && !clickedInsideSubmenu) { - closeSubMenus(); - filtermenuContent.classList.remove('is-open'); - } + if (!filtermenu.contains(e.target) && !clickedInsideSubmenu) { + closeSubMenus(); + filtermenuContent.classList.remove('is-open'); } + } }); }); function toggleSubMenu(subMenu) { var subMenu = document.getElementById("filter-" + subMenu); if (subMenu.classList.contains("is-open")) { - closeSubMenus(); + closeSubMenus(); } else { - closeSubMenus(); - subMenu.classList.add("is-open"); + closeSubMenus(); + subMenu.classList.add("is-open"); } } -document.querySelectorAll('.filter-item').forEach(function(item) { - item.addEventListener('click', function(e) { +document.querySelectorAll('.filter-item').forEach(function (item) { + item.addEventListener('click', function (e) { const searchInput = document.querySelector("#search"); searchInput.value = ""; if (this.hasAttribute('data-categoryid')) { - const categoryId = this.getAttribute('data-categoryid'); - if (activeFilters['category'] === categoryId) { - activeFilters['category'] = ""; - this.classList.remove('selected'); - } else { - activeFilters['category'] = categoryId; - Array.from(this.parentNode.children).forEach(sibling => { - sibling.classList.remove('selected'); - }); - this.classList.add('selected'); - } + const categoryId = this.getAttribute('data-categoryid'); + if (activeFilters['category'] === categoryId) { + activeFilters['category'] = ""; + this.classList.remove('selected'); + } else { + activeFilters['category'] = categoryId; + Array.from(this.parentNode.children).forEach(sibling => { + sibling.classList.remove('selected'); + }); + this.classList.add('selected'); + } } else if (this.hasAttribute('data-memberid')) { - const memberId = this.getAttribute('data-memberid'); - if (activeFilters['member'] === memberId) { - activeFilters['member'] = ""; - this.classList.remove('selected'); - } else { - activeFilters['member'] = memberId; - Array.from(this.parentNode.children).forEach(sibling => { - sibling.classList.remove('selected'); - }); - this.classList.add('selected'); - } + const memberId = this.getAttribute('data-memberid'); + if (activeFilters['member'] === memberId) { + activeFilters['member'] = ""; + this.classList.remove('selected'); + } else { + activeFilters['member'] = memberId; + Array.from(this.parentNode.children).forEach(sibling => { + sibling.classList.remove('selected'); + }); + this.classList.add('selected'); + } } else if (this.hasAttribute('data-paymentid')) { - const paymentId = this.getAttribute('data-paymentid'); - if (activeFilters['payment'] === paymentId) { - activeFilters['payment'] = ""; - this.classList.remove('selected'); - } else { - activeFilters['payment'] = paymentId; - Array.from(this.parentNode.children).forEach(sibling => { - sibling.classList.remove('selected'); - }); - this.classList.add('selected'); - } + const paymentId = this.getAttribute('data-paymentid'); + if (activeFilters['payment'] === paymentId) { + activeFilters['payment'] = ""; + this.classList.remove('selected'); + } else { + activeFilters['payment'] = paymentId; + Array.from(this.parentNode.children).forEach(sibling => { + sibling.classList.remove('selected'); + }); + this.classList.add('selected'); + } } if (activeFilters['category'] !== "" || activeFilters['member'] !== "" || activeFilters['payment'] !== "") { - document.querySelector('#clear-filters').classList.remove('hide'); + document.querySelector('#clear-filters').classList.remove('hide'); } else { - document.querySelector('#clear-filters').classList.add('hide'); + document.querySelector('#clear-filters').classList.add('hide'); } fetchSubscriptions(); @@ -501,7 +550,7 @@ function clearFilters() { activeFilters['category'] = ""; activeFilters['member'] = ""; activeFilters['payment'] = ""; - document.querySelectorAll('.filter-item').forEach(function(item) { + document.querySelectorAll('.filter-item').forEach(function (item) { item.classList.remove('selected'); }); document.querySelector('#clear-filters').classList.add('hide'); @@ -510,7 +559,7 @@ function clearFilters() { let currentActions = null; -document.addEventListener('click', function(event) { +document.addEventListener('click', function (event) { // Check if click was outside currentActions if (currentActions && !currentActions.contains(event.target)) { // Click was outside currentActions, close currentActions diff --git a/styles/styles.css b/styles/styles.css index ef6c14f..98fd25d 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -117,7 +117,7 @@ header .logo .logo-image svg { .dropbtn > img { width: 35px; height: 35px; - object-fit: contain; + object-fit: cover; } .dropdown-content { @@ -630,7 +630,7 @@ header #avatar { cursor: pointer; width: 80px; height: 80px; - object-fit: contain; + object-fit: cover; max-width: 80px; border-radius: 50%; border: 1px solid #ccc; @@ -701,7 +701,7 @@ header #avatar { .avatar-select .avatar-list .avatar-container > img { width: 60px; height: 60px; - object-fit: contain; + object-fit: cover; cursor: pointer; border: 1px solid #ccc; box-sizing: border-box @@ -2249,6 +2249,10 @@ button.dark-theme-button i { } } +.bold { + font-weight: 700; +} + /* Checkbox */ input[type="checkbox"] { opacity: 0;