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 @@ + + + + + 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 > -

- -

+ @@ -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,