From b10aa7424b108259a4010617ac439e41dfa2283e Mon Sep 17 00:00:00 2001
From: Birte Driehaus <47067241+MorennMcFly@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:54:14 +0200
Subject: [PATCH] Feature/bus 928 password reset (#56)
* [BUS-928] reset password flow
* [BUS-928] fix problem resulting from merge conflict
* [BUS-928] code review changes
---
components/Account/AccountLogin.vue | 24 ++++++-
.../Account/AccountLoginRegisterTabs.vue | 9 ++-
components/Account/AccountRecoverPassword.vue | 71 +++++++++++++++++++
components/Account/AccountResetPassword.vue | 62 ++++++++++++++++
.../Layout/Header/LayoutHeaderAccount.vue | 8 ++-
composables/usePasswordRecovery.ts | 29 ++++++++
composables/useStaticBreadcrumbs.ts | 17 ++++-
i18n/de-DE/account.json | 22 +++++-
i18n/de-DE/composable.json | 3 +-
i18n/en-GB/account.json | 21 +++++-
i18n/en-GB/composable.json | 3 +-
pages/account/recover/index.vue | 13 ++++
pages/account/recover/password.vue | 13 ++++
stores/CustomerStore.ts | 5 ++
14 files changed, 289 insertions(+), 11 deletions(-)
create mode 100644 components/Account/AccountRecoverPassword.vue
create mode 100644 components/Account/AccountResetPassword.vue
create mode 100644 composables/usePasswordRecovery.ts
create mode 100644 pages/account/recover/index.vue
create mode 100644 pages/account/recover/password.vue
diff --git a/components/Account/AccountLogin.vue b/components/Account/AccountLogin.vue
index 8399df6b..7ebca0da 100644
--- a/components/Account/AccountLogin.vue
+++ b/components/Account/AccountLogin.vue
@@ -12,14 +12,18 @@ const props = withDefaults(
redirectAfterSuccess?: boolean;
redirectTarget?: string;
showCreateLink?: boolean;
+ showRecoverLink?: boolean;
}>(),
{
redirectAfterSuccess: false,
redirectTarget: '/account',
showCreateLink: true,
+ showRecoverLink: true,
},
);
+defineEmits(['closeModal']);
+
const customerStore = useCustomerStore();
const { togglePasswordVisibility } = useFormkitHelper();
const { resolveApiErrors } = useApiErrorsResolver();
@@ -91,11 +95,25 @@ const handleLogin = async (fields: FormkitLoginFields) => {
suffix-icon="lock"
@suffix-icon-click="togglePasswordVisibility"
/>
+
+
{{ $t('account.login.createAccount') }}
-
+ {{ $t('account.login.createAccountLink') }}
+
+
+
+ {{ $t('account.login.recoverPasswordLink') }}
+
+
diff --git a/components/Account/AccountLoginRegisterTabs.vue b/components/Account/AccountLoginRegisterTabs.vue
index 62b364b1..98d33b2f 100644
--- a/components/Account/AccountLoginRegisterTabs.vue
+++ b/components/Account/AccountLoginRegisterTabs.vue
@@ -1,3 +1,7 @@
+
+
-
+
+import type { FormkitFields } from '~/types/formkit';
+
+const customerStore = useCustomerStore();
+const { query } = useRoute();
+const { t } = useI18n();
+const { pushSuccess, pushError } = useNotifications();
+const hashIsValid = ref(true);
+
+onMounted(async () => {
+ try {
+ const response = await customerStore.isRecoveryHashValid({ hash: query.hash });
+ hashIsValid.value = !response.isExpired;
+ } catch (error) {
+ hashIsValid.value = false;
+ }
+});
+
+const handlePasswordChange = async (fields: FormkitFields) => {
+ try {
+ await customerStore.recoverPassword({
+ hash: query.hash,
+ ...fields,
+ });
+ pushSuccess(t('account.recoverPassword.recover.successMessage'));
+ navigateTo('/account/login');
+ } catch (error) {
+ pushError(t('account.recoverPassword.recover.errorMessage'));
+ }
+};
+
+
+
+
+ {{ $t('account.recoverPassword.recover.linkNotValid') }}
+
+ {{ $t('account.recoverPassword.recover.requestNewLink') }}
+
+
+
+
+ {{ $t('account.recoverPassword.recover.heading') }}
+
+
+
+
+
diff --git a/components/Account/AccountResetPassword.vue b/components/Account/AccountResetPassword.vue
new file mode 100644
index 00000000..ed94e04f
--- /dev/null
+++ b/components/Account/AccountResetPassword.vue
@@ -0,0 +1,62 @@
+
+
+
+ {{ $t('account.recoverPassword.request.heading') }}
+
+ {{ $t('account.recoverPassword.request.subHeading') }}
+
+
+
+
+
+ {{ $t('account.recoverPassword.request.successMessage') }}
+
+
+ {{ $t('account.recoverPassword.request.loginButtonLabel') }}
+
+
+
diff --git a/components/Layout/Header/LayoutHeaderAccount.vue b/components/Layout/Header/LayoutHeaderAccount.vue
index 092274ad..3a56142d 100644
--- a/components/Layout/Header/LayoutHeaderAccount.vue
+++ b/components/Layout/Header/LayoutHeaderAccount.vue
@@ -2,11 +2,16 @@
const customerStore = useCustomerStore();
const { signedIn } = storeToRefs(customerStore);
const modalController = useModal();
+
+const closeModal = () => {
+ modalController.close();
+};
@@ -17,7 +22,7 @@ const modalController = useModal();
{{ $t('account.loginModal.heading') }}
-
+
@@ -25,7 +30,6 @@ const modalController = useModal();
diff --git a/composables/usePasswordRecovery.ts b/composables/usePasswordRecovery.ts
new file mode 100644
index 00000000..d5d44da2
--- /dev/null
+++ b/composables/usePasswordRecovery.ts
@@ -0,0 +1,29 @@
+import type { operations } from '@shopware/api-client/api-types';
+
+export function usePasswordRecovery() {
+ const { apiClient } = useShopwareContext();
+
+ async function recoverPassword(
+ recoverPasswordData: operations['recoveryPassword post /account/recovery-password-confirm']['body'],
+ ) {
+ const response = await apiClient.invoke('recoveryPassword post /account/recovery-password-confirm', {
+ body: recoverPasswordData,
+ });
+ return response.data;
+ }
+
+ async function isRecoveryHashValid(recoverHash: { hash: string }) {
+ const response = await apiClient.invoke(
+ 'getCustomerRecoveryIsExpired post /account/customer-recovery-is-expired',
+ {
+ body: recoverHash,
+ },
+ );
+ return response.data;
+ }
+
+ return {
+ isRecoveryHashValid,
+ recoverPassword,
+ };
+}
diff --git a/composables/useStaticBreadcrumbs.ts b/composables/useStaticBreadcrumbs.ts
index 47ede286..98aad634 100644
--- a/composables/useStaticBreadcrumbs.ts
+++ b/composables/useStaticBreadcrumbs.ts
@@ -37,11 +37,26 @@ export function useStaticBreadcrumbs() {
name: t('composable.breadcrumbs.loginName'),
path: '/account/login',
});
- } else if (type === 'register') {
+
+ return breadcrumbs;
+ }
+
+ if (type === 'register') {
breadcrumbs.push({
name: t('composable.breadcrumbs.registerName'),
path: '/account/register',
});
+
+ return breadcrumbs;
+ }
+
+ if (type === 'recover') {
+ breadcrumbs.push({
+ name: t('composable.breadcrumbs.recoverName'),
+ path: '/account/recover',
+ });
+
+ return breadcrumbs;
}
return breadcrumbs;
diff --git a/i18n/de-DE/account.json b/i18n/de-DE/account.json
index a242cf98..95eeaf71 100644
--- a/i18n/de-DE/account.json
+++ b/i18n/de-DE/account.json
@@ -53,7 +53,8 @@
"password": {
"label": "Passwort"
},
- "createAccountLink": "Erstelle hier deinen Account"
+ "createAccountLink": "Erstelle hier deinen Account",
+ "recoverPasswordLink": "Passwort vergessen?"
},
"register": {
"submitLabel": "Registrieren",
@@ -105,6 +106,25 @@
"logout": {
"buttonLabel": "Logout"
},
+ "recoverPassword": {
+ "request": {
+ "heading": "Passwort wiederherstellen",
+ "subHeading": "Wir schicken dir eine Email zu. Klicke auf den Link in der Email, um ein neues Passwort zu vergeben.",
+ "submitLabel": "Email anfordern",
+ "successMessage": "Falls deine Emailadresse bei uns registriert ist, haben wir dir eine Email geschickt. Bitte schau in dein Postfach.",
+ "loginButtonLabel": "Zum Login"
+ },
+ "recover": {
+ "heading": "Lege ein neues Passwort fest",
+ "submitLabel": "Passwort ändern",
+ "errorMessage": "Etwas ist beim Ändern deines Passworts schiefgegangen.",
+ "successMessage": "Dein Passwort wurde erfolgreich geändert.",
+ "passwordLabel": "Neues Passwort",
+ "confirmLabel": "Neues Passwort bestätigen",
+ "linkNotValid": "Dieser Link scheint nicht gültig zu sein. Bitte fordere einen neuen an.",
+ "requestNewLink": "Neuen Link anfordern"
+ }
+ },
"loginModal": {
"heading": "Login",
"tabs": {
diff --git a/i18n/de-DE/composable.json b/i18n/de-DE/composable.json
index 5c5629f4..2d67034a 100644
--- a/i18n/de-DE/composable.json
+++ b/i18n/de-DE/composable.json
@@ -12,7 +12,8 @@
"orderName": "Bestellung",
"accountName": "Konto",
"loginName": "Login",
- "registerName": "Registrieren"
+ "registerName": "Registrieren",
+ "recoverName": "Passwort wiederherstellen"
}
}
}
\ No newline at end of file
diff --git a/i18n/en-GB/account.json b/i18n/en-GB/account.json
index bea44a7a..cbf7c3dd 100644
--- a/i18n/en-GB/account.json
+++ b/i18n/en-GB/account.json
@@ -53,7 +53,8 @@
"password": {
"label": "Password"
},
- "createAccountLink": "Create your account here"
+ "createAccountLink": "Create your account here",
+ "recoverPasswordLink": "Forgot password?"
},
"register": {
"submitLabel": "Register",
@@ -105,6 +106,24 @@
"logout": {
"buttonLabel": "Logout"
},
+ "recoverPassword": {
+ "request": {
+ "heading": "Password Recovery",
+ "subHeading": "We will send you an email. Click the link in that email in order to change your password.",
+ "submitLabel": "Request Email",
+ "successMessage": "If your email address is registered with us, we sent you an email. Please check your inbox.",
+ "loginButtonLabel": "Login"
+ },
+ "recover": {
+ "heading": "Choose a new password",
+ "submitLabel": "Change password",
+ "errorMessage": "Something went wrong changing your password.",
+ "passwordLabel": "New password",
+ "confirmLabel": "Confirm new password",
+ "linkNotValid": "This link doesn't seem to be valid. Please request another.",
+ "requestNewLink": "Request a new recovery link"
+ }
+ },
"loginModal": {
"heading": "Login",
"tabs": {
diff --git a/i18n/en-GB/composable.json b/i18n/en-GB/composable.json
index 5ed89466..56b72d46 100644
--- a/i18n/en-GB/composable.json
+++ b/i18n/en-GB/composable.json
@@ -12,7 +12,8 @@
"orderName": "Order",
"accountName": "Account",
"loginName": "Login",
- "registerName": "Register"
+ "registerName": "Register",
+ "recoverName": "Recover password"
}
}
}
\ No newline at end of file
diff --git a/pages/account/recover/index.vue b/pages/account/recover/index.vue
new file mode 100644
index 00000000..59278bb4
--- /dev/null
+++ b/pages/account/recover/index.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/pages/account/recover/password.vue b/pages/account/recover/password.vue
new file mode 100644
index 00000000..d4145dc1
--- /dev/null
+++ b/pages/account/recover/password.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/stores/CustomerStore.ts b/stores/CustomerStore.ts
index 3e64d82d..4e2dcee1 100644
--- a/stores/CustomerStore.ts
+++ b/stores/CustomerStore.ts
@@ -3,6 +3,8 @@ import type { Schemas } from '@shopware/api-client/api-types';
export const useCustomerStore = defineStore('customer', () => {
const { refreshSessionContext, sessionContext } = useSessionContext();
const { login, logout, register } = useUser();
+ const { resetPassword } = useCustomerPassword();
+ const { isRecoveryHashValid, recoverPassword } = usePasswordRecovery();
const loading = ref(true);
async function refreshContext() {
@@ -22,5 +24,8 @@ export const useCustomerStore = defineStore('customer', () => {
logout,
loading,
register,
+ resetPassword,
+ isRecoveryHashValid,
+ recoverPassword,
};
});