Clarify customer login and invoice emails (#250)

Separate Pleno login email from e-conomic invoice email, make the login-email update flow authoritative and cache-safe, clear stale economic profile state, and add focused frontend coverage.
This commit is contained in:
Jeppe B
2026-08-03 10:43:53 +02:00
committed by GitHub
parent 1768f5a38e
commit 664b50d4ef
17 changed files with 448 additions and 106 deletions
@@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, watch } from 'vue'; import { getCurrentInstance, ref, watch } from 'vue';
import {parseError, getError} from "@/components/request/HandleGlobalError.vue"; import {parseError, getError} from "@/components/request/HandleGlobalError.vue";
const props = defineProps({ const props = defineProps({
@@ -24,6 +24,7 @@ const props = defineProps({
}); });
const isEditting = ref(false); const isEditting = ref(false);
const inputId = `configuration-input-${getCurrentInstance()?.uid}`;
const v_model = ref(props.value); const v_model = ref(props.value);
@@ -45,7 +46,7 @@ const parseErrorOnSave = (error) => {
<template> <template>
<div class="field"> <div class="field">
<label class="label">{{ title }}</label> <label class="label" :for="inputId">{{ title }}</label>
<!-- If the value is not set, show a warning message --> <!-- If the value is not set, show a warning message -->
<div v-if="!props.value && showEmptyWarning && !isEditting" <div v-if="!props.value && showEmptyWarning && !isEditting"
class="message is-warning"> class="message is-warning">
@@ -58,6 +59,7 @@ const parseErrorOnSave = (error) => {
<div class="field has-addons"> <div class="field has-addons">
<div class="control is-expanded"> <div class="control is-expanded">
<input <input
:id="inputId"
class="input" class="input"
type="text" type="text"
v-model="v_model" v-model="v_model"
+17 -11
View File
@@ -254,6 +254,20 @@ export const refreshReleaseRuntime = async ({ throwOnError = false } = {}) => {
} }
}; };
const resetEconomicData = () => {
SessionUser.economicData.customerNumber.value = null;
SessionUser.economicData.name.value = null;
SessionUser.economicData.address.value = null;
SessionUser.economicData.zip.value = null;
SessionUser.economicData.city.value = null;
SessionUser.economicData.mobilePhone.value = null;
SessionUser.economicData.email.value = null;
SessionUser.economicData.cvr.value = null;
SessionUser.economicData.currency.value = null;
SessionUser.economicData.country.value = null;
SessionUser.economicData.cached_at.value = null;
};
const resetSessionState = () => { const resetSessionState = () => {
SessionUser.user.id.value = null; SessionUser.user.id.value = null;
SessionUser.user.customer_number.value = null; SessionUser.user.customer_number.value = null;
@@ -302,17 +316,7 @@ const resetSessionState = () => {
} }
} }
SessionUser.economicData.customerNumber.value = null; resetEconomicData();
SessionUser.economicData.name.value = null;
SessionUser.economicData.address.value = null;
SessionUser.economicData.zip.value = null;
SessionUser.economicData.city.value = null;
SessionUser.economicData.mobilePhone.value = null;
SessionUser.economicData.email.value = null;
SessionUser.economicData.cvr.value = null;
SessionUser.economicData.currency.value = null;
SessionUser.economicData.country.value = null;
SessionUser.economicData.cached_at.value = null;
SessionUser.runtimeConfig.economic.transactionDraftCustomerNumber.value = null; SessionUser.runtimeConfig.economic.transactionDraftCustomerNumber.value = null;
SessionUser.runtimeConfig.economic.defaultDistributionDepartmentId.value = null; SessionUser.runtimeConfig.economic.defaultDistributionDepartmentId.value = null;
@@ -698,6 +702,8 @@ export const getSessionData = async () => {
SessionUser.economicData.currency.value = session.economic_customer.currency; SessionUser.economicData.currency.value = session.economic_customer.currency;
SessionUser.economicData.country.value = session.economic_customer.country; SessionUser.economicData.country.value = session.economic_customer.country;
SessionUser.economicData.cached_at.value = new Date(); SessionUser.economicData.cached_at.value = new Date();
} else {
resetEconomicData();
} }
SessionUser.initiated.value = true; SessionUser.initiated.value = true;
}) })
+6 -1
View File
@@ -6908,6 +6908,9 @@
"account_name_desc": "@:{'words.generated.kundekonto'} @:{'words.generated.navn'}", "account_name_desc": "@:{'words.generated.kundekonto'} @:{'words.generated.navn'}",
"account_number_desc": "@:{'words.generated.kundekonto'} @:{'words.generated.nummer'}", "account_number_desc": "@:{'words.generated.kundekonto'} @:{'words.generated.nummer'}",
"description": "@.capitalize:{'words.generated.dine'} @:{'words.generated.fakturaoplysninger'}", "description": "@.capitalize:{'words.generated.dine'} @:{'words.generated.fakturaoplysninger'}",
"invoice_email_desc": "Administreres i e-conomic og ændres ikke, når du ændrer din login-e-mail.",
"invoice_email_label": "Faktura-e-mail (e-conomic)",
"invoice_email_missing": "Ikke angivet i e-conomic",
"title": "@.capitalize:{'words.generated.fakturaoplysninger'}" "title": "@.capitalize:{'words.generated.fakturaoplysninger'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -6935,7 +6938,9 @@
}, },
"security": { "security": {
"description": "@.capitalize:{'words.generated.dine'} sikkerhedsindstillinger", "description": "@.capitalize:{'words.generated.dine'} sikkerhedsindstillinger",
"email_desc": "@.capitalize:{'words.generated.vi'} @:{'words.generated.bruger'} @:{'words.generated.denne'} @:{'words.generated.e'}-@:{'words.generated.mail'} @:{'words.generated.til'} @:{'words.generated.at'} nulstille @:{'words.generated.din'} @:{'words.generated.adgangskode'}, fremsende @:{'words.generated.vaskecertifikater'} @:{'words.generated.og'} @:{'words.generated.andre'} vigtige @:{'words.generated.oplysninger'}." "email_desc": "@.capitalize:{'words.generated.vi'} @:{'words.generated.bruger'} @:{'words.generated.denne'} @:{'words.generated.e'}-@:{'words.generated.mail'} @:{'words.generated.til'} @:{'words.generated.at'} nulstille @:{'words.generated.din'} @:{'words.generated.adgangskode'}, fremsende @:{'words.generated.vaskecertifikater'} @:{'words.generated.og'} @:{'words.generated.andre'} vigtige @:{'words.generated.oplysninger'}.",
"login_email_desc": "Bruges til at logge ind og nulstille din adgangskode. Dette ændrer ikke faktura-e-mailen i e-conomic.",
"login_email_label": "Login-e-mail"
}, },
"title": "@.capitalize:{'words.generated.brugerprofil'}" "title": "@.capitalize:{'words.generated.brugerprofil'}"
}, },
+6 -1
View File
@@ -7018,6 +7018,9 @@
"account_name_desc": "Kundenkontoname", "account_name_desc": "Kundenkontoname",
"account_number_desc": "Kundenkontonummer", "account_number_desc": "Kundenkontonummer",
"description": "@.capitalize:{'words.generated.ihre'} @:{'words.generated.rechnungsinformationen'}", "description": "@.capitalize:{'words.generated.ihre'} @:{'words.generated.rechnungsinformationen'}",
"invoice_email_desc": "Wird in e-conomic verwaltet und nicht geändert, wenn Sie Ihre Anmelde-E-Mail ändern.",
"invoice_email_label": "Rechnungs-E-Mail (e-conomic)",
"invoice_email_missing": "Nicht in e-conomic hinterlegt",
"title": "@:{'words.generated.rechnungsinformationen'}" "title": "@:{'words.generated.rechnungsinformationen'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -7045,7 +7048,9 @@
}, },
"security": { "security": {
"description": "@.capitalize:{'words.generated.ihre'} Sicherheitseinstellungen", "description": "@.capitalize:{'words.generated.ihre'} Sicherheitseinstellungen",
"email_desc": "@.capitalize:{'words.generated.wir'} @:{'words.generated.verwenden'} @:{'words.generated.diese'} @.upper:{'words.generated.e'}-@:{'words.generated.mail'}, @:{'words.generated.um'} @:{'words.generated.ihr'} @:{'words.generated.passwort'} @:{'words.generated.zur'}?ckzusetzen, @:{'words.generated.waschzertifikate'} @:{'words.generated.und'} @:{'words.generated.weitere'} wichtige @:{'words.generated.informationen'} @:{'words.generated.zu'} @:{'words.generated.senden'}." "email_desc": "@.capitalize:{'words.generated.wir'} @:{'words.generated.verwenden'} @:{'words.generated.diese'} @.upper:{'words.generated.e'}-@:{'words.generated.mail'}, @:{'words.generated.um'} @:{'words.generated.ihr'} @:{'words.generated.passwort'} @:{'words.generated.zur'}?ckzusetzen, @:{'words.generated.waschzertifikate'} @:{'words.generated.und'} @:{'words.generated.weitere'} wichtige @:{'words.generated.informationen'} @:{'words.generated.zu'} @:{'words.generated.senden'}.",
"login_email_desc": "Wird zur Anmeldung und zum Zurücksetzen Ihres Passworts verwendet. Die Rechnungs-E-Mail in e-conomic wird dadurch nicht geändert.",
"login_email_label": "Anmelde-E-Mail"
}, },
"title": "Benutzerprofil" "title": "Benutzerprofil"
}, },
+6 -1
View File
@@ -6739,6 +6739,9 @@
"account_name_desc": "@.capitalize:{'words.generated.customer'} @:{'words.generated.account'} @:{'words.generated.name'}", "account_name_desc": "@.capitalize:{'words.generated.customer'} @:{'words.generated.account'} @:{'words.generated.name'}",
"account_number_desc": "@.capitalize:{'words.generated.customer'} @:{'words.generated.account'} @:{'words.generated.number'}", "account_number_desc": "@.capitalize:{'words.generated.customer'} @:{'words.generated.account'} @:{'words.generated.number'}",
"description": "@.capitalize:{'words.generated.your'} @:{'words.generated.invoice'} @:{'words.generated.information'}", "description": "@.capitalize:{'words.generated.your'} @:{'words.generated.invoice'} @:{'words.generated.information'}",
"invoice_email_desc": "Managed in e-conomic and not changed when you change your login email.",
"invoice_email_label": "Invoice email (e-conomic)",
"invoice_email_missing": "Not set in e-conomic",
"title": "@.capitalize:{'words.generated.invoice'} @.capitalize:{'words.generated.information'}" "title": "@.capitalize:{'words.generated.invoice'} @.capitalize:{'words.generated.information'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -6766,7 +6769,9 @@
}, },
"security": { "security": {
"description": "@.capitalize:{'words.generated.your'} @:{'words.generated.security'} @:{'words.generated.settings'}", "description": "@.capitalize:{'words.generated.your'} @:{'words.generated.security'} @:{'words.generated.settings'}",
"email_desc": "@.capitalize:{'words.generated.we'} @:{'words.generated.use'} @:{'words.generated.this'} @:{'words.generated.email'} @:{'words.generated.to'} @:{'words.generated.reset'} @:{'words.generated.your'} @:{'words.generated.password'}, @:{'words.generated.send'} @:{'words.generated.wash'} @:{'words.generated.certificates'} @:{'words.generated.and'} @:{'words.generated.other'} important @:{'words.generated.information'}." "email_desc": "@.capitalize:{'words.generated.we'} @:{'words.generated.use'} @:{'words.generated.this'} @:{'words.generated.email'} @:{'words.generated.to'} @:{'words.generated.reset'} @:{'words.generated.your'} @:{'words.generated.password'}, @:{'words.generated.send'} @:{'words.generated.wash'} @:{'words.generated.certificates'} @:{'words.generated.and'} @:{'words.generated.other'} important @:{'words.generated.information'}.",
"login_email_desc": "Used to sign in and reset your password. This does not change the invoice email in e-conomic.",
"login_email_label": "Login email"
}, },
"title": "@.capitalize:{'words.generated.user'} @.capitalize:{'words.generated.profile'}" "title": "@.capitalize:{'words.generated.user'} @.capitalize:{'words.generated.profile'}"
}, },
+5
View File
@@ -7119,6 +7119,9 @@
"account_name_desc": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.account_name_desc'}", "account_name_desc": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.account_name_desc'}",
"account_number_desc": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.account_number_desc'}", "account_number_desc": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.account_number_desc'}",
"description": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.description'}", "description": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.description'}",
"invoice_email_desc": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.invoice_email_desc'}",
"invoice_email_label": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.invoice_email_label'}",
"invoice_email_missing": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.invoice_email_missing'}",
"title": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.title'}" "title": "@:{'templates.generated.compat.user_dashboard.profile.invoicing.title'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -7157,6 +7160,8 @@
"edit_email": "@:{'templates.generated.compat.global.edit'}", "edit_email": "@:{'templates.generated.compat.global.edit'}",
"edit_phone": "@:{'templates.generated.compat.global.edit'}", "edit_phone": "@:{'templates.generated.compat.global.edit'}",
"email_desc": "@:{'templates.generated.compat.user_dashboard.profile.security.email_desc'}", "email_desc": "@:{'templates.generated.compat.user_dashboard.profile.security.email_desc'}",
"login_email_desc": "@:{'templates.generated.compat.user_dashboard.profile.security.login_email_desc'}",
"login_email_label": "@:{'templates.generated.compat.user_dashboard.profile.security.login_email_label'}",
"logout": "@:common.logout", "logout": "@:common.logout",
"password_label": "@:global.password", "password_label": "@:global.password",
"title": "@:settings.security" "title": "@:settings.security"
+6 -1
View File
@@ -7021,6 +7021,9 @@
"account_name_desc": "Kundekontonavn", "account_name_desc": "Kundekontonavn",
"account_number_desc": "Kundekontonummer", "account_number_desc": "Kundekontonummer",
"description": "@.capitalize:{'words.generated.din'} @:{'words.generated.fakturainformasjon'}", "description": "@.capitalize:{'words.generated.din'} @:{'words.generated.fakturainformasjon'}",
"invoice_email_desc": "Administreres i e-conomic og endres ikke når du endrer innloggings-e-posten din.",
"invoice_email_label": "Faktura-e-post (e-conomic)",
"invoice_email_missing": "Ikke angitt i e-conomic",
"title": "@.capitalize:{'words.generated.fakturainformasjon'}" "title": "@.capitalize:{'words.generated.fakturainformasjon'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -7048,7 +7051,9 @@
}, },
"security": { "security": {
"description": "Sikkerhetsinnstillingene @:{'words.generated.dine'}", "description": "Sikkerhetsinnstillingene @:{'words.generated.dine'}",
"email_desc": "@.capitalize:{'words.generated.vi'} @:{'words.generated.bruker'} @:{'words.generated.denne'} @:{'words.generated.e'}-@:{'words.generated.posten'} @:{'words.generated.til'} @:{'words.generated.a'} tilbakestille @:{'words.generated.passordet'} @:{'words.generated.ditt'}, @:{'words.generated.sende'} @:{'words.generated.vaskesertifikater'} @:{'words.generated.og'} @:{'words.generated.annen'} viktig @:{'words.generated.informasjon'}." "email_desc": "@.capitalize:{'words.generated.vi'} @:{'words.generated.bruker'} @:{'words.generated.denne'} @:{'words.generated.e'}-@:{'words.generated.posten'} @:{'words.generated.til'} @:{'words.generated.a'} tilbakestille @:{'words.generated.passordet'} @:{'words.generated.ditt'}, @:{'words.generated.sende'} @:{'words.generated.vaskesertifikater'} @:{'words.generated.og'} @:{'words.generated.annen'} viktig @:{'words.generated.informasjon'}.",
"login_email_desc": "Brukes til å logge inn og tilbakestille passordet ditt. Dette endrer ikke faktura-e-posten i e-conomic.",
"login_email_label": "Innloggings-e-post"
}, },
"title": "Brukerprofil" "title": "Brukerprofil"
}, },
+6 -1
View File
@@ -7071,6 +7071,9 @@
"account_name_desc": "@:{'words.generated.kundens'} kontonamn", "account_name_desc": "@:{'words.generated.kundens'} kontonamn",
"account_number_desc": "@:{'words.generated.kundens'} kontonummer", "account_number_desc": "@:{'words.generated.kundens'} kontonummer",
"description": "@.capitalize:{'words.generated.din'} @:{'words.generated.fakturainformation'}", "description": "@.capitalize:{'words.generated.din'} @:{'words.generated.fakturainformation'}",
"invoice_email_desc": "Hanteras i e-conomic och ändras inte när du ändrar din e-postadress för inloggning.",
"invoice_email_label": "E-post för fakturor (e-conomic)",
"invoice_email_missing": "Inte angiven i e-conomic",
"title": "@.capitalize:{'words.generated.fakturainformation'}" "title": "@.capitalize:{'words.generated.fakturainformation'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -7098,7 +7101,9 @@
}, },
"security": { "security": {
"description": "@.capitalize:{'words.generated.dina'} säkerhetsinställningar", "description": "@.capitalize:{'words.generated.dina'} säkerhetsinställningar",
"email_desc": "@.capitalize:{'words.generated.vi'} @:{'words.generated.anvander'} @:{'words.generated.denna'} @:{'words.generated.e'}-@:{'words.generated.post'} @:{'words.generated.for'} @:{'words.generated.att'} @:{'words.generated.aterstalla'} @:{'words.generated.ditt'} @:{'words.generated.losenord'}, @:{'words.generated.skicka'} @:{'words.generated.tvattcertifikat'} @:{'words.generated.och'} @:{'words.generated.annan'} viktig @:{'words.generated.information'}." "email_desc": "@.capitalize:{'words.generated.vi'} @:{'words.generated.anvander'} @:{'words.generated.denna'} @:{'words.generated.e'}-@:{'words.generated.post'} @:{'words.generated.for'} @:{'words.generated.att'} @:{'words.generated.aterstalla'} @:{'words.generated.ditt'} @:{'words.generated.losenord'}, @:{'words.generated.skicka'} @:{'words.generated.tvattcertifikat'} @:{'words.generated.och'} @:{'words.generated.annan'} viktig @:{'words.generated.information'}.",
"login_email_desc": "Används för att logga in och återställa ditt lösenord. Detta ändrar inte e-postadressen för fakturor i e-conomic.",
"login_email_label": "E-post för inloggning"
}, },
"title": "@.capitalize:{'words.generated.user'} @.capitalize:{'words.generated.profile'}" "title": "@.capitalize:{'words.generated.user'} @.capitalize:{'words.generated.profile'}"
}, },
@@ -147,6 +147,9 @@
"account_name_desc": "@:{'terms.glossary.kundekonto'} @:{'terms.glossary.navn'}", "account_name_desc": "@:{'terms.glossary.kundekonto'} @:{'terms.glossary.navn'}",
"account_number_desc": "@:{'terms.glossary.kundekonto'} @:{'terms.glossary.nummer'}", "account_number_desc": "@:{'terms.glossary.kundekonto'} @:{'terms.glossary.nummer'}",
"description": "@.capitalize:{'terms.glossary.dine'} @:{'terms.glossary.fakturaoplysninger'}", "description": "@.capitalize:{'terms.glossary.dine'} @:{'terms.glossary.fakturaoplysninger'}",
"invoice_email_desc": "Administreres i e-conomic og ændres ikke, når du ændrer din login-e-mail.",
"invoice_email_label": "Faktura-e-mail (e-conomic)",
"invoice_email_missing": "Ikke angivet i e-conomic",
"title": "@.capitalize:{'terms.glossary.fakturaoplysninger'}" "title": "@.capitalize:{'terms.glossary.fakturaoplysninger'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -174,7 +177,9 @@
}, },
"security": { "security": {
"description": "@.capitalize:{'terms.glossary.dine'} sikkerhedsindstillinger", "description": "@.capitalize:{'terms.glossary.dine'} sikkerhedsindstillinger",
"email_desc": "@.capitalize:{'terms.glossary.vi'} @:{'terms.glossary.bruger'} @:{'terms.glossary.denne'} @:{'terms.glossary.e'}-@:{'terms.glossary.mail'} @:{'terms.glossary.til'} @:{'terms.glossary.at'} nulstille @:{'terms.glossary.din'} @:{'terms.glossary.adgangskode'}, fremsende @:{'terms.glossary.vaskecertifikater'} @:{'terms.glossary.og'} @:{'terms.glossary.andre'} vigtige @:{'terms.glossary.oplysninger'}." "email_desc": "@.capitalize:{'terms.glossary.vi'} @:{'terms.glossary.bruger'} @:{'terms.glossary.denne'} @:{'terms.glossary.e'}-@:{'terms.glossary.mail'} @:{'terms.glossary.til'} @:{'terms.glossary.at'} nulstille @:{'terms.glossary.din'} @:{'terms.glossary.adgangskode'}, fremsende @:{'terms.glossary.vaskecertifikater'} @:{'terms.glossary.og'} @:{'terms.glossary.andre'} vigtige @:{'terms.glossary.oplysninger'}.",
"login_email_desc": "Bruges til at logge ind og nulstille din adgangskode. Dette ændrer ikke faktura-e-mailen i e-conomic.",
"login_email_label": "Login-e-mail"
}, },
"title": "@.capitalize:{'terms.glossary.brugerprofil'}" "title": "@.capitalize:{'terms.glossary.brugerprofil'}"
}, },
@@ -140,6 +140,9 @@
"account_name_desc": "Kundenkontoname", "account_name_desc": "Kundenkontoname",
"account_number_desc": "Kundenkontonummer", "account_number_desc": "Kundenkontonummer",
"description": "@.capitalize:{'terms.glossary.ihre'} @:{'terms.glossary.rechnungsinformationen'}", "description": "@.capitalize:{'terms.glossary.ihre'} @:{'terms.glossary.rechnungsinformationen'}",
"invoice_email_desc": "Wird in e-conomic verwaltet und nicht geändert, wenn Sie Ihre Anmelde-E-Mail ändern.",
"invoice_email_label": "Rechnungs-E-Mail (e-conomic)",
"invoice_email_missing": "Nicht in e-conomic hinterlegt",
"title": "@:{'terms.glossary.rechnungsinformationen'}" "title": "@:{'terms.glossary.rechnungsinformationen'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -167,7 +170,9 @@
}, },
"security": { "security": {
"description": "@.capitalize:{'terms.glossary.ihre'} Sicherheitseinstellungen", "description": "@.capitalize:{'terms.glossary.ihre'} Sicherheitseinstellungen",
"email_desc": "@.capitalize:{'terms.glossary.wir'} @:{'terms.glossary.verwenden'} @:{'terms.glossary.diese'} @.upper:{'terms.glossary.e'}-@:{'terms.glossary.mail'}, @:{'terms.glossary.um'} @:{'terms.glossary.ihr'} @:{'terms.glossary.passwort'} @:{'terms.glossary.zur'}?ckzusetzen, @:{'terms.glossary.waschzertifikate'} @:{'terms.glossary.und'} @:{'terms.glossary.weitere'} wichtige @:{'terms.glossary.informationen'} @:{'terms.glossary.zu'} @:{'terms.glossary.senden'}." "email_desc": "@.capitalize:{'terms.glossary.wir'} @:{'terms.glossary.verwenden'} @:{'terms.glossary.diese'} @.upper:{'terms.glossary.e'}-@:{'terms.glossary.mail'}, @:{'terms.glossary.um'} @:{'terms.glossary.ihr'} @:{'terms.glossary.passwort'} @:{'terms.glossary.zur'}?ckzusetzen, @:{'terms.glossary.waschzertifikate'} @:{'terms.glossary.und'} @:{'terms.glossary.weitere'} wichtige @:{'terms.glossary.informationen'} @:{'terms.glossary.zu'} @:{'terms.glossary.senden'}.",
"login_email_desc": "Wird zur Anmeldung und zum Zurücksetzen Ihres Passworts verwendet. Die Rechnungs-E-Mail in e-conomic wird dadurch nicht geändert.",
"login_email_label": "Anmelde-E-Mail"
}, },
"title": "Benutzerprofil" "title": "Benutzerprofil"
}, },
@@ -147,6 +147,9 @@
"account_name_desc": "@.capitalize:{'terms.glossary.customer'} @:{'terms.glossary.account'} @:{'terms.glossary.name'}", "account_name_desc": "@.capitalize:{'terms.glossary.customer'} @:{'terms.glossary.account'} @:{'terms.glossary.name'}",
"account_number_desc": "@.capitalize:{'terms.glossary.customer'} @:{'terms.glossary.account'} @:{'terms.glossary.number'}", "account_number_desc": "@.capitalize:{'terms.glossary.customer'} @:{'terms.glossary.account'} @:{'terms.glossary.number'}",
"description": "@.capitalize:{'terms.glossary.your'} @:{'terms.glossary.invoice'} @:{'terms.glossary.information'}", "description": "@.capitalize:{'terms.glossary.your'} @:{'terms.glossary.invoice'} @:{'terms.glossary.information'}",
"invoice_email_desc": "Managed in e-conomic and not changed when you change your login email.",
"invoice_email_label": "Invoice email (e-conomic)",
"invoice_email_missing": "Not set in e-conomic",
"title": "@.capitalize:{'terms.glossary.invoice'} @.capitalize:{'terms.glossary.information'}" "title": "@.capitalize:{'terms.glossary.invoice'} @.capitalize:{'terms.glossary.information'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -174,7 +177,9 @@
}, },
"security": { "security": {
"description": "@.capitalize:{'terms.glossary.your'} @:{'terms.glossary.security'} @:{'terms.glossary.settings'}", "description": "@.capitalize:{'terms.glossary.your'} @:{'terms.glossary.security'} @:{'terms.glossary.settings'}",
"email_desc": "@.capitalize:{'terms.glossary.we'} @:{'terms.glossary.use'} @:{'terms.glossary.this'} @:{'terms.glossary.email'} @:{'terms.glossary.to'} @:{'terms.glossary.reset'} @:{'terms.glossary.your'} @:{'terms.glossary.password'}, @:{'terms.glossary.send'} @:{'terms.glossary.wash'} @:{'terms.glossary.certificates'} @:{'terms.glossary.and'} @:{'terms.glossary.other'} important @:{'terms.glossary.information'}." "email_desc": "@.capitalize:{'terms.glossary.we'} @:{'terms.glossary.use'} @:{'terms.glossary.this'} @:{'terms.glossary.email'} @:{'terms.glossary.to'} @:{'terms.glossary.reset'} @:{'terms.glossary.your'} @:{'terms.glossary.password'}, @:{'terms.glossary.send'} @:{'terms.glossary.wash'} @:{'terms.glossary.certificates'} @:{'terms.glossary.and'} @:{'terms.glossary.other'} important @:{'terms.glossary.information'}.",
"login_email_desc": "Used to sign in and reset your password. This does not change the invoice email in e-conomic.",
"login_email_label": "Login email"
}, },
"title": "@.capitalize:{'terms.glossary.user'} @.capitalize:{'terms.glossary.profile'}" "title": "@.capitalize:{'terms.glossary.user'} @.capitalize:{'terms.glossary.profile'}"
}, },
@@ -184,6 +184,9 @@
"account_name_desc": "@:{'phrases.compat.user_dashboard.profile.invoicing.account_name_desc'}", "account_name_desc": "@:{'phrases.compat.user_dashboard.profile.invoicing.account_name_desc'}",
"account_number_desc": "@:{'phrases.compat.user_dashboard.profile.invoicing.account_number_desc'}", "account_number_desc": "@:{'phrases.compat.user_dashboard.profile.invoicing.account_number_desc'}",
"description": "@:{'phrases.compat.user_dashboard.profile.invoicing.description'}", "description": "@:{'phrases.compat.user_dashboard.profile.invoicing.description'}",
"invoice_email_desc": "@:{'phrases.compat.user_dashboard.profile.invoicing.invoice_email_desc'}",
"invoice_email_label": "@:{'phrases.compat.user_dashboard.profile.invoicing.invoice_email_label'}",
"invoice_email_missing": "@:{'phrases.compat.user_dashboard.profile.invoicing.invoice_email_missing'}",
"title": "@:{'phrases.compat.user_dashboard.profile.invoicing.title'}" "title": "@:{'phrases.compat.user_dashboard.profile.invoicing.title'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -222,6 +225,8 @@
"edit_email": "@:{'phrases.compat.global.edit'}", "edit_email": "@:{'phrases.compat.global.edit'}",
"edit_phone": "@:{'phrases.compat.global.edit'}", "edit_phone": "@:{'phrases.compat.global.edit'}",
"email_desc": "@:{'phrases.compat.user_dashboard.profile.security.email_desc'}", "email_desc": "@:{'phrases.compat.user_dashboard.profile.security.email_desc'}",
"login_email_desc": "@:{'phrases.compat.user_dashboard.profile.security.login_email_desc'}",
"login_email_label": "@:{'phrases.compat.user_dashboard.profile.security.login_email_label'}",
"logout": "@:common.logout", "logout": "@:common.logout",
"password_label": "@:global.password", "password_label": "@:global.password",
"title": "@:settings.security" "title": "@:settings.security"
@@ -140,6 +140,9 @@
"account_name_desc": "Kundekontonavn", "account_name_desc": "Kundekontonavn",
"account_number_desc": "Kundekontonummer", "account_number_desc": "Kundekontonummer",
"description": "@.capitalize:{'terms.glossary.din'} @:{'terms.glossary.fakturainformasjon'}", "description": "@.capitalize:{'terms.glossary.din'} @:{'terms.glossary.fakturainformasjon'}",
"invoice_email_desc": "Administreres i e-conomic og endres ikke når du endrer innloggings-e-posten din.",
"invoice_email_label": "Faktura-e-post (e-conomic)",
"invoice_email_missing": "Ikke angitt i e-conomic",
"title": "@.capitalize:{'terms.glossary.fakturainformasjon'}" "title": "@.capitalize:{'terms.glossary.fakturainformasjon'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -167,7 +170,9 @@
}, },
"security": { "security": {
"description": "Sikkerhetsinnstillingene @:{'terms.glossary.dine'}", "description": "Sikkerhetsinnstillingene @:{'terms.glossary.dine'}",
"email_desc": "@.capitalize:{'terms.glossary.vi'} @:{'terms.glossary.bruker'} @:{'terms.glossary.denne'} @:{'terms.glossary.e'}-@:{'terms.glossary.posten'} @:{'terms.glossary.til'} @:{'terms.glossary.a'} tilbakestille @:{'terms.glossary.passordet'} @:{'terms.glossary.ditt'}, @:{'terms.glossary.sende'} @:{'terms.glossary.vaskesertifikater'} @:{'terms.glossary.og'} @:{'terms.glossary.annen'} viktig @:{'terms.glossary.informasjon'}." "email_desc": "@.capitalize:{'terms.glossary.vi'} @:{'terms.glossary.bruker'} @:{'terms.glossary.denne'} @:{'terms.glossary.e'}-@:{'terms.glossary.posten'} @:{'terms.glossary.til'} @:{'terms.glossary.a'} tilbakestille @:{'terms.glossary.passordet'} @:{'terms.glossary.ditt'}, @:{'terms.glossary.sende'} @:{'terms.glossary.vaskesertifikater'} @:{'terms.glossary.og'} @:{'terms.glossary.annen'} viktig @:{'terms.glossary.informasjon'}.",
"login_email_desc": "Brukes til å logge inn og tilbakestille passordet ditt. Dette endrer ikke faktura-e-posten i e-conomic.",
"login_email_label": "Innloggings-e-post"
}, },
"title": "Brukerprofil" "title": "Brukerprofil"
}, },
@@ -140,6 +140,9 @@
"account_name_desc": "@:{'terms.glossary.kundens'} kontonamn", "account_name_desc": "@:{'terms.glossary.kundens'} kontonamn",
"account_number_desc": "@:{'terms.glossary.kundens'} kontonummer", "account_number_desc": "@:{'terms.glossary.kundens'} kontonummer",
"description": "@.capitalize:{'terms.glossary.din'} @:{'terms.glossary.fakturainformation'}", "description": "@.capitalize:{'terms.glossary.din'} @:{'terms.glossary.fakturainformation'}",
"invoice_email_desc": "Hanteras i e-conomic och ändras inte när du ändrar din e-postadress för inloggning.",
"invoice_email_label": "E-post för fakturor (e-conomic)",
"invoice_email_missing": "Inte angiven i e-conomic",
"title": "@.capitalize:{'terms.glossary.fakturainformation'}" "title": "@.capitalize:{'terms.glossary.fakturainformation'}"
}, },
"microsoft_auth": { "microsoft_auth": {
@@ -167,7 +170,9 @@
}, },
"security": { "security": {
"description": "@.capitalize:{'terms.glossary.dina'} säkerhetsinställningar", "description": "@.capitalize:{'terms.glossary.dina'} säkerhetsinställningar",
"email_desc": "@.capitalize:{'terms.glossary.vi'} @:{'terms.glossary.anvander'} @:{'terms.glossary.denna'} @:{'terms.glossary.e'}-@:{'terms.glossary.post'} @:{'terms.glossary.for'} @:{'terms.glossary.att'} @:{'terms.glossary.aterstalla'} @:{'terms.glossary.ditt'} @:{'terms.glossary.losenord'}, @:{'terms.glossary.skicka'} @:{'terms.glossary.tvattcertifikat'} @:{'terms.glossary.och'} @:{'terms.glossary.annan'} viktig @:{'terms.glossary.information'}." "email_desc": "@.capitalize:{'terms.glossary.vi'} @:{'terms.glossary.anvander'} @:{'terms.glossary.denna'} @:{'terms.glossary.e'}-@:{'terms.glossary.post'} @:{'terms.glossary.for'} @:{'terms.glossary.att'} @:{'terms.glossary.aterstalla'} @:{'terms.glossary.ditt'} @:{'terms.glossary.losenord'}, @:{'terms.glossary.skicka'} @:{'terms.glossary.tvattcertifikat'} @:{'terms.glossary.och'} @:{'terms.glossary.annan'} viktig @:{'terms.glossary.information'}.",
"login_email_desc": "Används för att logga in och återställa ditt lösenord. Detta ändrar inte e-postadressen för fakturor i e-conomic.",
"login_email_label": "E-post för inloggning"
}, },
"title": "@.capitalize:{'terms.glossary.user'} @.capitalize:{'terms.glossary.profile'}" "title": "@.capitalize:{'terms.glossary.user'} @.capitalize:{'terms.glossary.profile'}"
}, },
@@ -21,6 +21,7 @@ import { isAccountDeletionEligible } from "@/services/accountDeletion.js";
const { t } = useI18n(); const { t } = useI18n();
const isLoading = ref(false); const isLoading = ref(false);
const isSavingEmail = ref(false);
const canRequestAccountDeletion = computed( const canRequestAccountDeletion = computed(
() => () =>
!SessionUser.hasSuperUserToken() && !SessionUser.hasSuperUserToken() &&
@@ -197,115 +198,98 @@ const onClickVerifySubuserContact = async (channel) => {
}; };
/** Email */ /** Email */
const onClickSaveEmail = async () => { const onClickSaveEmail = async () => {
// Get the password from Swal const passwordResult = await Swal.fire({
await Swal.fire({
title: t("profile.email.enter_password"), title: t("profile.email.enter_password"),
input: "password", input: "password",
inputPlaceholder: t("auth.password"), inputPlaceholder: t("auth.password"),
showCancelButton: true, showCancelButton: true,
preConfirm: () => { preConfirm: async (password) => {
const value = Swal.getInput().value; if (!password) {
if (!value) {
Swal.showValidationMessage(t("profile.email.password_prompt")); Swal.showValidationMessage(t("profile.email.password_prompt"));
return; return false;
}
try {
const response = await SessionUser.request("/account/security/validate-password", "POST", {
password,
});
if (response.status !== 200) {
Swal.showValidationMessage(t("profile.error.validation_error"));
return false;
}
return password;
} catch (error) {
Swal.showValidationMessage(SessionUser.functions.parseErrorMessage(error));
return false;
} }
Swal.showLoading();
return value;
}, },
inputAttributes: { inputAttributes: {
"aria-label": t("auth.password"), "aria-label": t("auth.password"),
}, },
inputValidator: async (value) => {
if (!value) {
return t("profile.email.password_prompt");
}
// Validate the password
await SessionUser.request("/account/security/validate-password", "POST", {
password: value,
})
.then((result) => {
if (result.status === 200) {
// Show the email input dialog
onSaveEmail(value);
} else {
Swal.fire({
title: t("common.error"),
text: t("profile.error.validation_error"),
icon: "error",
});
}
})
.catch((error) => {
Swal.fire({
title: t("common.error"),
text: SessionUser.functions.parseErrorMessage(error),
icon: "error",
});
})
.finally(() => {
isLoading.value = false;
});
},
allowOutsideClick: () => !Swal.isLoading(), allowOutsideClick: () => !Swal.isLoading(),
showLoaderOnConfirm: true, showLoaderOnConfirm: true,
}); });
if (passwordResult.isConfirmed) {
await onSaveEmail(passwordResult.value);
}
}; };
const onSaveEmail = async (password) => { const onSaveEmail = async (password) => {
// Show the confirmation dialog const emailResult = await Swal.fire({
await Swal.fire({
title: t("profile.email.new_email_prompt"), title: t("profile.email.new_email_prompt"),
text: t("profile.email.new_email_text"), text: t("profile.email.new_email_text"),
icon: "warning", icon: "warning",
input: "text", input: "text",
inputLabel: t("profile.email.label"), inputLabel: t("profile.email.label"),
showCancelButton: true, showCancelButton: true,
preConfirm: () => { preConfirm: async (value) => {
const value = Swal.getInput().value; const email = String(value || "").trim();
if (!value) { if (!email) {
Swal.showValidationMessage(t("profile.email.enter_email")); Swal.showValidationMessage(t("profile.email.enter_email"));
return; return false;
} }
// Validate the email address
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) { if (!emailRegex.test(email)) {
Swal.showValidationMessage(t("profile.email.invalid_email")); Swal.showValidationMessage(t("profile.email.invalid_email"));
return; return false;
} }
isLoading.value = true;
Swal.showLoading(); isSavingEmail.value = true;
SessionUser.request("/account/security/change-email", "POST", { try {
email: value, const response = await SessionUser.request("/account/security/change-email", "POST", {
password: password, email,
}) password,
.then((result) => {
if (result.status === 200) {
Swal.fire({
title: t("profile.email.saved"),
text: t("profile.email.saved_text"),
icon: "success",
});
SessionUser.getSessionData();
} else {
Swal.fire({
title: t("common.error"),
text: t("profile.error.save_email_error"),
icon: "error",
});
}
})
.catch((error) => {
Swal.fire({
title: t("common.error"),
text: SessionUser.functions.parseErrorMessage(error),
icon: "error",
});
// Reset the password
})
.finally(() => {
isLoading.value = false;
}); });
if (response.status !== 200) {
Swal.showValidationMessage(t("profile.error.save_email_error"));
return false;
}
const savedEmail = response?.data?.data?.email || response?.data?.email || email;
SessionUser.user.email.value = savedEmail;
return savedEmail;
} catch (error) {
Swal.showValidationMessage(SessionUser.functions.parseErrorMessage(error));
return false;
} finally {
isSavingEmail.value = false;
}
}, },
allowOutsideClick: () => !Swal.isLoading(),
showLoaderOnConfirm: true,
}); });
if (emailResult.isConfirmed) {
await Swal.fire({
title: t("profile.email.saved"),
text: t("profile.email.saved_text"),
icon: "success",
});
}
}; };
/** Default department */ /** Default department */
const defaultDepartment = ref(null); const defaultDepartment = ref(null);
@@ -732,6 +716,7 @@ const onClickSaveWashCertificateEmail = async () => {
:title="$t('user_dashboard.profile.invoicing.title')" :title="$t('user_dashboard.profile.invoicing.title')"
:description="$t('user_dashboard.profile.invoicing.description')" :description="$t('user_dashboard.profile.invoicing.description')"
icon="fas fa-user" icon="fas fa-user"
data-testid="user-profile-invoicing-card"
> >
<!-- Konto navn --> <!-- Konto navn -->
<ConfigurationInput <ConfigurationInput
@@ -748,6 +733,15 @@ const onClickSaveWashCertificateEmail = async () => {
:value="SessionUser.user.customer_number" :value="SessionUser.user.customer_number"
:disabled="true" :disabled="true"
/> />
<ConfigurationInput
class="mt-4"
:title="$t('user_dashboard.profile.invoicing.invoice_email_label')"
:description="$t('user_dashboard.profile.invoicing.invoice_email_desc')"
:value="SessionUser.economicData.email.value || $t('user_dashboard.profile.invoicing.invoice_email_missing')"
:disabled="true"
:show-empty-warning="false"
data-testid="user-profile-invoice-email"
/>
</ConfigurationCategory> </ConfigurationCategory>
<!-- Security --> <!-- Security -->
@@ -763,16 +757,18 @@ const onClickSaveWashCertificateEmail = async () => {
<div class="columns is-desktop is-align-items-flex-end mb-2"> <div class="columns is-desktop is-align-items-flex-end mb-2">
<div class="column"> <div class="column">
<ConfigurationInput <ConfigurationInput
:title="SessionUser.objects.global.language.email" :title="$t('user_dashboard.profile.security.login_email_label')"
:description="$t('user_dashboard.profile.security.login_email_desc')"
:value="SessionUser.user.email" :value="SessionUser.user.email"
:disabled="true" :disabled="true"
data-testid="user-profile-login-email"
/> />
</div> </div>
<div class="column is-narrow"> <div class="column is-narrow">
<button <button
class="button is-link is-fullwidth-mobile mb-1 is-fullwidth" class="button is-link is-fullwidth-mobile mb-1 is-fullwidth"
@click="onClickSaveEmail" @click="onClickSaveEmail"
:disabled="isLoading" :disabled="isLoading || isSavingEmail"
data-testid="user-profile-security-edit-email" data-testid="user-profile-security-edit-email"
> >
<span class="icon is-small"><i class="fas fa-edit"></i></span> <span class="icon is-small"><i class="fas fa-edit"></i></span>
+234 -1
View File
@@ -1,6 +1,92 @@
import { expect, test } from "@playwright/test"; import { expect, test, type Page } from "@playwright/test";
import { loginAsUser } from "./fixtures"; import { loginAsUser } from "./fixtures";
type ProfileEmailSession = {
loginEmail: string;
invoiceEmail?: string | null;
};
const buildProfileSession = ({ loginEmail, invoiceEmail }: ProfileEmailSession) => ({
id: 1,
customer_number: 12345679,
group_id: 1,
email: loginEmail,
phone: {
number: "12345678",
country_code: 45,
},
notifications: {
wash_certificate_email: null,
email_notifications_enabled: true,
sms_notifications_enabled: false,
superuser_new_customer_email_notifications_enabled: false,
},
created_at: "2026-01-01T00:00:00.000Z",
updated_at: "2026-01-01T00:00:00.000Z",
display_name: "E2E User",
permissions: ["user"],
economic_customer: invoiceEmail
? {
customerNumber: 12345679,
name: "E2E User",
address: "Test Street 1",
zip: "1000",
city: "Copenhagen",
mobilePhone: "12345678",
email: invoiceEmail,
corporateIdentificationNumber: "12345678",
currency: "DKK",
country: "DK",
}
: [],
runtime_config: {
economic: {
transaction_draft_customer_number: null,
default_distribution_department_id: null,
},
release: {},
},
});
async function openUserProfileWithEmails(page: Page, session: ProfileEmailSession) {
await loginAsUser(page);
let sessionRequestCount = 0;
await page.route("**/auth/session", async (route) => {
sessionRequestCount += 1;
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: buildProfileSession(session) }),
});
});
await page.goto("/user/profile");
const invoicingCard = page.getByTestId("user-profile-invoicing-card");
const securityCard = page.getByTestId("user-profile-security-card");
await expect(invoicingCard).toBeVisible();
await expect(securityCard).toBeVisible();
await invoicingCard.locator(".card-header").click();
await securityCard.locator(".card-header").click();
await expect(page.getByTestId("user-profile-login-email")).toBeVisible();
await expect(page.getByTestId("user-profile-invoice-email")).toBeVisible();
return {
getSessionRequestCount: () => sessionRequestCount,
};
}
const emailValue = (page: Page, testId: string) => page.getByTestId(testId).locator("input");
async function completePasswordStep(page: Page, password = "valid-password") {
await page.getByTestId("user-profile-security-edit-email").click();
const popup = page.locator(".swal2-popup");
await expect(popup).toBeVisible();
await popup.locator(".swal2-input").fill(password);
await popup.locator(".swal2-confirm").click();
await expect(popup.locator(".swal2-input")).toHaveAttribute("type", "text");
}
async function openUserSecurity(page) { async function openUserSecurity(page) {
await loginAsUser(page); await loginAsUser(page);
await page.goto("/user/profile"); await page.goto("/user/profile");
@@ -44,6 +130,153 @@ test("[PROFILE][User][Security] renders the current security surface", async ({
await expect(page.getByTestId("subuser-profile-security-logout")).toHaveCount(0); await expect(page.getByTestId("subuser-profile-security-logout")).toHaveCount(0);
}); });
test("[PROFILE][User][Security] shows exact login and invoice emails under distinct labels", async ({ page }) => {
await openUserProfileWithEmails(page, {
loginEmail: "k.sand@example.test",
invoiceEmail: "billing+wash@example.test",
});
const loginEmail = page.getByTestId("user-profile-login-email");
const invoiceEmail = page.getByTestId("user-profile-invoice-email");
const loginLabel = (await loginEmail.locator("label").innerText()).trim();
const invoiceLabel = (await invoiceEmail.locator("label").innerText()).trim();
expect(loginLabel).not.toBe("");
expect(invoiceLabel).not.toBe("");
expect(loginLabel).not.toBe(invoiceLabel);
await expect(page.getByRole("textbox", { name: loginLabel, exact: true })).toHaveValue("k.sand@example.test");
await expect(page.getByRole("textbox", { name: invoiceLabel, exact: true })).toHaveValue("billing+wash@example.test");
await expect(emailValue(page, "user-profile-login-email")).toHaveValue("k.sand@example.test");
await expect(emailValue(page, "user-profile-invoice-email")).toHaveValue("billing+wash@example.test");
for (const field of [loginEmail, invoiceEmail]) {
const bounds = await field.boundingBox();
const viewport = page.viewportSize();
expect(bounds).not.toBeNull();
expect(viewport).not.toBeNull();
expect(bounds!.x).toBeGreaterThanOrEqual(0);
expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(viewport!.width + 1);
}
});
test("[PROFILE][User][Security] keeps equal login and invoice emails as separate fields", async ({ page }) => {
await openUserProfileWithEmails(page, {
loginEmail: "same.address@example.test",
invoiceEmail: "same.address@example.test",
});
await expect(emailValue(page, "user-profile-login-email")).toHaveValue("same.address@example.test");
await expect(emailValue(page, "user-profile-invoice-email")).toHaveValue("same.address@example.test");
await expect(page.getByTestId("user-profile-login-email").locator("label")).not.toHaveText("");
await expect(page.getByTestId("user-profile-invoice-email").locator("label")).not.toHaveText("");
});
test("[PROFILE][User][Security] shows a neutral state when the invoice email is missing", async ({ page }) => {
await openUserProfileWithEmails(page, {
loginEmail: "k.sand@example.test",
invoiceEmail: null,
});
const invoiceEmail = page.getByTestId("user-profile-invoice-email");
await expect(emailValue(page, "user-profile-invoice-email")).toHaveValue(/e-conomic/i);
await expect(invoiceEmail.locator(".message.is-warning")).toHaveCount(0);
});
test("[PROFILE][User][Security] applies the confirmed login email without changing invoice email or refetching session", async ({
page,
}) => {
const profile = await openUserProfileWithEmails(page, {
loginEmail: "old.login@example.test",
invoiceEmail: "invoice@example.test",
});
const sessionRequestsBeforeSave = profile.getSessionRequestCount();
let changeEmailPayload: Record<string, unknown> | null = null;
await page.route("**/account/security/validate-password", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: { valid: true } }),
});
});
await page.route("**/account/security/change-email", async (route) => {
changeEmailPayload = route.request().postDataJSON();
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: { message: "Email changed", email: "k.sand@example.test" } }),
});
});
await completePasswordStep(page);
const popup = page.locator(".swal2-popup");
await popup.locator(".swal2-input").fill("K.Sand@example.test");
await popup.locator(".swal2-confirm").click();
await expect(emailValue(page, "user-profile-login-email")).toHaveValue("k.sand@example.test");
await expect(emailValue(page, "user-profile-invoice-email")).toHaveValue("invoice@example.test");
expect(changeEmailPayload).toEqual({ email: "K.Sand@example.test", password: "valid-password" });
expect(profile.getSessionRequestCount()).toBe(sessionRequestsBeforeSave);
});
test("[PROFILE][User][Security] falls back to the submitted email for an older success response", async ({ page }) => {
await openUserProfileWithEmails(page, {
loginEmail: "old.login@example.test",
invoiceEmail: "invoice@example.test",
});
await page.route("**/account/security/validate-password", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: { valid: true } }),
});
});
await page.route("**/account/security/change-email", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: { message: "Email changed" } }),
});
});
await completePasswordStep(page);
const popup = page.locator(".swal2-popup");
await popup.locator(".swal2-input").fill("fallback.address@example.test");
await popup.locator(".swal2-confirm").click();
await expect(emailValue(page, "user-profile-login-email")).toHaveValue("fallback.address@example.test");
await expect(emailValue(page, "user-profile-invoice-email")).toHaveValue("invoice@example.test");
});
test("[PROFILE][User][Security] leaves both emails unchanged when saving fails", async ({ page }) => {
await openUserProfileWithEmails(page, {
loginEmail: "old.login@example.test",
invoiceEmail: "invoice@example.test",
});
await page.route("**/account/security/validate-password", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: { valid: true } }),
});
});
await page.route("**/account/security/change-email", async (route) => {
await route.fulfill({
status: 422,
contentType: "application/json",
body: JSON.stringify({ message: "Email is already in use" }),
});
});
await completePasswordStep(page);
const popup = page.locator(".swal2-popup");
await popup.locator(".swal2-input").fill("duplicate@example.test");
await popup.locator(".swal2-confirm").click();
await expect(popup.locator(".swal2-validation-message")).toBeVisible();
await expect(emailValue(page, "user-profile-login-email")).toHaveValue("old.login@example.test");
await expect(emailValue(page, "user-profile-invoice-email")).toHaveValue("invoice@example.test");
});
test("[PROFILE][User][Security] opens the logout confirmation dialog", async ({ page }) => { test("[PROFILE][User][Security] opens the logout confirmation dialog", async ({ page }) => {
await openUserSecurity(page); await openUserSecurity(page);
+45
View File
@@ -183,6 +183,51 @@ describe("session bootstrap failure handling", () => {
expect(mocks.loginRedirect).toHaveBeenCalledTimes(1); expect(mocks.loginRedirect).toHaveBeenCalledTimes(1);
}); });
it("clears stale economic data when a refreshed session has no economic customer", async () => {
seedStoredSession("customer-session-token");
SessionUser.economicData.customerNumber.value = 12345679;
SessionUser.economicData.name.value = "Stale Customer";
SessionUser.economicData.address.value = "Old Street 1";
SessionUser.economicData.zip.value = "1000";
SessionUser.economicData.city.value = "Copenhagen";
SessionUser.economicData.mobilePhone.value = "12345678";
SessionUser.economicData.email.value = "stale.invoice@example.test";
SessionUser.economicData.cvr.value = "12345678";
SessionUser.economicData.currency.value = "DKK";
SessionUser.economicData.country.value = "DK";
SessionUser.economicData.cached_at.value = new Date();
mocks.authenticatedRequest.mockResolvedValueOnce({
data: {
data: {
id: 1,
customer_number: 12345679,
group_id: 1,
email: "login@example.test",
phone: { number: "12345678", country_code: 45 },
notifications: {},
display_name: "Customer",
permissions: ["user"],
economic_customer: null,
runtime_config: { economic: {}, release: {} },
},
},
});
await getSessionData();
expect(SessionUser.economicData.customerNumber.value).toBeNull();
expect(SessionUser.economicData.name.value).toBeNull();
expect(SessionUser.economicData.address.value).toBeNull();
expect(SessionUser.economicData.zip.value).toBeNull();
expect(SessionUser.economicData.city.value).toBeNull();
expect(SessionUser.economicData.mobilePhone.value).toBeNull();
expect(SessionUser.economicData.email.value).toBeNull();
expect(SessionUser.economicData.cvr.value).toBeNull();
expect(SessionUser.economicData.currency.value).toBeNull();
expect(SessionUser.economicData.country.value).toBeNull();
expect(SessionUser.economicData.cached_at.value).toBeNull();
});
it("clears an invalid stored session after ping confirms the API is reachable", async () => { it("clears an invalid stored session after ping confirms the API is reachable", async () => {
seedStoredSession("invalid-token"); seedStoredSession("invalid-token");
mocks.authenticatedRequest.mockRejectedValueOnce(invalidSessionError()); mocks.authenticatedRequest.mockRejectedValueOnce(invalidSessionError());