diff --git a/locale/en.json b/locale/en.json index 190a363d..2e0b90bd 100644 --- a/locale/en.json +++ b/locale/en.json @@ -1283,6 +1283,11 @@ "ACCOUNT_CONFIRM_BACKUP": "I confirm that I have verified that my data has been downloaded to my computer.", "BACKUP_READY": "Your backup is now ready for download.", "ACCOUNT_BACKUP": "Save my account", + "BACKUP_ALL_MY_DATA": "Backup all my data", + "BACKUP_MESSAGE": "Enter your Drumee password. You will receive on your email a link to download all your data.", + "ENTER_PASSWORD": "Enter your password", + "PASSWORD_REQUIRED": "Password is required", + "BACKUP_SUCCESS": "Your backup request has been submitted. You will receive an email with a download link shortly.", "YOUR_DATA": "Your data", "CONTINUE": "Continue", "ACCOUNT_DELETION_GOODBYE": "Thanks for using Drumee. Your account has been deleted.", diff --git a/locale/es.json b/locale/es.json index 1deeb634..99b76c0a 100644 --- a/locale/es.json +++ b/locale/es.json @@ -51,6 +51,11 @@ "ACCESS_LIST": "Lista de acceso", "ACCOUNT_ARCHIVED": "Cuenta archivada", "ACCOUNT_BACKUP": "Guardar mi cuenta", + "BACKUP_ALL_MY_DATA": "Hacer copia de seguridad de todos mis datos", + "BACKUP_MESSAGE": "Ingrese su contraseña de Drumee. Recibirá por correo electrónico un enlace para descargar todos sus datos.", + "ENTER_PASSWORD": "Ingrese su contraseña", + "PASSWORD_REQUIRED": "Se requiere contraseña", + "BACKUP_SUCCESS": "Su solicitud de copia de seguridad ha sido enviada. Recibirá un correo electrónico con un enlace de descarga en breve.", "ACCOUNT_BLOCKED": "Cuenta bloqueada", "ACCOUNT_CHANGE_TIP": "Cambiar la cuenta desconectará automáticamente la cuenta actual.", "ACCOUNT_CONFIRM_BACKUP": "Confirmo que he verificado que mis datos han sido descargados en mi computadora.", diff --git a/locale/fr.json b/locale/fr.json index 378133bb..d7c53026 100644 --- a/locale/fr.json +++ b/locale/fr.json @@ -1283,6 +1283,11 @@ "ACCOUNT_CONFIRM_BACKUP": "Je confirme avoir vérifié que mes données ont bien été téléchargées sur mon ordinateur.", "BACKUP_READY": "Votre sauvegarde est maintenant prête pour le téléchargement. ", "ACCOUNT_BACKUP": "Sauvegarder mon compte", + "BACKUP_ALL_MY_DATA": "Sauvegarder toutes mes données", + "BACKUP_MESSAGE": "Entrez votre mot de passe Drumee. Vous recevrez par e-mail un lien pour télécharger toutes vos données.", + "ENTER_PASSWORD": "Entrez votre mot de passe", + "PASSWORD_REQUIRED": "Le mot de passe est requis", + "BACKUP_SUCCESS": "Votre demande de sauvegarde a été soumise. Vous recevrez un e-mail avec un lien de téléchargement sous peu.", "YOUR_DATA": "Vos données", "CONTINUE": "Continuer", "ACCOUNT_DELETION_GOODBYE": "Merci d'avoir utilisé Drumee. Votre compte a été supprimé. ", diff --git a/locale/km.json b/locale/km.json index b7b26013..b3138ef0 100644 --- a/locale/km.json +++ b/locale/km.json @@ -1278,6 +1278,11 @@ "ACCOUNT_CONFIRM_BACKUP": "ខ្ញុំបញ្ជាក់ថាខ្ញុំបានផ្ទៀងផ្ទាត់ថាទិន្នន័យរបស់ខ្ញុំត្រូវបានទាញយកទៅកុំព្យូទ័ររបស់ខ្ញុំ។", "BACKUP_READY": "ការបម្រុងទុករបស់អ្នកឥឡូវនេះរួចរាល់សម្រាប់ការទាញយក។", "ACCOUNT_BACKUP": "រក្សាទុកគណនីរបស់ខ្ញុំ", + "BACKUP_ALL_MY_DATA": "បម្រុងទុកទិន្នន័យទាំងអស់របស់ខ្ញុំ", + "BACKUP_MESSAGE": "បញ្ចូលពាក្យសម្ងាត់ Drumee របស់អ្នក។ អ្នកនឹងទទួលបានតំណទាញយកទិន្នន័យទាំងអស់របស់អ្នកតាមអ៊ីម៉ែល។", + "ENTER_PASSWORD": "បញ្ចូលពាក្យសម្ងាត់របស់អ្នក", + "PASSWORD_REQUIRED": "ត្រូវការពាក្យសម្ងាត់", + "BACKUP_SUCCESS": "សំណើបម្រុងទុករបស់អ្នកត្រូវបានដាក់ជូន។ អ្នកនឹងទទួលបានអ៊ីម៉ែលជាមួយតំណទាញយកក្នុងពេលឆាប់ៗនេះ។", "YOUR_DATA": "ទិន្នន័យរបស់អ្នក។", "CONTINUE": "បន្ត", "ACCOUNT_DELETION_GOODBYE": "សូមអរគុណចំពោះការប្រើប្រាស់ Drumee ។ ចំនួនរបស់អ្នកត្រូវបានលុប។", diff --git a/locale/ru.json b/locale/ru.json index 272540a2..78141ab3 100644 --- a/locale/ru.json +++ b/locale/ru.json @@ -1282,6 +1282,11 @@ "ACCOUNT_CONFIRM_BACKUP": "Я подтверждаю, что подтвердил, что мои данные были загружены на мой компьютер.", "BACKUP_READY": "Теперь ваша резервная копия готова к загрузке.", "ACCOUNT_BACKUP": "Сохранить мой аккаунт", + "BACKUP_ALL_MY_DATA": "Резервное копирование всех моих данных", + "BACKUP_MESSAGE": "Введите пароль Drumee. Вы получите по электронной почте ссылку для загрузки всех ваших данных.", + "ENTER_PASSWORD": "Введите ваш пароль", + "PASSWORD_REQUIRED": "Требуется пароль", + "BACKUP_SUCCESS": "Ваш запрос на резервное копирование был отправлен. Вы получите электронное письмо со ссылкой для загрузки в ближайшее время.", "YOUR_DATA": "Ваши данные", "CONTINUE": "Продолжать", "ACCOUNT_DELETION_GOODBYE": "Спасибо за использование Drumee. Ваш счет был удален.", diff --git a/locale/zh.json b/locale/zh.json index e7c24d9c..05c6e54a 100644 --- a/locale/zh.json +++ b/locale/zh.json @@ -1283,6 +1283,11 @@ "ACCOUNT_CONFIRM_BACKUP": "我确认已验证我的数据已下载到计算机上。", "BACKUP_READY": "您的备份现在可以下载了。", "ACCOUNT_BACKUP": "保存我的账户", + "BACKUP_ALL_MY_DATA": "备份我的所有数据", + "BACKUP_MESSAGE": "输入您的Drumee密码。您将通过电子邮件收到下载所有数据的链接。", + "ENTER_PASSWORD": "输入您的密码", + "PASSWORD_REQUIRED": "需要密码", + "BACKUP_SUCCESS": "您的备份请求已提交。您将很快收到带有下载链接的电子邮件。", "YOUR_DATA": "您的资料", "CONTINUE": "继续", "ACCOUNT_DELETION_GOODBYE": "感谢您使用Drumee。 您的计数已被删除。", diff --git a/src/drumee/builtins/widget/settings/account/index.js b/src/drumee/builtins/widget/settings/account/index.js index 3aec941b..9bef2305 100644 --- a/src/drumee/builtins/widget/settings/account/index.js +++ b/src/drumee/builtins/widget/settings/account/index.js @@ -25,6 +25,7 @@ class settings_account extends LetcBox { require("./skeleton/storage").default, require("./skeleton/security").default, require("./skeleton/date").default, + require("./skeleton/data").default, ]; this.tab_name = [ LOCALE.PROFILE, @@ -32,6 +33,7 @@ class settings_account extends LetcBox { LOCALE.STORAGE, LOCALE.SECURITY, LOCALE.DATE_AND_TIME, + LOCALE.MY_DATA, ]; if (this.canAdmin()) { this.tab_name.push("My seats"); @@ -230,6 +232,77 @@ class settings_account extends LetcBox { }); } + /** + * Backup all user data + */ + async backupData() { + const data = this.getData(); + const { password } = data; + + // Clear any previous error first + this.ensurePart("error").then((p) => { + if (p) { + p.set({ content: "" }); + p.el.dataset.state = "0"; + } + }); + + // Validate password + if (!password) { + return this.showError(LOCALE.PASSWORD_REQUIRED || "Password is required"); + } + + // TODO: Backend service will be implemented later + // For now, simulate the request + return this.postService({ + service: SERVICE.drumate.backup_data, // This service will be created later + password: password, + hub_id: Visitor.id + }).then((response) => { + // Handle response with error + if (response && response.error) { + // Handle password error + if (response.error === "wrong_password") { + return this.showError(LOCALE.WRONG_CREDENTIALS); + } + return this.showError(LOCALE.UNKNOWN_ERROR || "An error occurred"); + } + + // Clear error on success + this.ensurePart("error").then((p) => { + if (p) { + p.set({ content: "" }); + p.el.dataset.state = "0"; + } + }); + + // Show success popup + this.__overlay.feed( + require("./skeleton/ack").default(this, LOCALE.BACKUP_SUCCESS || "Your backup request has been submitted. You will receive an email with a download link shortly.") + ); + + // Clear password field - refresh the form to reset + this.__content.feed(this.skeletons[this._page](this)); + }).catch((err) => { + this.warn('Error backing up data:', err); + // If service doesn't exist yet, show success message for now + if (err && err.message && err.message.includes("not found")) { + // Clear error on success + this.ensurePart("error").then((p) => { + if (p) { + p.set({ content: "" }); + p.el.dataset.state = "0"; + } + }); + this.__overlay.feed( + require("./skeleton/ack").default(this, LOCALE.BACKUP_SUCCESS || "Your backup request has been submitted. You will receive an email with a download link shortly.") + ); + } else { + this.showError(LOCALE.UNKNOWN_ERROR || "Failed to backup data"); + } + }); + } + /** * */ @@ -409,6 +482,10 @@ class settings_account extends LetcBox { if (this._page === 4) { return this.updateDateSettings(cmd); } + // Check if current page is My data (page 5) + if (this._page === 5) { + return this.backupData(cmd); + } return this.updateProfile(cmd); case "load-page": @@ -425,6 +502,9 @@ class settings_account extends LetcBox { case "change-password": return this.updatePassword(cmd); + case "backup-data": + return this.backupData(cmd); + case "manage-seats": return this.handSeatsManager() diff --git a/src/drumee/builtins/widget/settings/account/skeleton/data.js b/src/drumee/builtins/widget/settings/account/skeleton/data.js new file mode 100644 index 00000000..d81f6d6f --- /dev/null +++ b/src/drumee/builtins/widget/settings/account/skeleton/data.js @@ -0,0 +1,93 @@ +const { button, entry } = require("../../../../skeleton/toolkit"); + +/** + * Form for backup all my data + * @param {*} ui + * @returns + */ +function form(ui) { + const fig = `${ui.fig.family}-data`; + + return Skeletons.Box.Y({ + className: `${fig}__form-main`, + kids: [ + // Title + Skeletons.Note({ + className: `${fig}__form-title`, + content: LOCALE.BACKUP_ALL_MY_DATA, + }), + // Message + Skeletons.Note({ + className: `${fig}__form-message content text`, + content: LOCALE.BACKUP_MESSAGE, + }), + // Password entry + Skeletons.Box.G({ + className: `${fig}__form-row`, + kids: [ + Skeletons.Box.G({ + className: `${ui.fig.family}__entry-main`, + kids: [ + Skeletons.Note({ + className: `${ui.fig.family}__entry-label`, + content: LOCALE.PASSWORD, + }), + Skeletons.EntryBox({ + className: `${ui.fig.family}__entry-input`, + name: 'password', + type: _a.password, + placeholder: LOCALE.ENTER_PASSWORD, + shower: 1, + value: '', + interactive: 1, + uiHandler: [ui], + formItem: 'password', + }), + ] + }), + ] + }), + // Error message + Skeletons.Note({ + sys_pn: "error", + className: `${fig}__form-error`, + state: 0, + content: "" + }), + ] + }); +} + +/** + * Settings body for My data page + * @param {*} ui + * @param {*} opt + * @returns + */ +function settings_body(ui) { + const fig = ui.fig.family; + const group = ui.fig.group; + + const buttons = Skeletons.Box.X({ + className: `${group}__buttons ${fig}__buttons`, + uiHandler: ui, + sys_pn: _a.footer, + dataset: { page: ui._page }, + kids: [ + button(ui, { + label: LOCALE.VALIDATE, + type: _a.toggle, + className: `${group}__button`, + service: "backup-data", + priority: "primary", + }), + ], + }); + + return [ + form(ui), + buttons + ]; +} + +export default settings_body; diff --git a/src/drumee/builtins/widget/settings/account/skeleton/index.js b/src/drumee/builtins/widget/settings/account/skeleton/index.js index 583ef356..f6f18f23 100644 --- a/src/drumee/builtins/widget/settings/account/skeleton/index.js +++ b/src/drumee/builtins/widget/settings/account/skeleton/index.js @@ -47,10 +47,11 @@ function nav(ui) { nav_item(ui, "storage", LOCALE.STORAGE, 2), nav_item(ui, "shield", LOCALE.SECURITY, 3), nav_item(ui, "calendar", LOCALE.DATE_AND_TIME, 4), + nav_item(ui, "raw-hard-drive", LOCALE.MY_DATA, 5), ] if (ui.canAdmin()) { - items.push(nav_item(ui, "two-users", "My seats", 5),) + items.push(nav_item(ui, "two-users", "My seats", 6),) } const topics = Skeletons.Box.Y({ diff --git a/src/drumee/builtins/widget/settings/account/skin/data.scss b/src/drumee/builtins/widget/settings/account/skin/data.scss new file mode 100644 index 00000000..a701542f --- /dev/null +++ b/src/drumee/builtins/widget/settings/account/skin/data.scss @@ -0,0 +1,90 @@ +@use "sass:meta"; +@use "mixins/drumee"; +@use "../../skin" as *; + +.settings-account-data { + &__form { + &-main { + width: 100%; + gap: 24px; + } + + &-title { + font-size: 18px; + font-weight: 600; + line-height: 28px; + color: var(--neutral-900); + } + + &-message { + font-size: 14px; + font-weight: 400; + line-height: 22px; + color: var(--neutral-600); + } + + &-row { + grid-template-columns: 1fr; + gap: var(--spacer-5); + } + + &-error { + font-size: 14px; + font-weight: 600; + line-height: 22px; + color: var(--red-400); + + &[data-state="0"] { + display: none; + } + } + } + + // Entry styling for password field - using settings-account classes + // The entry classes are defined in the parent component (settings-account) + // We just need to ensure the input has proper padding for the eye icon + &__form { + &-row { + .settings-account__entry-input { + position: relative; + padding-right: 44px; // Extra padding for eye icon (20px icon + 12px spacing + 12px right padding) + overflow: visible; + + input { + padding-right: 44px; // Match container padding + } + + // Style for shower (eye icon) button + .shower { + position: absolute; + right: 12px; // Inside the input border + top: 50%; + transform: translateY(-50%); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + width: 20px; + height: 20px; + + svg { + width: 20px; + height: 20px; + color: var(--neutral-600); + } + + &:hover { + svg { + color: var(--neutral-900); + } + } + } + + .entry-reminder__icon { + right: -20px; + } + } + } + } +} diff --git a/src/drumee/builtins/widget/settings/account/skin/index.scss b/src/drumee/builtins/widget/settings/account/skin/index.scss index ce70c182..0846028f 100644 --- a/src/drumee/builtins/widget/settings/account/skin/index.scss +++ b/src/drumee/builtins/widget/settings/account/skin/index.scss @@ -7,6 +7,7 @@ @use "modal"; @use "seats"; @use "date"; +@use "data"; .settings-account { &__ui {