@opexa/portal-components 0.0.254 → 0.0.255

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.
@@ -1 +1,5 @@
1
- export declare function MobileNumberSignIn(): import("react/jsx-runtime").JSX.Element;
1
+ interface Props {
2
+ siteName?: string;
3
+ }
4
+ export declare function MobileNumberSignIn(props: Props): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -19,7 +19,8 @@ import { Checkbox } from '../../ui/Checkbox/index.js';
19
19
  import { Field } from '../../ui/Field/index.js';
20
20
  import { PinInput } from '../../ui/PinInput/index.js';
21
21
  import { useSignInContext, useSignInPropsContext } from './SignInContext.js';
22
- export function MobileNumberSignIn() {
22
+ import { getFriendlyErrorMessage } from './utils.js';
23
+ export function MobileNumberSignIn(props) {
23
24
  const signInProps = useSignInPropsContext();
24
25
  const context = useSignInContext();
25
26
  const globalStore = useGlobalStore(useShallow((ctx) => ({
@@ -27,8 +28,49 @@ export function MobileNumberSignIn() {
27
28
  signUp: ctx.signUp,
28
29
  kycReminder: ctx.kycReminder,
29
30
  })));
30
- const signInMutation = useSignInMutation();
31
- const sendVerificationCodeMutation = useSendVerificationCodeMutation();
31
+ const signInMutation = useSignInMutation({
32
+ onSuccess: () => {
33
+ context.setStep(2);
34
+ cooldown.start();
35
+ },
36
+ onError: (err) => {
37
+ const errorTitle = err.name === 'MEMBER_ACCOUNT_BLACKLISTED'
38
+ ? 'Your Account is Blacklisted'
39
+ : err.name === 'MEMBER_ACCOUNT_SUSPENDED'
40
+ ? 'Your Account is Suspended'
41
+ : 'Sign In Failed';
42
+ toaster.error({
43
+ title: errorTitle,
44
+ description: getFriendlyErrorMessage(err.name, {
45
+ siteName: props.siteName,
46
+ accountName: step1Form.getValues('mobileNumber'),
47
+ }),
48
+ });
49
+ },
50
+ });
51
+ const sendVerificationCodeMutation = useSendVerificationCodeMutation({
52
+ onSuccess: () => {
53
+ step1Form.reset();
54
+ step2Form.reset();
55
+ context.setStep(1);
56
+ globalStore.signIn.setOpen(false);
57
+ globalStore.kycReminder.setOpen(true);
58
+ },
59
+ onError: (err) => {
60
+ const errorTitle = err.name === 'MEMBER_ACCOUNT_BLACKLISTED'
61
+ ? 'Your Account is Blacklisted'
62
+ : err.name === 'MEMBER_ACCOUNT_SUSPENDED'
63
+ ? 'Your Account is Suspended'
64
+ : 'Sign In Failed';
65
+ toaster.error({
66
+ title: errorTitle,
67
+ description: getFriendlyErrorMessage(err.name, {
68
+ siteName: props.siteName,
69
+ accountName: step1Form.getValues('mobileNumber'),
70
+ }),
71
+ });
72
+ },
73
+ });
32
74
  const localeInfo = useLocaleInfo();
33
75
  const mobileNumberParser = useMobileNumberParser();
34
76
  const Step1Definition = z.object({
@@ -81,22 +123,11 @@ export function MobileNumberSignIn() {
81
123
  });
82
124
  const form2Ref = useRef(null);
83
125
  return (_jsxs(_Fragment, { children: [context.step === 1 && (_jsxs(_Fragment, { children: [_jsxs("form", { className: "mt-3xl", onSubmit: step1Form.handleSubmit(async (data) => {
84
- try {
85
- await sendVerificationCodeMutation.mutateAsync({
86
- channel: 'SMS',
87
- recipient: mobileNumberParser.format(data.mobileNumber),
88
- strict: true,
89
- });
90
- context.setStep(2);
91
- cooldown.start();
92
- }
93
- catch (e) {
94
- toaster.error({
95
- description: e instanceof Error
96
- ? e.message
97
- : 'Failed to send verification code',
98
- });
99
- }
126
+ sendVerificationCodeMutation.mutate({
127
+ channel: 'SMS',
128
+ recipient: mobileNumberParser.format(data.mobileNumber),
129
+ strict: true,
130
+ });
100
131
  }), children: [_jsxs(Field.Root, { invalid: !!step1Form.formState.errors.mobileNumber, children: [_jsx(Field.Label, { children: "Mobile Number" }), _jsxs("div", { className: "relative", children: [_jsxs("div", { className: "absolute left-3.5 top-1/2 flex shrink-0 -translate-y-1/2 items-center gap-md", children: [_jsx(localeInfo.country.flag, { className: "size-5" }), _jsx("span", { className: "text-text-placeholder", children: localeInfo.mobileNumber.areaCode })] }), _jsx(Field.Input, { style: {
101
132
  paddingLeft: `calc(2.75rem + ${localeInfo.mobileNumber.areaCode.length}ch)`,
102
133
  }, ...step1Form.register('mobileNumber') })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.mobileNumber?.message })] }), _jsx(Button, { type: "submit", className: "mt-3xl", disabled: step1Form.formState.isSubmitting, children: "Log In" }), _jsx(Controller, { control: step1Form.control, name: "termsAccepted", render: (o) => (_jsxs(Field.Root, { invalid: o.fieldState.invalid, className: "mt-xl", children: [_jsxs(Checkbox.Root, { checked: o.field.value, onCheckedChange: (details) => {
@@ -109,25 +140,11 @@ export function MobileNumberSignIn() {
109
140
  globalStore.signUp.setOpen(true);
110
141
  }, children: "Sign up" })] })] })), context.step === 2 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mt-xl text-center text-lg font-semibold", children: "Check your Phone" }), _jsxs("p", { className: "text-text-secondary-700 mt-xs text-center text-sm", children: ["We\u2019ve sent a verification code to your mobile number", ' ', _jsx("span", { className: "font-semibold", children: mobileNumberParser.format(step1Form.getValues('mobileNumber')) }), ' ', "via text"] }), _jsxs("form", { ref: form2Ref, className: "mt-5", onSubmit: step2Form.handleSubmit(async ({ verificationCode }) => {
111
142
  const { mobileNumber } = step1Form.getValues();
112
- try {
113
- await signInMutation.mutateAsync({
114
- type: 'MOBILE_NUMBER',
115
- mobileNumber: mobileNumberParser.format(mobileNumber),
116
- verificationCode: verificationCode.join(''),
117
- });
118
- step1Form.reset();
119
- step2Form.reset();
120
- context.setStep(1);
121
- globalStore.signIn.setOpen(false);
122
- globalStore.kycReminder.setOpen(true);
123
- }
124
- catch (err) {
125
- toaster.error({
126
- description: err instanceof Error
127
- ? err.message
128
- : 'Invalid Verification Code',
129
- });
130
- }
143
+ signInMutation.mutate({
144
+ type: 'MOBILE_NUMBER',
145
+ mobileNumber: mobileNumberParser.format(mobileNumber),
146
+ verificationCode: verificationCode.join(''),
147
+ });
131
148
  }), children: [_jsx(Controller, { name: "verificationCode", control: step2Form.control, render: (o) => (_jsxs(Field.Root, { invalid: o.fieldState.invalid, children: [_jsxs(PinInput.Root, { placeholder: "0", value: o.field.value, onValueChange: (details) => {
132
149
  o.field.onChange(details.value);
133
150
  o.field.onBlur();
@@ -1 +1,5 @@
1
- export declare function NameAndPasswordSignIn(): import("react/jsx-runtime").JSX.Element;
1
+ interface Props {
2
+ siteName?: string;
3
+ }
4
+ export declare function NameAndPasswordSignIn(props: Props): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -18,7 +18,8 @@ import { Checkbox } from '../../ui/Checkbox/index.js';
18
18
  import { Field } from '../../ui/Field/index.js';
19
19
  import { PasswordInput } from '../../ui/PasswordInput/index.js';
20
20
  import { useSignInContext, useSignInPropsContext } from './SignInContext.js';
21
- export function NameAndPasswordSignIn() {
21
+ import { getFriendlyErrorMessage } from './utils.js';
22
+ export function NameAndPasswordSignIn(props) {
22
23
  const signInProps = useSignInPropsContext();
23
24
  const context = useSignInContext();
24
25
  const dialog = useDialogContext();
@@ -42,9 +43,17 @@ export function NameAndPasswordSignIn() {
42
43
  }
43
44
  },
44
45
  onError(err) {
46
+ const errorTitle = err.name === 'MEMBER_ACCOUNT_BLACKLISTED'
47
+ ? 'Your Account is Blacklisted'
48
+ : err.name === 'MEMBER_ACCOUNT_SUSPENDED'
49
+ ? 'Your Account is Suspended'
50
+ : 'Sign In Failed';
45
51
  toaster.error({
46
- description: err.message ||
47
- 'An error occurred while signing in. Please try again.',
52
+ title: errorTitle,
53
+ description: getFriendlyErrorMessage(err.name, {
54
+ siteName: props.siteName,
55
+ accountName: form.getValues('name'),
56
+ }),
48
57
  });
49
58
  },
50
59
  });
@@ -6,5 +6,6 @@ export interface SignInProps {
6
6
  /** @default '/terms-and-conditions' */
7
7
  termsAndConditionsUrl?: string;
8
8
  children?: ReactNode;
9
+ siteName?: string;
9
10
  }
10
11
  export declare function SignIn(props: SignInProps): import("react/jsx-runtime").JSX.Element;
@@ -15,5 +15,5 @@ export function SignIn(props) {
15
15
  const signInStore = useGlobalStore(useShallow((ctx) => ctx.signIn));
16
16
  return (_jsx(SignInPropsProvider, { value: props, children: _jsx(SignInProvider, { value: signIn, children: _jsx(Dialog.Root, { open: signInStore.open, onOpenChange: (details) => {
17
17
  signInStore.setOpen(details.open);
18
- }, lazyMount: true, unmountOnExit: true, closeOnEscape: false, closeOnInteractOutside: false, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { children: _jsxs(Dialog.Content, { className: twMerge('bg-bg-primary-alt mx-auto h-full w-full items-start p-3xl pb-4xl lg:h-auto lg:w-[400px] lg:rounded-xl overflow-y-auto', props.className), children: [_jsx(Dialog.CloseTrigger, { children: _jsx(XIcon, {}) }), _jsx(Image, { src: props.logo, alt: "", width: 200, height: 100, className: "mx-auto h-7.5 w-auto", draggable: false }), _jsx(SignInForm, {})] }) })] }) }) }) }));
18
+ }, lazyMount: true, unmountOnExit: true, closeOnEscape: false, closeOnInteractOutside: false, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { children: _jsxs(Dialog.Content, { className: twMerge('bg-bg-primary-alt mx-auto h-full w-full items-start overflow-y-auto p-3xl pb-4xl lg:h-auto lg:w-[400px] lg:rounded-xl', props.className), children: [_jsx(Dialog.CloseTrigger, { children: _jsx(XIcon, {}) }), _jsx(Image, { src: props.logo, alt: "", width: 200, height: 100, className: "mx-auto h-7.5 w-auto", draggable: false }), _jsx(SignInForm, { siteName: props.siteName })] }) })] }) }) }) }));
19
19
  }
@@ -1 +1,5 @@
1
- export declare function SignInForm(): import("react/jsx-runtime").JSX.Element;
1
+ interface Props {
2
+ siteName?: string;
3
+ }
4
+ export declare function SignInForm(props: Props): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -4,7 +4,7 @@ import { MobileNumberSignIn } from './MobileNumberSignIn.js';
4
4
  import { NameAndPasswordSignIn } from './NameAndPasswordSignIn.js';
5
5
  import { useSignInContext } from './SignInContext.js';
6
6
  import { SignInMethod } from './SignInMethod.js';
7
- export function SignInForm() {
7
+ export function SignInForm(props) {
8
8
  const context = useSignInContext();
9
- return (_jsxs(_Fragment, { children: [context.step === 1 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mt-xl text-center text-lg font-semibold", children: "Log in to your account" }), _jsx("p", { className: "text-text-secondary-700 mt-xs text-center text-sm", children: "Welcome back! Please enter your details." })] })), _jsx(SignInMethod, {}), context.type === 'MOBILE_NUMBER' && _jsx(MobileNumberSignIn, {}), context.type === 'NAME_AND_PASSWORD' && _jsx(NameAndPasswordSignIn, {})] }));
9
+ return (_jsxs(_Fragment, { children: [context.step === 1 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mt-xl text-center text-lg font-semibold", children: "Log in to your account" }), _jsx("p", { className: "text-text-secondary-700 mt-xs text-center text-sm", children: "Welcome back! Please enter your details." })] })), _jsx(SignInMethod, {}), context.type === 'MOBILE_NUMBER' && (_jsx(MobileNumberSignIn, { siteName: props.siteName })), context.type === 'NAME_AND_PASSWORD' && (_jsx(NameAndPasswordSignIn, { siteName: props.siteName }))] }));
10
10
  }
@@ -0,0 +1,7 @@
1
+ type Options = {
2
+ accountName?: string;
3
+ siteName?: string;
4
+ };
5
+ export type ErrorCode = 'MEMBER_ACCOUNT_BLACKLISTED' | 'MEMBER_ACCOUNT_SUSPENDED' | 'INVALID_RECAPTCHA_RESPONSE' | 'MEMBER_NOT_FOUND' | 'RATE_LIMIT_REACH' | 'NOT_READY_TO_SEND_VERIFICATION_ERROR' | 'Forbidden';
6
+ export declare function getFriendlyErrorMessage(code: ErrorCode, options?: Options): string;
7
+ export {};
@@ -0,0 +1,22 @@
1
+ export function getFriendlyErrorMessage(code, options) {
2
+ const accountName = options?.accountName ?? '';
3
+ const siteName = options?.siteName ?? '';
4
+ switch (code) {
5
+ case 'MEMBER_ACCOUNT_BLACKLISTED':
6
+ return `Your account ${accountName} has been blacklisted due to a serious violation of our policies. For more information or to appeal, please contact the ${siteName} Chat Support team.`;
7
+ case 'MEMBER_ACCOUNT_SUSPENDED':
8
+ return `Your account ${accountName} has been temporarily suspended. Please reach out to the ${siteName} Chat Support team to learn more and get help with resolving the issue.`;
9
+ case 'INVALID_RECAPTCHA_RESPONSE':
10
+ return 'Invalid reCAPTCHA response';
11
+ case 'MEMBER_NOT_FOUND':
12
+ return 'Account not found. Please check your username and try again.';
13
+ case 'RATE_LIMIT_REACH':
14
+ return 'Rate limit exceeded';
15
+ case 'NOT_READY_TO_SEND_VERIFICATION_ERROR':
16
+ return 'Your account is not ready to send verification requests. Please try again later.';
17
+ case 'Forbidden':
18
+ return 'Invalid username or password, please try again.';
19
+ default:
20
+ return 'Something went wrong. Please try again later.';
21
+ }
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opexa/portal-components",
3
- "version": "0.0.254",
3
+ "version": "0.0.255",
4
4
  "exports": {
5
5
  "./ui/*": {
6
6
  "types": "./dist/ui/*/index.d.ts",