From 83ec772131b039a896a2bf7e554904f771031bdd Mon Sep 17 00:00:00 2001
From: "Ismail H. Ayaz"
Date: Mon, 20 May 2024 18:56:25 +0300
Subject: [PATCH] feat: resend timer for mfa & passwordless views that support
it
---
src/i18n/en.ts | 1 +
src/ui/components/ResendAction.vue | 56 +++++++++++++++++++++++++++++
src/ui/composables/use_resend.ts | 9 +++++
src/ui/utils/index.ts | 7 ++++
src/ui/utils/translator.ts | 17 +++++----
src/ui/views/VerifyEmail.vue | 16 ++-------
src/ui/views/mfa/Email.vue | 16 ++-------
src/ui/views/mfa/Push.vue | 17 ++-------
src/ui/views/mfa/SMS.vue | 13 ++-----
src/ui/views/passwordless/Email.vue | 16 ++-------
src/ui/views/passwordless/Push.vue | 17 ++-------
src/ui/views/passwordless/SMS.vue | 16 ++-------
12 files changed, 105 insertions(+), 96 deletions(-)
create mode 100644 src/ui/components/ResendAction.vue
create mode 100644 src/ui/composables/use_resend.ts
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 9f4a91b..f83022b 100755
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -24,6 +24,7 @@ export default {
reject: 'Reject',
resend: 'Resend',
resendText: 'Didn\'t receive the {type}?',
+ resendAfter: 'Resend in: {time}',
seconds: 'seconds',
show: 'Show',
submit: 'Submit',
diff --git a/src/ui/components/ResendAction.vue b/src/ui/components/ResendAction.vue
new file mode 100644
index 0000000..3344f6e
--- /dev/null
+++ b/src/ui/components/ResendAction.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+ {{ t('common.resendAfter', {time: formattedTimer}) }}
+
+
+
+
+
+
diff --git a/src/ui/composables/use_resend.ts b/src/ui/composables/use_resend.ts
new file mode 100644
index 0000000..fb251f4
--- /dev/null
+++ b/src/ui/composables/use_resend.ts
@@ -0,0 +1,9 @@
+import type { IPlusAuthContext } from '../interfaces';
+
+export function useResend(context: IPlusAuthContext){
+ const resendLink = `${ window.location.pathname }/resend`
+ const resendAfter = context.details.resend_after
+ return {
+ resendLink
+ }
+}
diff --git a/src/ui/utils/index.ts b/src/ui/utils/index.ts
index a088c21..d593249 100755
--- a/src/ui/utils/index.ts
+++ b/src/ui/utils/index.ts
@@ -156,3 +156,10 @@ export function setColorStyle(props: { color?: string, textColor?: string } ){
...props.textColor ? { color: resolveColor(props.textColor) } : {},
}
}
+
+export function secondsToTime(e: number){
+ const m = Math.floor(e % 3600 / 60).toString().padStart(2,'0'),
+ s = Math.floor(e % 60).toString().padStart(2,'0');
+
+ return `${m}:${s}`;
+}
diff --git a/src/ui/utils/translator.ts b/src/ui/utils/translator.ts
index 2d317c7..634b9fa 100755
--- a/src/ui/utils/translator.ts
+++ b/src/ui/utils/translator.ts
@@ -1,4 +1,5 @@
import type { Ref } from 'vue';
+import { getCurrentInstance } from 'vue';
import { ref } from 'vue';
import { escapeRegExp, isObject, keysToDotNotation, propertyAccessor } from '.';
@@ -37,6 +38,10 @@ export class Translator {
locale?: string
} = {}
) {
+ const vm = this instanceof Translator
+ ? this
+ : getCurrentInstance()?.appContext?.config?.globalProperties.$i18n;
+
if (!opts.fallback && (params instanceof Error || typeof params === 'string')) {
opts.fallback = params['error_description']
|| params['error_details']
@@ -45,18 +50,18 @@ export class Translator {
|| params
}
const locale = opts.locale || this.locale
- const value = propertyAccessor(this.dictionary[locale], key)
- || propertyAccessor(this.dictionary[this.fallBackLocale], key);
+ const value = propertyAccessor(vm.dictionary[locale], key)
+ || propertyAccessor(vm.dictionary[vm.fallBackLocale], key);
if (value) {
- return this._interpolate(
+ return vm._interpolate(
value,
params,
locale
)
} else if (opts.fallback) {
- return this._interpolate(
- propertyAccessor(this.dictionary[locale], opts.fallback)
- || propertyAccessor(this.dictionary[this.fallBackLocale], opts.fallback)
+ return vm._interpolate(
+ propertyAccessor(vm.dictionary[locale], opts.fallback)
+ || propertyAccessor(vm.dictionary[vm.fallBackLocale], opts.fallback)
|| opts.fallback,
params,
locale
diff --git a/src/ui/views/VerifyEmail.vue b/src/ui/views/VerifyEmail.vue
index 699b73d..c7afa20 100755
--- a/src/ui/views/VerifyEmail.vue
+++ b/src/ui/views/VerifyEmail.vue
@@ -11,15 +11,7 @@
v-if="!error && (context.prompt?.mode === 'check' || !context.details.email_verified)"
#content-footer
>
-
-
-
+
{
if (
context.prompt?.mode
@@ -80,7 +71,6 @@ export default defineComponent({
actionCompleted,
error,
loginUrl,
- resendLink,
resolveClientLogo: resolveLogo
}
},
diff --git a/src/ui/views/mfa/Email.vue b/src/ui/views/mfa/Email.vue
index 2160faf..dc5db0f 100755
--- a/src/ui/views/mfa/Email.vue
+++ b/src/ui/views/mfa/Email.vue
@@ -28,15 +28,7 @@
href="/signin/challenge"
/>
-
-
-
+
@@ -45,6 +37,7 @@
import { defineComponent } from 'vue';
import GenericForm from '../../components/GenericForm.vue';
+import ResendAction from '../../components/ResendAction.vue';
import WidgetLayout from '../../components/WidgetLayout.vue';
import { useContext, useHttp } from '../../composables';
import type { AdditionalFields } from '../../interfaces';
@@ -53,13 +46,11 @@ import { useGenericForm } from '../../utils/form_generics';
export default defineComponent({
name: 'Email',
- components: { WidgetLayout, GenericForm },
+ components: { ResendAction, WidgetLayout, GenericForm },
setup(){
const http = useHttp()
const context = useContext()
- const resendLink = `${ window.location.pathname }/resend`
-
const defaultFields: AdditionalFields = {
code: {
type: 'text',
@@ -76,7 +67,6 @@ export default defineComponent({
}
)
return {
- resendLink,
loading,
fields,
form,
diff --git a/src/ui/views/mfa/Push.vue b/src/ui/views/mfa/Push.vue
index ffad536..136d867 100755
--- a/src/ui/views/mfa/Push.vue
+++ b/src/ui/views/mfa/Push.vue
@@ -75,16 +75,7 @@
@click="switchToCode"
/>
-
-
-
-
+
@@ -94,6 +85,7 @@ import { computed, defineComponent, nextTick, ref, watch } from 'vue';
import GenericForm from '../../components/GenericForm.vue';
import PSpinner from '../../components/PSpinner/PSpinner';
+import ResendAction from '../../components/ResendAction.vue';
import WidgetLayout from '../../components/WidgetLayout.vue';
import { useContext, useHttp } from '../../composables';
import type { AdditionalFields } from '../../interfaces';
@@ -101,14 +93,12 @@ import { useGenericForm } from '../../utils/form_generics';
export default defineComponent({
name: 'Push',
- components: { WidgetLayout, PSpinner, GenericForm },
+ components: { ResendAction, WidgetLayout, PSpinner, GenericForm },
setup() {
const http = useHttp()
const context = useContext()
- const resendLink = `${window.location.pathname}/resend`
-
const urlParams = new URLSearchParams(window.location.search)
const manualMode = computed(() => urlParams.get('useQuery') === 'true')
@@ -175,7 +165,6 @@ export default defineComponent({
}, { immediate: true, flush: 'post' })
return {
fields,
- resendLink,
validate,
isRegistration,
manualMode,
diff --git a/src/ui/views/mfa/SMS.vue b/src/ui/views/mfa/SMS.vue
index 34d2423..cc2073b 100755
--- a/src/ui/views/mfa/SMS.vue
+++ b/src/ui/views/mfa/SMS.vue
@@ -33,15 +33,7 @@
href="/signin/challenge"
/>
-
-
-
+
@@ -51,6 +43,7 @@ import { defineComponent } from 'vue';
import GenericForm from '../../components/GenericForm.vue';
import PTimer from '../../components/PTimer/PTimer';
+import ResendAction from '../../components/ResendAction.vue';
import WidgetLayout from '../../components/WidgetLayout.vue';
import { useContext, useHttp } from '../../composables';
import type { AdditionalFields } from '../../interfaces';
@@ -58,7 +51,7 @@ import { useGenericForm } from '../../utils/form_generics';
export default defineComponent({
name: 'SMS',
- components: { WidgetLayout, PTimer, GenericForm },
+ components: { ResendAction, WidgetLayout, PTimer, GenericForm },
setup(){
const http = useHttp()
diff --git a/src/ui/views/passwordless/Email.vue b/src/ui/views/passwordless/Email.vue
index 008f9d6..903a3b2 100755
--- a/src/ui/views/passwordless/Email.vue
+++ b/src/ui/views/passwordless/Email.vue
@@ -31,15 +31,7 @@
-
-
-
+
@@ -48,6 +40,7 @@
import { computed, defineComponent } from 'vue';
import GenericForm from '../../components/GenericForm.vue';
+import ResendAction from '../../components/ResendAction.vue';
import WidgetLayout from '../../components/WidgetLayout.vue';
import { useContext, useHttp, useLocale } from '../../composables';
import type { AdditionalFields } from '../../interfaces';
@@ -56,14 +49,12 @@ import { useGenericForm } from '../../utils/form_generics';
export default defineComponent({
name: 'Email',
- components: { WidgetLayout, GenericForm },
+ components: { ResendAction, WidgetLayout, GenericForm },
setup(){
const http = useHttp()
const context = useContext()
const i18n = useLocale()
- const resendLink = `${ window.location.pathname }/resend`
-
const isMagicLink = context.prompt?.mode === 'check_email'
const defaultFields: AdditionalFields = {
email: {
@@ -114,7 +105,6 @@ export default defineComponent({
fields,
form,
context,
- resendLink,
validate,
submit
}
diff --git a/src/ui/views/passwordless/Push.vue b/src/ui/views/passwordless/Push.vue
index e8356f5..4755359 100755
--- a/src/ui/views/passwordless/Push.vue
+++ b/src/ui/views/passwordless/Push.vue
@@ -67,16 +67,7 @@
@click="switchToCode"
/>
-
-
-
-
+
@@ -86,6 +77,7 @@ import { computed, defineComponent, nextTick, ref, watch } from 'vue';
import GenericForm from '../../components/GenericForm.vue';
import PSpinner from '../../components/PSpinner/PSpinner';
+import ResendAction from '../../components/ResendAction.vue';
import WidgetLayout from '../../components/WidgetLayout.vue';
import { useContext, useHttp, useLocale } from '../../composables';
import type { AdditionalFields } from '../../interfaces';
@@ -93,14 +85,12 @@ import { useGenericForm } from '../../utils/form_generics';
export default defineComponent({
name: 'Push',
- components: { WidgetLayout, PSpinner, GenericForm },
+ components: { ResendAction, WidgetLayout, PSpinner, GenericForm },
setup(){
const http = useHttp()
const context = useContext()
const i18n = useLocale()
- const resendLink = `${window.location.pathname }/resend`
-
const urlParams = new URLSearchParams(window.location.search)
const manualMode = computed(() => urlParams.get('useQuery') === 'true')
@@ -186,7 +176,6 @@ export default defineComponent({
}, { immediate: true, flush: 'post' } )
return {
fields,
- resendLink,
validate,
isRegistration,
manualMode,
diff --git a/src/ui/views/passwordless/SMS.vue b/src/ui/views/passwordless/SMS.vue
index 9d4258a..416fa24 100755
--- a/src/ui/views/passwordless/SMS.vue
+++ b/src/ui/views/passwordless/SMS.vue
@@ -27,15 +27,7 @@
-
-
-
+
@@ -45,6 +37,7 @@ import { defineComponent } from 'vue';
import GenericForm from '../../components/GenericForm.vue';
import PTimer from '../../components/PTimer/PTimer';
+import ResendAction from '../../components/ResendAction.vue';
import WidgetLayout from '../../components/WidgetLayout.vue';
import { useContext, useHttp, useLocale } from '../../composables';
import type { AdditionalFields } from '../../interfaces';
@@ -52,14 +45,12 @@ import { useGenericForm } from '../../utils/form_generics';
export default defineComponent({
name: 'SMS',
- components: { WidgetLayout, PTimer, GenericForm },
+ components: { ResendAction, WidgetLayout, PTimer, GenericForm },
setup(){
const http = useHttp()
const context = useContext()
const i18n = useLocale()
- const resendLink = `${ window.location.pathname }/resend`
-
const defaultFields: AdditionalFields = {
phone_number: {
type: 'text',
@@ -93,7 +84,6 @@ export default defineComponent({
}
)
return {
- resendLink,
loading,
fields,
form,