@pagamio/frontend-commons-lib 0.8.304 → 0.8.306
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,32 @@ export function PagamioLoginPage({ logo, text = loginPageDefaultText, appLabel,
|
|
|
254
301
|
}
|
|
255
302
|
};
|
|
256
303
|
const showPhoneTab = phoneOtpConfig ?? phonePinConfig;
|
|
257
|
-
const
|
|
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
|
-
|
|
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 inline-flex w-full items-center gap-1 rounded-xl bg-muted/60 p-1", role: "tablist", "aria-label": "Login method", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": activeTab === 'password', onClick: () => setActiveTab('password'), className: `flex-1 rounded-lg px-3 py-2 text-center text-sm font-medium transition-all ${activeTab === 'password'
|
|
313
|
+
? 'bg-background text-foreground shadow-sm ring-1 ring-border'
|
|
314
|
+
: 'text-muted-foreground hover:bg-background/40 hover:text-foreground'}`, children: passwordTabLabel }), showPhoneTab && (_jsx("button", { type: "button", role: "tab", "aria-selected": activeTab === 'phone', onClick: () => {
|
|
260
315
|
setActiveTab('phone');
|
|
261
316
|
setPhoneStep('enterPhone');
|
|
262
317
|
setPhoneOtpValue('');
|
|
263
318
|
setPhonePinValue('');
|
|
264
319
|
setPhoneError(null);
|
|
265
|
-
}, className: `flex-1 py-2 text-sm font-medium transition-
|
|
320
|
+
}, className: `flex-1 rounded-lg px-3 py-2 text-center text-sm font-medium transition-all ${activeTab === 'phone'
|
|
321
|
+
? 'bg-background text-foreground shadow-sm ring-1 ring-border'
|
|
322
|
+
: 'text-muted-foreground hover:bg-background/40 hover:text-foreground'}`, children: phoneTabLabel })), showUsernameTab && (_jsx("button", { type: "button", role: "tab", "aria-selected": activeTab === 'username', onClick: () => {
|
|
323
|
+
setActiveTab('username');
|
|
324
|
+
setUsernameStep('enterLoginNumber');
|
|
325
|
+
setLoginNumberPinValue('');
|
|
326
|
+
setUsernameError(null);
|
|
327
|
+
}, className: `flex-1 rounded-lg px-3 py-2 text-center text-sm font-medium transition-all ${activeTab === 'username'
|
|
328
|
+
? 'bg-background text-foreground shadow-sm ring-1 ring-border'
|
|
329
|
+
: 'text-muted-foreground hover:bg-background/40 hover:text-foreground'}`, 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
330
|
...(loginFieldType === 'email' ? { email: '' } : { username: '' }),
|
|
267
331
|
password: '',
|
|
268
332
|
rememberMe: false,
|
|
@@ -280,6 +344,13 @@ export function PagamioLoginPage({ logo, text = loginPageDefaultText, appLabel,
|
|
|
280
344
|
setPhoneStep('enterPhone');
|
|
281
345
|
setPhonePinValue('');
|
|
282
346
|
setPhoneError(null);
|
|
283
|
-
}, className: "w-full text-sm text-foreground/70", children: "\u2190 Change number" })] }))] }))] }) }))
|
|
347
|
+
}, 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) => {
|
|
348
|
+
setLoginNumberPinValue(v);
|
|
349
|
+
setUsernameError(null);
|
|
350
|
+
}, 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: () => {
|
|
351
|
+
setUsernameStep('enterLoginNumber');
|
|
352
|
+
setLoginNumberPinValue('');
|
|
353
|
+
setUsernameError(null);
|
|
354
|
+
}, className: "w-full text-sm text-foreground/70", children: "\u2190 Change login number" })] }))] }))] }) }));
|
|
284
355
|
}
|
|
285
356
|
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/lib/styles.css
CHANGED
|
@@ -2486,6 +2486,9 @@ video {
|
|
|
2486
2486
|
.bg-muted\/30 {
|
|
2487
2487
|
background-color: hsl(var(--muted) / 0.3);
|
|
2488
2488
|
}
|
|
2489
|
+
.bg-muted\/60 {
|
|
2490
|
+
background-color: hsl(var(--muted) / 0.6);
|
|
2491
|
+
}
|
|
2489
2492
|
.bg-orange-100 {
|
|
2490
2493
|
--tw-bg-opacity: 1;
|
|
2491
2494
|
background-color: rgb(254 236 220 / var(--tw-bg-opacity, 1));
|
|
@@ -3591,6 +3594,11 @@ video {
|
|
|
3591
3594
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
3592
3595
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
3593
3596
|
}
|
|
3597
|
+
.ring-1 {
|
|
3598
|
+
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
3599
|
+
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
3600
|
+
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
3601
|
+
}
|
|
3594
3602
|
.ring-2 {
|
|
3595
3603
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
3596
3604
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
@@ -3606,6 +3614,9 @@ video {
|
|
|
3606
3614
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(8px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
3607
3615
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
3608
3616
|
}
|
|
3617
|
+
.ring-border {
|
|
3618
|
+
--tw-ring-color: hsl(var(--border));
|
|
3619
|
+
}
|
|
3609
3620
|
.ring-cyan-400 {
|
|
3610
3621
|
--tw-ring-opacity: 1;
|
|
3611
3622
|
--tw-ring-color: rgb(34 211 238 / var(--tw-ring-opacity, 1));
|
|
@@ -4193,6 +4204,9 @@ video {
|
|
|
4193
4204
|
.hover\:bg-accent:hover {
|
|
4194
4205
|
background-color: hsl(var(--accent));
|
|
4195
4206
|
}
|
|
4207
|
+
.hover\:bg-background\/40:hover {
|
|
4208
|
+
background-color: hsl(var(--background) / 0.4);
|
|
4209
|
+
}
|
|
4196
4210
|
.hover\:bg-blue-200:hover {
|
|
4197
4211
|
--tw-bg-opacity: 1;
|
|
4198
4212
|
background-color: rgb(195 221 253 / var(--tw-bg-opacity, 1));
|
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.
|
|
4
|
+
"version": "0.8.306",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": false
|