Skip to content

Commit

Permalink
Merge pull request #604 from avantifellows/otp_resend_time
Browse files Browse the repository at this point in the history
Adds support for OTP resend + fixes icon alignment
  • Loading branch information
dalmia authored Feb 9, 2022
2 parents 6f9015b + 0f40376 commit 1ad35ff
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 21 deletions.
9 changes: 7 additions & 2 deletions src/assets/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
9 changes: 7 additions & 2 deletions src/assets/locales/hi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/UI/Text/InputNumber.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<!-- start icon -->
<div
v-if="isStartIconEnabled"
class="z-10 absolute font-xl text-blueGray-300 bg-transparent rounded text-base items-center w-5 inset-y-1/4 left-1.5"
class="z-10 absolute bg-transparent rounded place-self-center w-5 left-1.5"
@click="startIconSelected"
:class="startIconClass"
data-test="startIcon"
Expand Down
49 changes: 38 additions & 11 deletions src/pages/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
:iconConfig="submitOTPIconConfig"
:buttonClass="submitOTPButtonClass"
v-if="requestedOtp"
:disabled="!isSubmitOTPEnabled || submitOTPIconConfig.enabled"
:disabled="!isSubmitOTPEnabled || isSubmitOTPInProgress"
data-test="submitOTP"
></icon-button>
<!-- button to request resending OTP -->
Expand All @@ -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"
></icon-button>
<!-- text to show when OTP has been resent -->
<p v-if="resentOtp" class="text-center mt-2">
{{ $t("login.otp.resent") }}
</p>

<!-- terms and service declaration message -->
<div class="p-2 rounded flex items-start max-w-xl mx-auto mt-4 space-x-1">
Expand Down Expand Up @@ -216,16 +212,17 @@ export default {
isRequestOtpEnabled() {
if (!this.isRequestOtpEnabled) {
this.requestedOtp = false;
this.resentOtp = false;
if (this.resendOTPTimer) clearInterval(this.otpTimerInterval);
}
},
},
data() {
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"),
Expand Down Expand Up @@ -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 == "/") {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down
33 changes: 28 additions & 5 deletions tests/unit/pages/Login.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -88,29 +88,52 @@ describe("Login.vue", () => {
.setValue("919191919");

expect(wrapper.vm.requestedOtp).toBe(false);
expect(wrapper.vm.resentOtp).toBe(false);
});

it("resends OTP upon requesting", async () => {
// mock function to verify OTP
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;
Expand Down

0 comments on commit 1ad35ff

Please sign in to comment.