@opexa/portal-components 0.0.138 → 0.0.140

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.
@@ -0,0 +1,10 @@
1
+ import type { CaptureSubject } from './useKYC';
2
+ interface FileUploadProps {
3
+ name: string;
4
+ value: string;
5
+ hasDescription?: boolean;
6
+ onChange?: (id: string) => void;
7
+ captureObject: CaptureSubject;
8
+ }
9
+ declare function FileUpload({ value, name, hasDescription, onChange, captureObject, }: FileUploadProps): import("react/jsx-runtime").JSX.Element;
10
+ export default FileUpload;
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import Image from 'next/image';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { useFileQuery } from '../../client/hooks/useFileQuery.js';
6
+ import { useUploadImageFileMutation } from '../../client/hooks/useUploadImageFileMutation.js';
7
+ import { UploadCloud02Icon } from '../../icons/UploadCloud02Icon.js';
8
+ import { Button } from '../../ui/Button/index.js';
9
+ import { useKYCContext } from './KYCContext.js';
10
+ import { validateFile } from './utils.js';
11
+ function FileUpload({ value, name, hasDescription, onChange, captureObject, }) {
12
+ const kyc = useKYCContext();
13
+ const { mutate, isPending } = useUploadImageFileMutation();
14
+ const { data: uploadedImage, isSuccess: uploadedImageReady } = useFileQuery(value);
15
+ if (isPending) {
16
+ return (_jsx("div", { className: "border-border-primary bg-bg-primary flex min-h-[11.25rem] flex-col justify-center gap-lg rounded-xl border px-3xl py-xl", children: _jsx("div", { className: "flex items-center justify-center", children: _jsx("span", { className: "text-text-secondary-700", children: "Uploading..." }) }) }));
17
+ }
18
+ function handleFileChange(event) {
19
+ const file = event.target.files?.[0];
20
+ if (!file) {
21
+ console.error('No file selected');
22
+ return;
23
+ }
24
+ const error = validateFile(file);
25
+ if (error) {
26
+ console.error(error);
27
+ return;
28
+ }
29
+ mutate({ file }, {
30
+ onSuccess: (id) => {
31
+ onChange?.(id);
32
+ },
33
+ });
34
+ }
35
+ function handleUploadClick() {
36
+ const fileInput = document.getElementById(`file-upload-${name}`);
37
+ if (fileInput) {
38
+ fileInput.click();
39
+ }
40
+ }
41
+ return (_jsxs("div", { className: "border-border-primary bg-bg-primary flex min-h-[11.25rem] flex-col justify-center gap-lg rounded-xl border px-3xl py-xl", children: [uploadedImageReady ? (_jsxs("div", { className: "border-border-primary bg-bg-primary min-h-[9.25rem]", children: [_jsx(Image, { src: uploadedImage?.url ?? '', alt: "Uploaded", className: "w-full object-contain", width: 350, height: 350, draggable: false }), _jsx(Button, { size: "xs", variant: "outline", className: twMerge('mx-auto mt-4 w-fit', value && 'hidden'), type: "button", onClick: handleUploadClick, children: "Reupload" })] })) : hasDescription ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "border-border-primary shadow-xs mx-auto flex size-8 items-center justify-center rounded-sm border", children: _jsx(UploadCloud02Icon, { className: "text-text-quinary size-4" }) }), _jsxs("p", { className: "text-text-tertiary-600 text-center text-sm", children: [_jsx("span", { className: "text-button-secondary-fg cursor-pointer font-semibold", onClick: handleUploadClick, children: "Click to upload" }), ' ', "or drag and drop", _jsx("br", {}), "PNG, JPG or JPEG (max. 10mb)", _jsx("br", {}), "or"] })] })) : null, _jsx("input", { type: "file", id: `file-upload-${name}`, name: name, onChange: handleFileChange, accept: "image/png, image/jpg, image/jpeg", className: "hidden" }), _jsx(Button, { size: "xs", variant: "outline", className: "mx-auto w-fit", onClick: () => kyc.setCapturing(captureObject), disabled: isPending, type: "button", children: value ? 'Retake Photo' : 'Take a Photo' })] }));
42
+ }
43
+ export default FileUpload;
@@ -1,37 +1,107 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { zodResolver } from '@hookform/resolvers/zod';
4
+ import { useEffect } from 'react';
5
+ import { useForm } from 'react-hook-form';
6
+ import { z } from 'zod';
2
7
  import { useShallow } from 'zustand/shallow';
8
+ import { useCreateMemberVerificationMutation } from '../../client/hooks/useCreateMemberVerificationMutation.js';
3
9
  import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
10
+ import { useMemberVerificationQuery } from '../../client/hooks/useMemberVerificationQuery.js';
11
+ import { useUpdateMemberVerificationMutation } from '../../client/hooks/useUpdateMemberVerificationMutation.js';
12
+ import { toaster } from '../../client/utils/toaster.js';
4
13
  import { HelpCircleIcon } from '../../icons/HelpCircleIcon.js';
5
- import { UploadCloud02Icon } from '../../icons/UploadCloud02Icon.js';
6
14
  import { Button } from '../../ui/Button/index.js';
7
15
  import { Dialog } from '../../ui/Dialog/index.js';
8
16
  import { Field } from '../../ui/Field/index.js';
9
17
  import { Portal } from '../../ui/Portal/index.js';
10
- import DisplayImage from './DisplayImage.js';
18
+ import FileUpload from './FileUpload.js';
11
19
  import { useKYCContext } from './KYCContext.js';
20
+ const formSchema = z.object({
21
+ selfieImage: z.string().min(1, 'Selfie image is required.'),
22
+ idFrontImage: z.string().min(1, 'Front image of ID is required.'),
23
+ });
12
24
  export function IdentityVerification() {
13
25
  const kyc = useKYCContext();
26
+ const { mutate: createMemberVerification, isPending: createPending } = useCreateMemberVerificationMutation({
27
+ onSuccess: () => {
28
+ toaster.success({
29
+ title: 'ID Front Image & Selfie Image uploaded successfully',
30
+ });
31
+ kyc.setStep(3);
32
+ },
33
+ onError: (error) => {
34
+ toaster.error({
35
+ title: 'Failed to upload ID Front Image & Selfie Image',
36
+ description: error.message,
37
+ });
38
+ },
39
+ });
40
+ const { mutate: updateMemberVerification, isPending: updatePending } = useUpdateMemberVerificationMutation({
41
+ onSuccess: () => {
42
+ toaster.success({
43
+ title: 'ID Front Image & Selfie Image updated successfully',
44
+ });
45
+ kyc.setStep(3);
46
+ },
47
+ onError: (error) => {
48
+ toaster.error({
49
+ title: 'Failed to upload ID Front Image & Selfie Image',
50
+ description: error.message,
51
+ });
52
+ },
53
+ });
54
+ const memberVerification = useMemberVerificationQuery();
55
+ const memberId = memberVerification.data?.id;
14
56
  const globalStore = useGlobalStore(useShallow((ctx) => ({
15
57
  kyc: ctx.kyc,
16
58
  })));
17
- return (_jsxs("div", { children: [_jsx(Dialog.Title, { className: "text-center text-lg font-semibold", children: "Verify your identity" }), _jsx(Dialog.Description, { className: "text-text-secondary-700 mt-xs text-center text-sm", children: "Verify your details to confirm your identity and secure your access." }), _jsxs("div", { className: "border-border-primary bg-bg-primary text-text-placeholder mt-7 rounded-xl border p-lg text-xs leading-tight", children: [_jsx("h3", { className: "font-semibold", children: "Instructions" }), _jsxs("ol", { className: "mt-2 list-inside list-decimal", children: [_jsx("li", { children: "Upload a full photo of your ID." }), _jsx("li", { children: "Please ensure that all details in the uploaded image are legible." }), _jsx("li", { children: "Please ensure that the ID uploaded is within the validity period." })] })] }), _jsxs("form", { className: "mt-xl", onSubmit: (e) => {
18
- e.preventDefault();
19
- kyc.setStep(3);
20
- }, children: [_jsxs(Field.Root, { children: [_jsxs(Field.Label, { className: "flex items-center gap-1.5", children: [_jsx("span", { children: "Front of your ID" }), _jsx(HelpCircleIcon, { className: "text-text-quinary size-4" })] }), _jsx(UploadIdDocument, {})] }), _jsxs(Field.Root, { className: "mt-3", children: [_jsxs(Field.Label, { className: "flex items-center gap-1.5", children: [_jsx("span", { children: "Selfie holding your ID" }), _jsx(HelpCircleIcon, { className: "text-text-quinary size-4" })] }), _jsx(UploadSelfie, {})] }), _jsx(Button, { type: "submit", className: "mt-6", children: "Continue" }), _jsx(Button, { variant: "outline", colorScheme: "gray", className: "mt-lg", onClick: () => {
59
+ const form = useForm({
60
+ resolver: zodResolver(formSchema),
61
+ defaultValues: {
62
+ idFrontImage: '',
63
+ selfieImage: '',
64
+ },
65
+ });
66
+ function onSubmit(values) {
67
+ if (!memberId) {
68
+ createMemberVerification({
69
+ selfieImage: values.selfieImage,
70
+ idFrontImage: values.idFrontImage,
71
+ address: '',
72
+ nationality: '',
73
+ natureOfWork: '',
74
+ permanentAddress: '',
75
+ placeOfBirth: '',
76
+ sourceOfIncome: '',
77
+ });
78
+ }
79
+ else {
80
+ updateMemberVerification({
81
+ id: memberId,
82
+ data: {
83
+ selfieImage: values.selfieImage,
84
+ idFrontImage: values.idFrontImage,
85
+ },
86
+ });
87
+ }
88
+ }
89
+ useEffect(() => {
90
+ if (kyc.idFrontImageId) {
91
+ form.setValue('idFrontImage', kyc.idFrontImageId);
92
+ }
93
+ if (kyc.selfieImageId) {
94
+ form.setValue('selfieImage', kyc.selfieImageId);
95
+ }
96
+ }, [form, kyc]);
97
+ return (_jsxs("div", { children: [_jsx(Dialog.Title, { className: "text-center text-lg font-semibold", children: "Verify your identity" }), _jsx(Dialog.Description, { className: "text-text-secondary-700 mt-xs text-center text-sm", children: "Verify your details to confirm your identity and secure your access." }), _jsxs("div", { className: "border-border-primary bg-bg-primary text-text-placeholder mt-7 rounded-xl border p-lg text-xs leading-tight", children: [_jsx("h3", { className: "font-semibold", children: "Instructions" }), _jsxs("ol", { className: "mt-2 list-inside list-decimal", children: [_jsx("li", { children: "Upload a full photo of your ID." }), _jsx("li", { children: "Please ensure that all details in the uploaded image are legible." }), _jsx("li", { children: "Please ensure that the ID uploaded is within the validity period." })] })] }), _jsxs("form", { className: "mt-xl", onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(Field.Root, { invalid: !!form.formState.errors.idFrontImage, children: [_jsxs(Field.Label, { className: "flex items-center gap-1.5", children: [_jsx("span", { children: "Front of your ID" }), _jsx(HelpCircleIcon, { className: "text-text-quinary size-4" })] }), _jsx(FileUpload, { value: kyc.idFrontImageId || '', hasDescription: true, name: "idFrontImage", captureObject: "ID_DOCUMENT", onChange: (file) => {
98
+ form.setValue('idFrontImage', file);
99
+ kyc.setFrontImageId(file);
100
+ } }, "id-front-image-upload"), _jsx(Field.ErrorText, { children: form.formState.errors.idFrontImage?.message })] }), _jsxs(Field.Root, { className: "mt-3", invalid: !!form.formState.errors.selfieImage, children: [_jsxs(Field.Label, { className: "flex items-center gap-1.5", children: [_jsx("span", { children: "Selfie holding your ID" }), _jsx(HelpCircleIcon, { className: "text-text-quinary size-4" })] }), _jsx(FileUpload, { value: kyc.selfieImageId || '', name: "selfieImage", captureObject: "SELFIE", onChange: (file) => {
101
+ form.setValue('selfieImage', file);
102
+ } }, "selfie-image-upload"), _jsx(Field.ErrorText, { children: form.formState.errors.selfieImage?.message })] }), _jsx(Button, { type: "submit", className: "mt-6", disabled: createPending || updatePending, children: "Continue" }), _jsx(Button, { variant: "outline", colorScheme: "gray", className: "mt-lg", type: "button", onClick: () => {
21
103
  kyc.setStep(3);
22
104
  kyc.setDone(true);
23
105
  globalStore.kyc.setOpen(false);
24
106
  }, children: "Skip for now" })] }), _jsx(Dialog.Root, { lazyMount: true, unmountOnExit: true, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { children: _jsx(Dialog.Content, { className: "bg-bg-primary-alt mx-auto min-h-full w-full p-3xl lg:h-auto lg:w-[400px]" }) })] }) })] }));
25
107
  }
26
- function UploadIdDocument() {
27
- const kyc = useKYCContext();
28
- return (_jsxs("div", { className: "border-border-primary bg-bg-primary flex min-h-[11.25rem] flex-col justify-center gap-lg rounded-xl border px-3xl py-xl", children: [kyc.idFrontImageId ? (_jsx(DisplayImage, { id: kyc.idFrontImageId })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "border-border-primary shadow-xs mx-auto flex size-8 items-center justify-center rounded-sm border", children: _jsx(UploadCloud02Icon, { className: "text-text-quinary size-4" }) }), _jsxs("p", { className: "text-text-tertiary-600 text-center text-sm", children: [_jsx("span", { className: "text-button-secondary-fg font-semibold", children: "Click to upload" }), ' ', "or drag and drop", _jsx("br", {}), "PNG, JPG or JPEG (max. 10mb)", _jsx("br", {}), "or"] })] })), _jsx(Button, { size: "xs", variant: "outline", className: "mx-auto w-fit", onClick: () => {
29
- kyc.setCapturing('ID_DOCUMENT');
30
- }, children: kyc.idFrontImageId ? 'Rekate Photo' : 'Take a Photo' })] }));
31
- }
32
- function UploadSelfie() {
33
- const kyc = useKYCContext();
34
- return (_jsxs("div", { className: "border-border-primary bg-bg-primary flex min-h-[11.25rem] flex-col justify-center gap-lg rounded-xl border px-3xl py-xl", children: [kyc.selfieImageId ? (_jsx(DisplayImage, { id: kyc.selfieImageId })) : (_jsx("div", { className: "border-border-primary shadow-xs mx-auto flex size-8 items-center justify-center rounded-sm border", children: _jsx(UploadCloud02Icon, { className: "text-text-quinary size-4" }) })), _jsx(Button, { size: "xs", variant: "outline", className: "mx-auto w-fit", onClick: () => {
35
- kyc.setCapturing('SELFIE');
36
- }, children: "Take a photo" })] }));
37
- }
@@ -1,7 +1,11 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import Image from 'next/image';
4
+ import { useEffect } from 'react';
3
5
  import { useShallow } from 'zustand/shallow';
6
+ import { useAccountQuery } from '../../client/hooks/useAccountQuery.js';
4
7
  import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
8
+ import { useMemberVerificationQuery } from '../../client/hooks/useMemberVerificationQuery.js';
5
9
  import { XIcon } from '../../icons/XIcon.js';
6
10
  import { Dialog } from '../../ui/Dialog/index.js';
7
11
  import { Portal } from '../../ui/Portal/index.js';
@@ -15,6 +19,25 @@ import { PersonalInformation } from './PersonalInformation.js';
15
19
  import { useKYC } from './useKYC.js';
16
20
  export function KYC(props) {
17
21
  const kyc = useKYC();
22
+ const accountQuery = useAccountQuery();
23
+ const memberVerification = useMemberVerificationQuery();
24
+ const isBasicInfoVerified = Boolean(accountQuery.data?.realName) &&
25
+ Boolean(accountQuery.data?.birthDay);
26
+ const isMemberUploaded = Boolean(memberVerification.data?.idFrontImage) &&
27
+ Boolean(memberVerification.data?.selfieImage);
28
+ const memberStatus = memberVerification.data?.status;
29
+ //check steps
30
+ useEffect(() => {
31
+ if (!isBasicInfoVerified) {
32
+ kyc.setStep(1);
33
+ }
34
+ else if (memberStatus === 'REJECTED') {
35
+ kyc.setStep(2);
36
+ }
37
+ else if (isBasicInfoVerified) {
38
+ kyc.setStep(isMemberUploaded ? 3 : 2);
39
+ }
40
+ }, [isBasicInfoVerified, isMemberUploaded, memberStatus, kyc]);
18
41
  const globalStore = useGlobalStore(useShallow((ctx) => ({
19
42
  kyc: ctx.kyc,
20
43
  })));
@@ -7,7 +7,7 @@ type EventDetail = {
7
7
  };
8
8
  type UnsubscribeFn = () => void;
9
9
  type Subscriber = (detail: EventDetail) => UnsubscribeFn;
10
- type CaptureSubject = 'SELFIE' | 'ID_DOCUMENT';
10
+ export type CaptureSubject = 'SELFIE' | 'ID_DOCUMENT';
11
11
  export interface UseKYCReturn {
12
12
  step: number;
13
13
  setStep: (step: number) => void;
@@ -1,2 +1,6 @@
1
1
  export declare function dataURLtoBlob(dataURL: string): Blob;
2
2
  export declare const resizeFile: (file: File) => Promise<unknown>;
3
+ export declare const MAX_FILE_SIZE = 10;
4
+ export declare const MAX_NAME_LENGTH = 52;
5
+ export declare const SUPPORTED_FILES: string[];
6
+ export declare function validateFile(file: File): "File too large" | "File name too long" | "Unsupported file" | undefined;
@@ -15,3 +15,27 @@ export const resizeFile = (file) => new Promise((resolve) => {
15
15
  resolve(uri);
16
16
  }, 'file');
17
17
  });
18
+ export const MAX_FILE_SIZE = 10; /* in MB */
19
+ export const MAX_NAME_LENGTH = 52;
20
+ export const SUPPORTED_FILES = [
21
+ /* add more supported ext */
22
+ 'image/png',
23
+ 'image/jpg',
24
+ 'image/jpeg',
25
+ ];
26
+ function removeFilenameExt(subject) {
27
+ const l = subject.split('.');
28
+ l.pop();
29
+ return l.join('.');
30
+ }
31
+ export function validateFile(file) {
32
+ const fileType = file.type;
33
+ const fileSize = file.size / (1024 * 1024);
34
+ const nameLength = removeFilenameExt(file.name).length;
35
+ if (fileSize > MAX_FILE_SIZE)
36
+ return 'File too large';
37
+ if (nameLength > MAX_NAME_LENGTH)
38
+ return 'File name too long';
39
+ if (!SUPPORTED_FILES.includes(fileType))
40
+ return 'Unsupported file';
41
+ }
@@ -15,8 +15,6 @@ import { useSignInMutation } from '../../client/hooks/useSignInMutation.js';
15
15
  import { toaster } from '../../client/utils/toaster.js';
16
16
  import { ArrowLeftIcon } from '../../icons/ArrowLeftIcon.js';
17
17
  import { CheckIcon } from '../../icons/CheckIcon.js';
18
- import { FacebookIcon } from '../../icons/FacebookIcon.js';
19
- import { GoogleIcon } from '../../icons/GoogleIcon.js';
20
18
  import { Button } from '../../ui/Button/index.js';
21
19
  import { Checkbox } from '../../ui/Checkbox/index.js';
22
20
  import { Field } from '../../ui/Field/index.js';
@@ -106,11 +104,7 @@ export function MobileNumberSignIn() {
106
104
  }, children: [_jsx(Checkbox.Control, { children: _jsx(Checkbox.Indicator, { asChild: true, children: _jsx(CheckIcon, {}) }) }), _jsxs(Checkbox.Label, { children: ["I am at least 21 years of age and I accept the", ' ', _jsx(Link, { href: signInProps.termsAndConditionsUrl ??
107
105
  '/terms-and-conditions', onClick: () => {
108
106
  dialog.setOpen(false);
109
- }, className: "text-brand-400 whitespace-nowrap underline underline-offset-2", children: "Terms and Conditions." })] }), _jsx(Checkbox.HiddenInput, {})] }), _jsx(Field.ErrorText, { className: "ml-6 text-xs", children: o.fieldState.error?.message })] })) })] }), _jsxs("div", { className: "mt-6 flex items-center gap-2", children: [_jsx("div", { className: "bg-text-tertiary-600/50 h-px flex-grow" }), _jsx("span", { className: "text-text-tertiary-600 text-sm font-semibold", children: "Log in directly with" }), _jsx("div", { className: "bg-text-tertiary-600/50 h-px flex-grow" })] }), _jsxs("div", { className: "mt-6", children: [_jsxs(Button, { variant: "outline", colorScheme: "gray", onClick: () => {
110
- dialog.setOpen(false);
111
- }, className: "border-border-primary bg-bg-primary text-text-secondary-700", children: [_jsx(GoogleIcon, { className: "size-6" }), "Continue with Google"] }), _jsxs(Button, { variant: "outline", colorScheme: "gray", onClick: () => {
112
- dialog.setOpen(false);
113
- }, className: "border-border-primary bg-bg-primary text-text-secondary-700 mt-lg", children: [_jsx(FacebookIcon, { className: "size-6" }), "Continue with Facebook"] })] }), _jsxs("div", { className: "mt-6 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-tertiary-600", children: "Don't have an account?" }), _jsx("button", { type: "submit", className: "text-button-tertiary-fg font-semibold", onClick: () => {
107
+ }, className: "text-brand-400 whitespace-nowrap underline underline-offset-2", children: "Terms and Conditions." })] }), _jsx(Checkbox.HiddenInput, {})] }), _jsx(Field.ErrorText, { className: "ml-6 text-xs", children: o.fieldState.error?.message })] })) })] }), _jsxs("div", { className: "mt-6 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-tertiary-600", children: "Don't have an account?" }), _jsx("button", { type: "submit", className: "text-button-tertiary-fg font-semibold", onClick: () => {
114
108
  dialog.setOpen(false);
115
109
  globalStore.signUp.setOpen(true);
116
110
  }, 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 }) => {
@@ -13,8 +13,6 @@ import { SECRET_QUESTIONS, SECRET_QUESTIONS_MAP } from '../../constants/index.js
13
13
  import { CheckIcon } from '../../icons/CheckIcon.js';
14
14
  import { EyeIcon } from '../../icons/EyeIcon.js';
15
15
  import { EyeOffIcon } from '../../icons/EyeOffIcon.js';
16
- import { FacebookIcon } from '../../icons/FacebookIcon.js';
17
- import { GoogleIcon } from '../../icons/GoogleIcon.js';
18
16
  import { Button } from '../../ui/Button/index.js';
19
17
  import { Checkbox } from '../../ui/Checkbox/index.js';
20
18
  import { Field } from '../../ui/Field/index.js';
@@ -129,11 +127,7 @@ export function NameAndPasswordSignIn() {
129
127
  }, children: [_jsx(Checkbox.Control, { children: _jsx(Checkbox.Indicator, { asChild: true, children: _jsx(CheckIcon, {}) }) }), _jsxs(Checkbox.Label, { children: ["I am at least 21 years of age and I accept the", ' ', _jsx(Link, { href: signInProps.termsAndConditionsUrl ??
130
128
  '/terms-and-conditions', onClick: () => {
131
129
  dialog.setOpen(false);
132
- }, className: "text-brand-400 whitespace-nowrap underline underline-offset-2", children: "Terms and Conditions." })] }), _jsx(Checkbox.HiddenInput, {})] }), _jsx(Field.ErrorText, { className: "ml-6 text-xs", children: o.fieldState.error?.message })] })) })] }), _jsxs("div", { className: "mt-6 flex items-center gap-2", children: [_jsx("div", { className: "bg-text-tertiary-600/50 h-px flex-grow" }), _jsx("span", { className: "text-text-tertiary-600 text-sm font-semibold", children: "Log in directly with" }), _jsx("div", { className: "bg-text-tertiary-600/50 h-px flex-grow" })] }), _jsxs("div", { className: "mt-6", children: [_jsxs(Button, { variant: "outline", colorScheme: "gray", onClick: () => {
133
- dialog.setOpen(false);
134
- }, className: "border-border-primary bg-bg-primary text-text-secondary-700", children: [_jsx(GoogleIcon, { className: "size-6" }), "Continue with Google"] }), _jsxs(Button, { variant: "outline", colorScheme: "gray", onClick: () => {
135
- dialog.setOpen(false);
136
- }, className: "border-border-primary bg-bg-primary text-text-secondary-700 mt-lg", children: [_jsx(FacebookIcon, { className: "size-6" }), "Continue with Facebook"] })] }), _jsxs("div", { className: "mt-6 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-tertiary-600", children: "Don't have an account?" }), _jsx("button", { type: "submit", className: "text-button-tertiary-fg font-semibold", onClick: () => {
130
+ }, className: "text-brand-400 whitespace-nowrap underline underline-offset-2", children: "Terms and Conditions." })] }), _jsx(Checkbox.HiddenInput, {})] }), _jsx(Field.ErrorText, { className: "ml-6 text-xs", children: o.fieldState.error?.message })] })) })] }), _jsxs("div", { className: "mt-6 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-tertiary-600", children: "Don't have an account?" }), _jsx("button", { type: "submit", className: "text-button-tertiary-fg font-semibold", onClick: () => {
137
131
  dialog.setOpen(false);
138
132
  globalStore.signUp.setOpen(true);
139
133
  }, children: "Sign up" })] })] }));
@@ -12,6 +12,7 @@ import { useRef, useState } from 'react';
12
12
  import { Controller, useForm } from 'react-hook-form';
13
13
  import { z } from 'zod';
14
14
  import { useShallow } from 'zustand/shallow';
15
+ import { useControllableState } from '../../client/hooks/useControllableState.js';
15
16
  import { useCooldown } from '../../client/hooks/useCooldown.js';
16
17
  import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
17
18
  import { useLocaleInfo } from '../../client/hooks/useLocaleInfo.js';
@@ -28,8 +29,6 @@ import { CheckIcon } from '../../icons/CheckIcon.js';
28
29
  import { ChevronDownIcon } from '../../icons/ChevronDownIcon.js';
29
30
  import { ChevronLeftIcon } from '../../icons/ChevronLeftIcon.js';
30
31
  import { ChevronRightIcon } from '../../icons/ChevronRightIcon.js';
31
- import { FacebookIcon } from '../../icons/FacebookIcon.js';
32
- import { GoogleIcon } from '../../icons/GoogleIcon.js';
33
32
  import { ObjectType } from '../../services/ObjectType.js';
34
33
  import { Button } from '../../ui/Button/index.js';
35
34
  import { Checkbox } from '../../ui/Checkbox/index.js';
@@ -149,18 +148,17 @@ export function SignUpForm() {
149
148
  }
150
149
  }), 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: {
151
150
  paddingLeft: `calc(2.75rem + ${localeInfo.mobileNumber.areaCode.length}ch)`,
152
- }, ...step1Form.register('mobileNumber') })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.mobileNumber?.message })] }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.realName, className: "mt-xl", children: [_jsx(Field.Label, { children: "Real Name" }), _jsx(Field.Input, { placeholder: "Enter your real name", ...step1Form.register('realName') }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.realName?.message })] }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.birthDay, className: "mt-xl", children: [_jsxs(DatePicker.Root, { size: "md", variant: "outline", placeholder: "MM/DD/YYYY", lazyMount: true, unmountOnExit: true, fixedWeeks: true, selectionMode: "single", value: birthDay ? [parseDate(birthDay)] : [], onValueChange: (details) => {
153
- const value = details.value.at(0)?.toDate(DEVICE_TIMEZONE);
154
- if (value) {
155
- step1Form.setValue('birthDay', value, {
156
- shouldDirty: true,
157
- shouldTouch: true,
158
- shouldValidate: true,
159
- });
160
- }
161
- }, children: [_jsx(DatePicker.Label, { children: "Date of Birth" }), _jsxs(DatePicker.Control, { children: [_jsx(DatePicker.Input, { onBlur: () => {
162
- step1Form.trigger('birthDay');
163
- } }), _jsx(DatePicker.Trigger, { children: _jsx(CalendarIcon, {}) })] }), _jsx(DatePicker.Positioner, { children: _jsx(DatePicker.Content, { children: _jsx(DatePicker.View, { view: "day", children: _jsx(DatePicker.Context, { children: (api) => (_jsxs(_Fragment, { children: [_jsxs(DatePicker.ViewControl, { children: [_jsx(DatePicker.PrevTrigger, { children: _jsx(ChevronLeftIcon, {}) }), _jsx(DatePicker.ViewTrigger, { children: _jsx(DatePicker.RangeText, {}) }), _jsx(DatePicker.NextTrigger, { children: _jsx(ChevronRightIcon, {}) })] }), _jsxs(DatePicker.Table, { children: [_jsx(DatePicker.TableHead, { children: _jsx(DatePicker.TableRow, { children: api.weekDays.map((weekDay, id) => (_jsx(DatePicker.TableHeader, { children: weekDay.short.substring(0, 2) }, id))) }) }), _jsx(DatePicker.TableBody, { children: api.weeks.map((week, id) => (_jsx(DatePicker.TableRow, { children: week.map((day, id) => (_jsx(DatePicker.TableCell, { value: day, children: _jsx(DatePicker.TableCellTrigger, { children: day.day }) }, id))) }, id))) })] })] })) }) }) }) })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.birthDay?.message })] }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.branchCode, className: "mt-xl", children: [_jsxs(Select.Root, { value: [branchCode], onValueChange: (details) => {
151
+ }, ...step1Form.register('mobileNumber') })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.mobileNumber?.message })] }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.realName, className: "mt-xl", children: [_jsx(Field.Label, { children: "Real Name" }), _jsx(Field.Input, { placeholder: "Enter your real name", ...step1Form.register('realName') }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.realName?.message })] }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.birthDay, className: "mt-xl", children: [_jsx(DateOfBirthField, { value: birthDay, onChange: (value) => {
152
+ if (!value)
153
+ return;
154
+ step1Form.setValue('birthDay', value, {
155
+ shouldDirty: true,
156
+ shouldTouch: true,
157
+ shouldValidate: true,
158
+ });
159
+ }, onBlur: () => {
160
+ step1Form.trigger('birthDay');
161
+ } }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.birthDay?.message })] }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.branchCode, className: "mt-xl", children: [_jsxs(Select.Root, { value: [branchCode], onValueChange: (details) => {
164
162
  step1Form.setValue('branchCode', details.value.at(0) ?? BRANCH_CODES[0].code, {
165
163
  shouldDirty: true,
166
164
  shouldTouch: true,
@@ -174,11 +172,7 @@ export function SignUpForm() {
174
172
  }, children: [_jsx(Checkbox.Control, { children: _jsx(Checkbox.Indicator, { asChild: true, children: _jsx(CheckIcon, {}) }) }), _jsxs(Checkbox.Label, { children: ["I am at least 21 years of age and I accept the", ' ', _jsx(Link, { href: signUpProps.termsAndConditionsUrl ??
175
173
  '/terms-and-conditions', onClick: () => {
176
174
  dialog.setOpen(false);
177
- }, className: "text-brand-400 whitespace-nowrap underline underline-offset-2", children: "Terms and Conditions." })] }), ' ', _jsx(Checkbox.HiddenInput, {})] }), _jsx(Field.ErrorText, { className: "ml-6 text-xs", children: o.fieldState.error?.message })] })) }), _jsx(Button, { type: "submit", className: "mt-3xl", disabled: step1Form.formState.isSubmitting, children: "Create Account" })] }), _jsxs("div", { className: "mt-6 flex items-center gap-2", children: [_jsx("div", { className: "bg-text-tertiary-600/50 h-px flex-grow" }), _jsx("span", { className: "text-text-tertiary-600 text-sm font-semibold", children: "Log in directly with" }), _jsx("div", { className: "bg-text-tertiary-600/50 h-px flex-grow" })] }), _jsxs("div", { className: "mt-6", children: [_jsxs(Button, { variant: "outline", colorScheme: "gray", onClick: () => {
178
- dialog.setOpen(false);
179
- }, className: "border-border-primary bg-bg-primary text-text-secondary-700", children: [_jsx(GoogleIcon, { className: "size-6" }), "Sign in with Google"] }), _jsxs(Button, { variant: "outline", colorScheme: "gray", onClick: () => {
180
- dialog.setOpen(false);
181
- }, className: "border-border-primary bg-bg-primary text-text-secondary-700 mt-lg", children: [_jsx(FacebookIcon, { className: "size-6" }), "Sign in with Facebook"] })] }), _jsxs("div", { className: "mt-6 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-tertiary-600", children: "Already have an account?" }), _jsx("button", { type: "submit", className: "text-button-tertiary-fg font-semibold", onClick: () => {
175
+ }, className: "text-brand-400 whitespace-nowrap underline underline-offset-2", children: "Terms and Conditions." })] }), ' ', _jsx(Checkbox.HiddenInput, {})] }), _jsx(Field.ErrorText, { className: "ml-6 text-xs", children: o.fieldState.error?.message })] })) }), _jsx(Button, { type: "submit", className: "mt-3xl", disabled: step1Form.formState.isSubmitting, children: "Create Account" })] }), _jsxs("div", { className: "mt-6 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-tertiary-600", children: "Already have an account?" }), _jsx("button", { type: "submit", className: "text-button-tertiary-fg font-semibold", onClick: () => {
182
176
  dialog.setOpen(false);
183
177
  signInStore.setOpen(true);
184
178
  }, children: "Log In" })] })] })), 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 }) => {
@@ -255,3 +249,18 @@ export function SignUpForm() {
255
249
  }, children: [_jsx(ArrowLeftIcon, { className: "size-5" }), "Back"] })] })] }))] }));
256
250
  }
257
251
  const DEVICE_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
252
+ function DateOfBirthField(props) {
253
+ const [value, setValue] = useControllableState({
254
+ value: props.value,
255
+ defaultValue: props.defaultValue ?? null,
256
+ onChange: props.onChange,
257
+ });
258
+ return (_jsxs(DatePicker.Root, { size: "md", variant: "outline", placeholder: "MM/DD/YYYY", lazyMount: true, unmountOnExit: true, fixedWeeks: true, selectionMode: "single", value: value ? [parseDate(value)] : [], onValueChange: (details) => {
259
+ const value = details.value.at(0)?.toDate(DEVICE_TIMEZONE);
260
+ setValue(value ?? null);
261
+ }, children: [_jsx(DatePicker.Label, { children: "Date of Birth" }), _jsxs(DatePicker.Control, { children: [_jsx(DatePicker.Input, { onBlur: props.onBlur, onFocus: props.onFocus }), _jsx(DatePicker.Trigger, { children: _jsx(CalendarIcon, {}) })] }), _jsx(DatePicker.Positioner, { children: _jsxs(DatePicker.Content, { children: [_jsx(DatePicker.View, { view: "day", children: _jsx(DatePicker.Context, { children: (api) => (_jsxs(_Fragment, { children: [_jsxs(DatePicker.ViewControl, { children: [_jsx(DatePicker.PrevTrigger, { children: _jsx(ChevronLeftIcon, {}) }), _jsx(DatePicker.ViewTrigger, { children: _jsx(DatePicker.RangeText, {}) }), _jsx(DatePicker.NextTrigger, { children: _jsx(ChevronRightIcon, {}) })] }), _jsxs(DatePicker.Table, { children: [_jsx(DatePicker.TableHead, { children: _jsx(DatePicker.TableRow, { children: api.weekDays.map((weekDay, id) => (_jsx(DatePicker.TableHeader, { children: weekDay.short.substring(0, 2) }, id))) }) }), _jsx(DatePicker.TableBody, { children: api.weeks.map((week, id) => (_jsx(DatePicker.TableRow, { children: week.map((day, id) => (_jsx(DatePicker.TableCell, { value: day, children: _jsx(DatePicker.TableCellTrigger, { children: day.day }) }, id))) }, id))) })] })] })) }) }), _jsx(DatePicker.View, { view: "month", children: _jsx(DatePicker.Context, { children: (datePicker) => (_jsxs(_Fragment, { children: [_jsxs(DatePicker.ViewControl, { children: [_jsx(DatePicker.PrevTrigger, { children: _jsx(ChevronLeftIcon, {}) }), _jsx(DatePicker.ViewTrigger, { children: _jsx(DatePicker.RangeText, {}) }), _jsx(DatePicker.NextTrigger, { children: _jsx(ChevronRightIcon, {}) })] }), _jsx(DatePicker.Table, { children: _jsx(DatePicker.TableBody, { children: datePicker
262
+ .getMonthsGrid({ columns: 4, format: 'short' })
263
+ .map((months, id) => (_jsx(DatePicker.TableRow, { children: months.map((month, id) => (_jsx(DatePicker.TableCell, { value: month.value, children: _jsx(DatePicker.TableCellTrigger, { className: "w-[4.425rem]", children: month.label }) }, id))) }, id))) }) })] })) }) }), _jsx(DatePicker.View, { view: "year", children: _jsx(DatePicker.Context, { children: (datePicker) => (_jsxs(_Fragment, { children: [_jsxs(DatePicker.ViewControl, { children: [_jsx(DatePicker.PrevTrigger, { children: _jsx(ChevronLeftIcon, {}) }), _jsx(DatePicker.ViewTrigger, { children: _jsx(DatePicker.RangeText, {}) }), _jsx(DatePicker.NextTrigger, { children: _jsx(ChevronRightIcon, {}) })] }), _jsx(DatePicker.Table, { children: _jsx(DatePicker.TableBody, { children: datePicker
264
+ .getYearsGrid({ columns: 4 })
265
+ .map((years, id) => (_jsx(DatePicker.TableRow, { children: years.map((year, id) => (_jsx(DatePicker.TableCell, { value: year.value, children: _jsx(DatePicker.TableCellTrigger, { className: "w-[4.425rem]", children: year.label }) }, id))) }, id))) }) })] })) }) })] }) })] }));
266
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opexa/portal-components",
3
- "version": "0.0.138",
3
+ "version": "0.0.140",
4
4
  "exports": {
5
5
  "./ui/*": {
6
6
  "types": "./dist/ui/*/index.d.ts",
@@ -77,7 +77,7 @@
77
77
  "release": "release-it"
78
78
  },
79
79
  "dependencies": {
80
- "@ark-ui/react": "^5.14.1",
80
+ "@ark-ui/react": "^5.14.2",
81
81
  "@date-fns/tz": "^1.2.0",
82
82
  "@next/third-parties": "^15.3.4",
83
83
  "@tailwindcss/typography": "^0.5.16",