Skip to content

Commit

Permalink
feat: resend timer for mfa & passwordless views that support it
Browse files Browse the repository at this point in the history
  • Loading branch information
ayZagen committed May 20, 2024
1 parent 8a1f6ee commit 83ec772
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 96 deletions.
1 change: 1 addition & 0 deletions src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
56 changes: 56 additions & 0 deletions src/ui/components/ResendAction.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script setup lang="ts">
import {computed} from 'vue';
import {useContext, useLocale} from '../composables';
import {useTimer} from '../composables/use_timer.ts';
import {secondsToTime} from '../utils';
withDefaults(defineProps<{
type?: string
}>(), {
type: 'common.code'
})
const context = useContext()
const {t} = useLocale()
const resendLink = `${window.location.pathname}/resend`
const resendAfter = context.details.resend_after as number
const {countdown} = useTimer(Math.floor(resendAfter / 1000) || 0)
const formattedTimer = computed(() => {
return secondsToTime(countdown.value)
})
</script>

<template>
<div class="pa__resend-action">
<span
v-t="['common.resendText', { type }]"
/>
<span
v-if="countdown > 0"
>
{{ t('common.resendAfter', {time: formattedTimer}) }}
</span>
<a
v-else
v-t="'common.resend'"
:href="resendLink"
/>
</div>
</template>

<style>
.widget {
.resend-action {
@apply text-center;
span:first-of-type {
@apply pr-2
}
span + span {
@apply opacity-75
}
}
}
</style>
9 changes: 9 additions & 0 deletions src/ui/composables/use_resend.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
7 changes: 7 additions & 0 deletions src/ui/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
}
17 changes: 11 additions & 6 deletions src/ui/utils/translator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Ref } from 'vue';
import { getCurrentInstance } from 'vue';
import { ref } from 'vue';

import { escapeRegExp, isObject, keysToDotNotation, propertyAccessor } from '.';
Expand Down Expand Up @@ -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']
Expand All @@ -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
Expand Down
16 changes: 3 additions & 13 deletions src/ui/views/VerifyEmail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,7 @@
v-if="!error && (context.prompt?.mode === 'check' || !context.details.email_verified)"
#content-footer
>
<p>
<span
v-t="[ 'common.resendText', { type: 'common.email' } ]"
style="padding-right: 4px"
/><a
v-t="'common.resend'"
:href="resendLink"
/>
</p>
<ResendAction type="common.email" />
</template>
<template
v-else-if="!error"
Expand All @@ -38,13 +30,14 @@ import {
defineComponent, ref, onMounted
} from 'vue';
import ResendAction from '../components/ResendAction.vue';
import WidgetLayout from '../components/WidgetLayout.vue';
import { useContext } from '../composables';
import { resolveLogo } from '../utils';
export default defineComponent({
name: 'VerifyEmail',
components: { WidgetLayout },
components: { ResendAction, WidgetLayout },
setup() {
const context = useContext()
Expand All @@ -55,8 +48,6 @@ export default defineComponent({
const error = context.error?.error
let loginUrl = context.settings.auto_sign_in && context.settings.tenant_login_url
const resendLink = `${window.location.pathname}/resend`
onMounted(() => {
if (
context.prompt?.mode
Expand All @@ -80,7 +71,6 @@ export default defineComponent({
actionCompleted,
error,
loginUrl,
resendLink,
resolveClientLogo: resolveLogo
}
},
Expand Down
16 changes: 3 additions & 13 deletions src/ui/views/mfa/Email.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,7 @@
href="/signin/challenge"
/>
</p>
<p>
<span
v-t="['common.resendText', {type: 'common.email'}]"
style="padding-right: 4px"
/><a
v-t="'common.resend'"
:href="resendLink"
/>
</p>
<ResendAction type="common.email" />
</template>
</WidgetLayout>
</template>
Expand All @@ -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';
Expand All @@ -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',
Expand All @@ -76,7 +67,6 @@ export default defineComponent({
}
)
return {
resendLink,
loading,
fields,
form,
Expand Down
17 changes: 3 additions & 14 deletions src/ui/views/mfa/Push.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,7 @@
@click="switchToCode"
/>
</p>
<p>
<span
v-t="['common.resendText', { type: 'common.notification'}]"
style="padding-right: 4px"
/>
<a
v-t="'common.resend'"
:href="resendLink"
/>
</p>
<ResendAction type="common.notification" />
</template>
</WidgetLayout>
</template>
Expand All @@ -94,21 +85,20 @@ 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';
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')
Expand Down Expand Up @@ -175,7 +165,6 @@ export default defineComponent({
}, { immediate: true, flush: 'post' })
return {
fields,
resendLink,
validate,
isRegistration,
manualMode,
Expand Down
13 changes: 3 additions & 10 deletions src/ui/views/mfa/SMS.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,7 @@
href="/signin/challenge"
/>
</p>
<p>
<span
v-t="['common.resendText', {type: 'common.code'}]"
style="padding-right: 4px"
/><a
v-t="'common.resend'"
:href="resendLink"
/>
</p>
<ResendAction />
</template>
</WidgetLayout>
</template>
Expand All @@ -51,14 +43,15 @@ 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';
import { useGenericForm } from '../../utils/form_generics';
export default defineComponent({
name: 'SMS',
components: { WidgetLayout, PTimer, GenericForm },
components: { ResendAction, WidgetLayout, PTimer, GenericForm },
setup(){
const http = useHttp()
Expand Down
16 changes: 3 additions & 13 deletions src/ui/views/passwordless/Email.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,7 @@
</template>

<template #content-footer>
<p>
<span
v-t="[ 'common.resendText', { type: 'common.email' } ]"
style="padding-right: 4px"
/><a
v-t="'common.resend'"
:href="resendLink"
/>
</p>
<ResendAction type="common.email" />
</template>
</WidgetLayout>
</template>
Expand All @@ -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';
Expand All @@ -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: {
Expand Down Expand Up @@ -114,7 +105,6 @@ export default defineComponent({
fields,
form,
context,
resendLink,
validate,
submit
}
Expand Down
Loading

0 comments on commit 83ec772

Please sign in to comment.