diff --git a/src/assets/locales/en.js b/src/assets/locales/en.js index 539cad3ac..e297cf506 100644 --- a/src/assets/locales/en.js +++ b/src/assets/locales/en.js @@ -281,8 +281,13 @@ export default { invalid: "OTP should be 6 digits long", }, submit: "Submit OTP", - resend: "Resend OTP", - resent: "OTP has been sent again", + resend: { + no_timer: "Resend OTP", + timer: { + 1: "Resend OTP in ", + 2: " secs", + }, + }, }, or: "OR", google: { diff --git a/src/assets/locales/hi.js b/src/assets/locales/hi.js index 802262709..d323ac03c 100644 --- a/src/assets/locales/hi.js +++ b/src/assets/locales/hi.js @@ -283,8 +283,13 @@ export default { invalid: "OTP 6 अंको का होना चाहिए", }, submit: "OTP जमा करें", - resend: "OTP दोबारा भेजो", - resent: "OTP फिर भेजा गया है", + resend: { + no_timer: "OTP दोबारा भेजो", + timer: { + 1: "OTP वापस भेजें", + 2: "सेकंड में", + }, + }, }, or: "या फिर", google: { diff --git a/src/components/UI/Text/InputNumber.vue b/src/components/UI/Text/InputNumber.vue index 74e3fe43e..69e6e8d11 100644 --- a/src/components/UI/Text/InputNumber.vue +++ b/src/components/UI/Text/InputNumber.vue @@ -29,7 +29,7 @@
@@ -161,14 +161,10 @@ :titleConfig="resendOTPTitleConfig" :buttonClass="resendOTPButtonClass" class="mt-2" - :isDisabled="submitOTPIconConfig.enabled" - v-if="requestedOtp && !resentOtp" + :isDisabled="isSubmitOTPInProgress || !isResendOTPEnabled" + v-if="requestedOtp" data-test="resendOTP" > - -

- {{ $t("login.otp.resent") }} -

@@ -216,7 +212,7 @@ export default { isRequestOtpEnabled() { if (!this.isRequestOtpEnabled) { this.requestedOtp = false; - this.resentOtp = false; + if (this.resendOTPTimer) clearInterval(this.otpTimerInterval); } }, }, @@ -224,8 +220,9 @@ export default { return { phoneInput: "", // phone input text otpInput: "", // otp input text + resendOTPTimer: 0, // the count of the timer + otpTimerInterval: 0, // to reset the otp timer requestedOtp: false, // whether the user has requested OTP once - resentOtp: false, // whether the user has requested to resend OTP invalidOtp: false, // whether the OTP is invalid toast: useToast(), warningIcon: require("@/assets/images/exclamation-circle-solid.svg"), @@ -278,6 +275,13 @@ export default { iconClass: "animate-spin h-4 object-scale-down text-white", }; }, + /** + * whether the button for resending OTP is enabled + */ + isResendOTPEnabled() { + if (!this.resendOTPTimer) return true; + return false; + }, routeParams() { // returns the params for where the user should be directed to if (this.redirectTo == "" || this.redirectTo == "/") { @@ -349,8 +353,16 @@ export default { }, resendOTPTitleConfig() { // title config for the resend OTP button + if (!this.isResendOTPEnabled) { + return { + value: + this.$t("login.otp.resend.timer.1") + + this.resendOTPTimer + + this.$t("login.otp.resend.timer.2"), + }; + } return { - value: this.$t("login.otp.resend"), + value: this.$t("login.otp.resend.no_timer"), }; }, resendOTPButtonClass() { @@ -395,16 +407,31 @@ export default { // whether the phone number entered by the user is valid return this.phoneInput.toString().match(/^([0]|\+91)?[6-9]\d{9}$/g) != null; }, + /** + * counts seconds before enabling resend OTP button + * + * @param {Number} seconds - the number of seconds for which the timer is to be run + */ + startResendOTPTimer(seconds = 60) { + this.resendOTPTimer = seconds; + this.otpTimerInterval = setInterval(() => { + this.resendOTPTimer--; + if (!this.resendOTPTimer) clearInterval(this.otpTimerInterval); + }, 1000); + }, requestOtp() { // requests OTP for the first time UserAPIService.requestOtp(this.formattedPhoneInput); this.requestedOtp = true; + this.startResendOTPTimer(); + this.otpInput = ""; this.invalidOtp = false; }, resendOtp() { // resends OTP on user request UserAPIService.requestOtp(this.formattedPhoneInput); - this.resentOtp = true; + this.startResendOTPTimer(); + this.otpInput = ""; this.invalidOtp = false; }, phoneLogin() { diff --git a/tests/unit/pages/Login.spec.js b/tests/unit/pages/Login.spec.js index 16ae19b88..6f39595fc 100644 --- a/tests/unit/pages/Login.spec.js +++ b/tests/unit/pages/Login.spec.js @@ -56,8 +56,8 @@ describe("Login.vue", () => { mountWrapper(); await setPhoneNumber(); await requestOTP(); - expect(requestOtp).toHaveBeenCalled(); + expect(wrapper.vm.otpInput).toBe(""); expect(wrapper.vm.invalidOtp).toBe(false); expect(wrapper.vm.requestedOtp).toBe(true); expect(wrapper.find('[data-test="otp"]').exists()).toBe(true); @@ -88,7 +88,6 @@ describe("Login.vue", () => { .setValue("919191919"); expect(wrapper.vm.requestedOtp).toBe(false); - expect(wrapper.vm.resentOtp).toBe(false); }); it("resends OTP upon requesting", async () => { @@ -96,21 +95,45 @@ describe("Login.vue", () => { const requestOtp = jest .spyOn(UserAPIService, "requestOtp") .mockImplementation(() => jest.fn()); - + const resendOtp = jest.spyOn(Login.methods, "resendOtp"); + const startResendOTPTimer = jest.spyOn( + Login.methods, + "startResendOTPTimer" + ); mountWrapper(); + await setPhoneNumber(); + jest.useFakeTimers(); await requestOTP(); + await jest.advanceTimersByTime(60000); + // reset timers + jest.useRealTimers(); // resend OTP await wrapper.find('[data-test="resendOTP"]').trigger("click"); - await flushPromises(); expect(requestOtp).toHaveBeenCalled(); - expect(wrapper.vm.resentOtp).toBe(true); + expect(resendOtp).toHaveBeenCalled(); + expect(startResendOTPTimer).toHaveBeenCalled(); + expect(wrapper.vm.otpInput).toBe(""); + expect(wrapper.vm.resendOTPTimer).toBe(60); + expect(wrapper.vm.isResendOTPEnabled).toBe(false); expect(wrapper.vm.invalidOtp).toBe(false); }); + it("enables ResendOTP button when timer ends", async () => { + // fake timer is needed for this test case to advance the time of interval + jest.useFakeTimers(); + const numseconds = 2; + wrapper.vm.startResendOTPTimer(numseconds); + jest.advanceTimersByTime(numseconds * 1000); + expect(wrapper.vm.isResendOTPEnabled).toBe(true); + expect(wrapper.vm.resendOTPTimer).toBe(0); + // reset timers + jest.useRealTimers(); + }); + describe("submit valid otp", () => { let verifyOtp; let getUserByAccessToken;