diff --git a/packages/template-retail-react-app/app/components/otp-auth/index.jsx b/packages/template-retail-react-app/app/components/otp-auth/index.jsx
index 886c83ce52..0e36aceda2 100644
--- a/packages/template-retail-react-app/app/components/otp-auth/index.jsx
+++ b/packages/template-retail-react-app/app/components/otp-auth/index.jsx
@@ -12,6 +12,7 @@ import {
Button,
Input,
SimpleGrid,
+ Spinner,
Stack,
Text,
HStack,
@@ -271,6 +272,17 @@ const OtpAuth = ({
))}
+ {/* Loading spinner during verification */}
+ {isVerifying && (
+
+ )}
+
{/* Error message */}
{error && (
diff --git a/packages/template-retail-react-app/app/components/otp-auth/index.test.js b/packages/template-retail-react-app/app/components/otp-auth/index.test.js
index 06e2a2d049..d238c89e33 100644
--- a/packages/template-retail-react-app/app/components/otp-auth/index.test.js
+++ b/packages/template-retail-react-app/app/components/otp-auth/index.test.js
@@ -246,6 +246,41 @@ describe('OtpAuth', () => {
await user.type(otpInputs[7], '8')
expect(otpInputs[7]).toHaveFocus()
})
+
+ test('shows spinner while OTP is being verified', async () => {
+ const deferred = {}
+ const verifyingPromise = new Promise((resolve) => {
+ deferred.resolve = resolve
+ })
+ const mockVerify = jest.fn().mockReturnValue(verifyingPromise)
+
+ const user = userEvent.setup()
+ renderWithProviders(
+
+ )
+
+ expect(screen.queryByTestId('otp-verifying-spinner')).not.toBeInTheDocument()
+
+ const otpInputs = screen.getAllByRole('textbox')
+ fireEvent.paste(otpInputs[0], {
+ clipboardData: {getData: () => '12345678'}
+ })
+
+ await waitFor(() => {
+ expect(screen.getByTestId('otp-verifying-spinner')).toBeInTheDocument()
+ })
+
+ deferred.resolve({success: true})
+ await waitFor(() => {
+ expect(screen.queryByTestId('otp-verifying-spinner')).not.toBeInTheDocument()
+ })
+ })
})
describe('Keyboard Navigation', () => {