@pagamio/frontend-commons-lib 0.8.304 → 0.8.305

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,6 +64,24 @@ export interface PhonePinLoginConfig {
64
64
  /** Optional label for the password tab (default: 'Email & Password') */
65
65
  passwordTabLabel?: string;
66
66
  }
67
+ /**
68
+ * Configuration for the username + PIN login tab.
69
+ * When provided, a "Login Number + PIN" tab is displayed alongside the other
70
+ * tabs. Login numbers are admin-assigned 10-digit numeric identifiers (there
71
+ * is no self-service signup for this method).
72
+ */
73
+ export interface UsernamePinLoginConfig {
74
+ /** Login with login number and PIN. Should throw on error. */
75
+ loginWithUsername: (loginNumber: string, pin: string) => Promise<any>;
76
+ /**
77
+ * Optional callback called instead of onLoginSuccess when mustChangePIN is true.
78
+ */
79
+ onMustChangePIN?: (loginNumber: string, response: any) => void;
80
+ /** Optional label for the username-PIN tab (default: 'Login Number + PIN') */
81
+ tabLabel?: string;
82
+ /** Optional label for the password tab (default: 'Email & Password') */
83
+ passwordTabLabel?: string;
84
+ }
67
85
  /**
68
86
  * Base login credentials interface that can be extended for specific implementations
69
87
  */
@@ -145,6 +163,11 @@ interface PagamioLoginPageProps<T extends CustomAuthConfig> extends BaseAuthPage
145
163
  * The tab handles the 2-step phone PIN flow internally.
146
164
  */
147
165
  phonePinConfig?: PhonePinLoginConfig;
166
+ /**
167
+ * When provided, adds a "Login Number + PIN" tab alongside the standard login
168
+ * form. The tab handles the 2-step login-number + PIN flow internally.
169
+ */
170
+ usernamePinConfig?: UsernamePinLoginConfig;
148
171
  }
149
172
  export interface LoginErrorProps {
150
173
  code: string;
@@ -166,6 +189,6 @@ export declare const loginPageDefaultText: {
166
189
  * Generic Login Page component
167
190
  * @template T - Authentication configuration type
168
191
  */
169
- export declare function PagamioLoginPage<T extends CustomAuthConfig>({ logo, text, appLabel, onForgotPassword, onLoginSuccess, onLoginError, hasCreateAccount, createAccountRoute, onCreateAccount, transformLoginData, authenticatorType, loginFieldType, customLoginFields, className, features, sideContentClass, footer, phoneOtpConfig, phonePinConfig, }: Readonly<PagamioLoginPageProps<T>>): import("react/jsx-runtime").JSX.Element;
192
+ export declare function PagamioLoginPage<T extends CustomAuthConfig>({ logo, text, appLabel, onForgotPassword, onLoginSuccess, onLoginError, hasCreateAccount, createAccountRoute, onCreateAccount, transformLoginData, authenticatorType, loginFieldType, customLoginFields, className, features, sideContentClass, footer, phoneOtpConfig, phonePinConfig, usernamePinConfig, }: Readonly<PagamioLoginPageProps<T>>): import("react/jsx-runtime").JSX.Element;
170
193
  export default PagamioLoginPage;
171
194
  export type { PagamioLoginCredentials, PagamioLoginPageProps };
@@ -47,7 +47,7 @@ export const loginPageDefaultText = {
47
47
  * Generic Login Page component
48
48
  * @template T - Authentication configuration type
49
49
  */
50
- export function PagamioLoginPage({ logo, text = loginPageDefaultText, appLabel, onForgotPassword, onLoginSuccess, onLoginError, hasCreateAccount = false, createAccountRoute, onCreateAccount, transformLoginData, authenticatorType, loginFieldType = 'username', customLoginFields, className = '', features, sideContentClass, footer, phoneOtpConfig, phonePinConfig, }) {
50
+ export function PagamioLoginPage({ logo, text = loginPageDefaultText, appLabel, onForgotPassword, onLoginSuccess, onLoginError, hasCreateAccount = false, createAccountRoute, onCreateAccount, transformLoginData, authenticatorType, loginFieldType = 'username', customLoginFields, className = '', features, sideContentClass, footer, phoneOtpConfig, phonePinConfig, usernamePinConfig, }) {
51
51
  const { login, error: authError } = useAuth();
52
52
  const { addToast } = useToast();
53
53
  const [isLoading, setIsLoading] = useState(false);
@@ -65,6 +65,11 @@ export function PagamioLoginPage({ logo, text = loginPageDefaultText, appLabel,
65
65
  const [canResendPhone, setCanResendPhone] = useState(true);
66
66
  const [phonePinValue, setPhonePinValue] = useState('');
67
67
  const [isLoggingInWithPin, setIsLoggingInWithPin] = useState(false);
68
+ const [usernameStep, setUsernameStep] = useState('enterLoginNumber');
69
+ const [loginNumber, setLoginNumber] = useState('');
70
+ const [loginNumberPinValue, setLoginNumberPinValue] = useState('');
71
+ const [isLoggingInWithUsername, setIsLoggingInWithUsername] = useState(false);
72
+ const [usernameError, setUsernameError] = useState(null);
68
73
  const startCountdown = (seconds) => {
69
74
  setPhoneCountdown(seconds);
70
75
  setCanResendPhone(false);
@@ -91,6 +96,22 @@ export function PagamioLoginPage({ logo, text = loginPageDefaultText, appLabel,
91
96
  validation: { required: 'Phone number is required' },
92
97
  },
93
98
  ];
99
+ const loginNumberField = [
100
+ {
101
+ name: 'loginNumber',
102
+ label: 'Login Number',
103
+ type: 'text',
104
+ placeholder: '1230000001',
105
+ gridSpan: 12,
106
+ validation: {
107
+ required: 'Login number is required',
108
+ pattern: {
109
+ value: /^\d{10}$/,
110
+ message: 'Login number must be exactly 10 digits',
111
+ },
112
+ },
113
+ },
114
+ ];
94
115
  const handlePhoneFormSubmit = async (data) => {
95
116
  const phone = data.phoneNumber;
96
117
  if (phonePinConfig) {
@@ -136,6 +157,32 @@ export function PagamioLoginPage({ logo, text = loginPageDefaultText, appLabel,
136
157
  setIsLoggingInWithPin(false);
137
158
  }
138
159
  };
160
+ const handleLoginNumberFormSubmit = async (data) => {
161
+ setLoginNumber(data.loginNumber);
162
+ setUsernameStep('enterPin');
163
+ };
164
+ const handleLoginWithUsername = async () => {
165
+ if (loginNumberPinValue.length !== 4)
166
+ return;
167
+ setIsLoggingInWithUsername(true);
168
+ setUsernameError(null);
169
+ try {
170
+ const response = await usernamePinConfig.loginWithUsername(loginNumber, loginNumberPinValue);
171
+ const responseData = response?.data ?? response;
172
+ if (responseData?.mustChangePIN && usernamePinConfig.onMustChangePIN) {
173
+ usernamePinConfig.onMustChangePIN(loginNumber, response);
174
+ }
175
+ else {
176
+ onLoginSuccess?.(response);
177
+ }
178
+ }
179
+ catch (err) {
180
+ setUsernameError(err?.response?.data?.message ?? err?.message ?? 'Login failed. Please try again.');
181
+ }
182
+ finally {
183
+ setIsLoggingInWithUsername(false);
184
+ }
185
+ };
139
186
  const handleResendPhoneOtp = async () => {
140
187
  if (!canResendPhone || isResendingOtp)
141
188
  return;
@@ -254,15 +301,26 @@ export function PagamioLoginPage({ logo, text = loginPageDefaultText, appLabel,
254
301
  }
255
302
  };
256
303
  const showPhoneTab = phoneOtpConfig ?? phonePinConfig;
257
- const passwordTabLabel = phoneOtpConfig?.passwordTabLabel ?? phonePinConfig?.passwordTabLabel ?? 'Email & Password';
304
+ const showUsernameTab = usernamePinConfig;
305
+ const showTabs = showPhoneTab ?? showUsernameTab;
306
+ const passwordTabLabel = phoneOtpConfig?.passwordTabLabel ??
307
+ phonePinConfig?.passwordTabLabel ??
308
+ usernamePinConfig?.passwordTabLabel ??
309
+ 'Email & Password';
258
310
  const phoneTabLabel = phoneOtpConfig?.tabLabel ?? phonePinConfig?.tabLabel ?? (phonePinConfig ? 'Phone + PIN' : 'Phone + OTP');
259
- return (_jsx(AuthPageLayout, { appLabel: appLabel, title: text.welcomeTitle, subtitle: text.welcomeSubtitle, errorMessage: activeTab === 'password' ? (error ?? authError?.message) : undefined, logo: logo, className: className, horizontal: false, sideContent: features && features.length > 0 ? _jsx(FeatureCarousel, { features: features }) : undefined, sideContentClass: sideContentClass, footer: footer, children: _jsxs("div", { className: "mt-8", children: [showPhoneTab && (_jsxs("div", { className: "mb-6 flex rounded-lg border border-border overflow-hidden", children: [_jsx("button", { type: "button", onClick: () => setActiveTab('password'), className: `flex-1 py-2 text-sm font-medium transition-colors ${activeTab === 'password' ? 'bg-primary text-primary-foreground' : 'bg-background text-foreground/70 hover:bg-muted'}`, children: passwordTabLabel }), _jsx("button", { type: "button", onClick: () => {
311
+ const usernameTabLabel = usernamePinConfig?.tabLabel ?? 'Login Number + PIN';
312
+ return (_jsx(AuthPageLayout, { appLabel: appLabel, title: text.welcomeTitle, subtitle: text.welcomeSubtitle, errorMessage: activeTab === 'password' ? (error ?? authError?.message) : undefined, logo: logo, className: className, horizontal: false, sideContent: features && features.length > 0 ? _jsx(FeatureCarousel, { features: features }) : undefined, sideContentClass: sideContentClass, footer: footer, children: _jsxs("div", { className: "mt-8", children: [showTabs && (_jsxs("div", { className: "mb-6 flex rounded-lg border border-border overflow-hidden", children: [_jsx("button", { type: "button", onClick: () => setActiveTab('password'), className: `flex-1 py-2 text-sm font-medium transition-colors ${activeTab === 'password' ? 'bg-primary text-primary-foreground' : 'bg-background text-foreground/70 hover:bg-muted'}`, children: passwordTabLabel }), showPhoneTab && (_jsx("button", { type: "button", onClick: () => {
260
313
  setActiveTab('phone');
261
314
  setPhoneStep('enterPhone');
262
315
  setPhoneOtpValue('');
263
316
  setPhonePinValue('');
264
317
  setPhoneError(null);
265
- }, className: `flex-1 py-2 text-sm font-medium transition-colors ${activeTab === 'phone' ? 'bg-primary text-primary-foreground' : 'bg-background text-foreground/70 hover:bg-muted'}`, children: phoneTabLabel })] })), activeTab === 'password' && (_jsxs(_Fragment, { children: [_jsx(FormEngine, { fields: loginFields, onSubmit: handleSubmit, layout: "vertical", className: "mb-0 px-0", submitButtonClass: "w-full", submitButtonText: isLoading ? text.loadingButtonLabel : text.loginButtonLabel, onCancel: () => { }, showCancelButton: false, showSubmittingText: false, formRef: formRef, initialValues: {
318
+ }, className: `flex-1 py-2 text-sm font-medium transition-colors ${activeTab === 'phone' ? 'bg-primary text-primary-foreground' : 'bg-background text-foreground/70 hover:bg-muted'}`, children: phoneTabLabel })), showUsernameTab && (_jsx("button", { type: "button", onClick: () => {
319
+ setActiveTab('username');
320
+ setUsernameStep('enterLoginNumber');
321
+ setLoginNumberPinValue('');
322
+ setUsernameError(null);
323
+ }, className: `flex-1 py-2 text-sm font-medium transition-colors ${activeTab === 'username' ? 'bg-primary text-primary-foreground' : 'bg-background text-foreground/70 hover:bg-muted'}`, children: usernameTabLabel }))] })), activeTab === 'password' && (_jsxs(_Fragment, { children: [_jsx(FormEngine, { fields: loginFields, onSubmit: handleSubmit, layout: "vertical", className: "mb-0 px-0", submitButtonClass: "w-full", submitButtonText: isLoading ? text.loadingButtonLabel : text.loginButtonLabel, onCancel: () => { }, showCancelButton: false, showSubmittingText: false, formRef: formRef, initialValues: {
266
324
  ...(loginFieldType === 'email' ? { email: '' } : { username: '' }),
267
325
  password: '',
268
326
  rememberMe: false,
@@ -280,6 +338,13 @@ export function PagamioLoginPage({ logo, text = loginPageDefaultText, appLabel,
280
338
  setPhoneStep('enterPhone');
281
339
  setPhonePinValue('');
282
340
  setPhoneError(null);
283
- }, className: "w-full text-sm text-foreground/70", children: "\u2190 Change number" })] }))] }))] }) }));
341
+ }, className: "w-full text-sm text-foreground/70", children: "\u2190 Change number" })] }))] })), activeTab === 'username' && showUsernameTab && (_jsxs("div", { className: "space-y-4", children: [usernameError && _jsx("div", { className: "rounded-lg bg-red-50 p-4 text-sm text-red-600", children: usernameError }), usernameStep === 'enterLoginNumber' && (_jsx(FormEngine, { fields: loginNumberField, onSubmit: handleLoginNumberFormSubmit, layout: "vertical", className: "mb-0 px-0", submitButtonClass: "w-full", submitButtonText: "Continue", onCancel: () => { }, showCancelButton: false, showSubmittingText: false, initialValues: { loginNumber: '' } })), usernameStep === 'enterPin' && (_jsxs(_Fragment, { children: [_jsxs("p", { className: "text-sm text-muted-foreground text-center", children: ["Enter your 4-digit PIN for ", _jsx("span", { className: "font-medium text-foreground", children: loginNumber })] }), _jsxs("div", { children: [_jsx("p", { className: "mb-2 block text-sm font-medium text-foreground text-center", children: "PIN" }), _jsx(OtpInput, { length: 4, value: loginNumberPinValue, onChange: (v) => {
342
+ setLoginNumberPinValue(v);
343
+ setUsernameError(null);
344
+ }, disabled: isLoggingInWithUsername })] }), _jsx(Button, { type: "button", onClick: handleLoginWithUsername, disabled: loginNumberPinValue.length !== 4 || isLoggingInWithUsername, className: "w-full", size: "lg", children: isLoggingInWithUsername ? 'Signing in...' : 'Sign In' }), _jsx(Button, { type: "button", variant: "ghost", onClick: () => {
345
+ setUsernameStep('enterLoginNumber');
346
+ setLoginNumberPinValue('');
347
+ setUsernameError(null);
348
+ }, className: "w-full text-sm text-foreground/70", children: "\u2190 Change login number" })] }))] }))] }) }));
284
349
  }
285
350
  export default PagamioLoginPage;
@@ -1,5 +1,5 @@
1
1
  export { default as LogoutButton } from './LogoutButton';
2
- export { default as PagamioLoginPage, loginPageDefaultText, type LoginFieldType, type PagamioLoginCredentials, type PagamioLoginPageProps, type PhoneOtpLoginConfig, type PhonePinLoginConfig, } from './LoginPage';
2
+ export { default as PagamioLoginPage, loginPageDefaultText, type LoginFieldType, type PagamioLoginCredentials, type PagamioLoginPageProps, type PhoneOtpLoginConfig, type PhonePinLoginConfig, type UsernamePinLoginConfig, } from './LoginPage';
3
3
  export { default as ChangePasswordPage, type ChangePasswordPageProps } from './ChangePasswordPage';
4
4
  export { type PostDataProps } from './hooks/useChangeUserPassword';
5
5
  export { default as PagamioCustomerRegistrationPage, customerRegistrationPageDefaultText, type PagamioCustomerRegistrationPageProps, } from './CustomerRegistrationPage';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pagamio/frontend-commons-lib",
3
3
  "description": "Pagamio library for Frontend reusable components like the form engine and table container",
4
- "version": "0.8.304",
4
+ "version": "0.8.305",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false