@opexa/portal-components 0.0.258 → 0.0.260

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.
Files changed (59) hide show
  1. package/dist/client/hooks/useCamera.d.ts +11 -16
  2. package/dist/client/hooks/useCamera.js +29 -57
  3. package/dist/client/hooks/useUploadFileMutation.d.ts +4 -0
  4. package/dist/client/hooks/useUploadFileMutation.js +24 -0
  5. package/dist/client/hooks/useUploadImageFileMutation.d.ts +2 -2
  6. package/dist/client/hooks/useUploadImageFileMutation.js +2 -2
  7. package/dist/client/hooks/useUploadPrivateImageFileMutation.d.ts +4 -0
  8. package/dist/client/hooks/useUploadPrivateImageFileMutation.js +24 -0
  9. package/dist/components/Disclaimer/DisclaimerV2.js +2 -2
  10. package/dist/components/Disclaimer/TermsOfUse.js +0 -4
  11. package/dist/components/Search/Search.lazy.js +6 -2
  12. package/dist/components/SignIn/MobileNumberSignIn.js +1 -0
  13. package/dist/components/SignIn/utils.d.ts +1 -0
  14. package/dist/components/SignIn/utils.js +4 -0
  15. package/dist/components/shared/IdFrontImageField/IdFrontImageField.client.d.ts +15 -0
  16. package/dist/components/shared/IdFrontImageField/IdFrontImageField.client.js +140 -0
  17. package/dist/components/shared/IdFrontImageField/IdFrontImageField.d.ts +2 -0
  18. package/dist/components/shared/IdFrontImageField/IdFrontImageField.js +11 -0
  19. package/dist/components/shared/IdFrontImageField/IdFrontImageFieldContext.d.ts +6 -0
  20. package/dist/components/shared/IdFrontImageField/IdFrontImageFieldContext.js +2 -0
  21. package/dist/components/shared/IdFrontImageField/index.d.ts +1 -0
  22. package/dist/components/shared/IdFrontImageField/index.js +1 -0
  23. package/dist/components/shared/IdFrontImageField/useIdFrontImageField.d.ts +22 -0
  24. package/dist/components/shared/IdFrontImageField/useIdFrontImageField.js +111 -0
  25. package/dist/components/shared/SelfieImageField/SelfieImageField.client.d.ts +15 -0
  26. package/dist/components/shared/SelfieImageField/SelfieImageField.client.js +170 -0
  27. package/dist/components/shared/SelfieImageField/SelfieImageField.d.ts +2 -0
  28. package/dist/components/shared/SelfieImageField/SelfieImageField.js +11 -0
  29. package/dist/components/shared/SelfieImageField/SelfieImageFieldContext.d.ts +6 -0
  30. package/dist/components/shared/SelfieImageField/SelfieImageFieldContext.js +2 -0
  31. package/dist/components/shared/SelfieImageField/index.d.ts +1 -0
  32. package/dist/components/shared/SelfieImageField/index.js +1 -0
  33. package/dist/components/shared/SelfieImageField/useSelfieImageField.d.ts +23 -0
  34. package/dist/components/shared/SelfieImageField/useSelfieImageField.js +208 -0
  35. package/dist/handlers/deleteSession.d.ts +9 -0
  36. package/dist/handlers/deleteSession.js +19 -0
  37. package/dist/handlers/getHealthCheck.d.ts +9 -0
  38. package/dist/handlers/getHealthCheck.js +16 -0
  39. package/dist/handlers/getSession.d.ts +10 -0
  40. package/dist/handlers/getSession.js +63 -0
  41. package/dist/handlers/index.d.ts +40 -0
  42. package/dist/handlers/index.js +56 -0
  43. package/dist/handlers/patchSession.d.ts +10 -0
  44. package/dist/handlers/patchSession.js +26 -0
  45. package/dist/handlers/postAuthenticate.d.ts +10 -0
  46. package/dist/handlers/postAuthenticate.js +51 -0
  47. package/dist/handlers/postSession.d.ts +17 -0
  48. package/dist/handlers/postSession.js +78 -0
  49. package/dist/handlers/postTransformIdFrontImage.d.ts +3 -0
  50. package/dist/handlers/postTransformIdFrontImage.js +67 -0
  51. package/dist/handlers/postTransformSelfieImage.d.ts +3 -0
  52. package/dist/handlers/postTransformSelfieImage.js +71 -0
  53. package/dist/services/file.d.ts +17 -12
  54. package/dist/services/file.js +21 -1
  55. package/dist/services/queries.d.ts +2 -0
  56. package/dist/services/queries.js +36 -0
  57. package/dist/utils/mutationKeys.d.ts +2 -0
  58. package/dist/utils/mutationKeys.js +8 -0
  59. package/package.json +4 -3
@@ -9,22 +9,10 @@ export interface CameraData {
9
9
  file: File;
10
10
  image: HTMLImageElement;
11
11
  }
12
- export interface CameraCropPoints {
13
- x: number;
14
- y: number;
15
- width: number;
16
- height: number;
17
- }
18
- export type ScaleFactor = 1 | 1.15 | 1.25 | 1.5 | 1.75 | 2 | 2.15 | 2.25 | 2.5 | 2.75 | 3;
19
- export interface CameraSnapOptions {
20
- crop?: CameraCropPoints;
21
- /** @default 2 */
22
- scale?: ScaleFactor;
23
- }
24
12
  export interface UseCameraReturn<T extends string = never> {
25
13
  open(): Promise<void>;
26
14
  close(): Promise<void>;
27
- snap(options?: CameraSnapOptions): Promise<void>;
15
+ snap(): Promise<void>;
28
16
  reopen(): Promise<void>;
29
17
  reset(): Promise<void>;
30
18
  data: CameraData | null;
@@ -34,11 +22,18 @@ export interface UseCameraReturn<T extends string = never> {
34
22
  videoRef: React.RefObject<HTMLVideoElement | null>;
35
23
  videoProps: ComponentPropsWithRef<'video'>;
36
24
  }
37
- export interface CameraTransformData extends CameraData {
25
+ export type CameraTransformResult<T extends string = never> = {
26
+ ok: false;
27
+ error: CameraError<T>;
28
+ } | {
29
+ ok: true;
30
+ data: CameraData;
31
+ };
32
+ export interface CameraTransformContext extends CameraData {
33
+ video: HTMLVideoElement;
38
34
  canvas: HTMLCanvasElement;
39
35
  }
40
36
  export interface UseCameraOptions<T extends string = never> extends MediaStreamConstraints {
41
- transform?(data: CameraTransformData): CameraData | Promise<CameraData>;
42
- validate?(data: CameraData): CameraError<T> | void | null | undefined | Promise<CameraError<T> | void | null | undefined>;
37
+ transform?(context: CameraTransformContext): CameraTransformResult<T> | Promise<CameraTransformResult<T>>;
43
38
  }
44
39
  export declare function useCamera<T extends string = never>(options?: UseCameraOptions<T>): UseCameraReturn<T>;
@@ -1,9 +1,8 @@
1
- import { isBoolean, isFunction, isObject } from 'lodash-es';
1
+ import { isBoolean, isObject } from 'lodash-es';
2
2
  import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
3
3
  import invariant from 'tiny-invariant';
4
4
  import { useMediaQuery } from 'usehooks-ts';
5
5
  export function useCamera(options = {}) {
6
- const { transform, validate, ...constraints } = options;
7
6
  const videoRef = useRef(null);
8
7
  const streamRef = useRef(null);
9
8
  const [data, setData] = useState(null);
@@ -30,7 +29,7 @@ export function useCamera(options = {}) {
30
29
  const result = await navigator.mediaDevices.getUserMedia({
31
30
  audio: false,
32
31
  preferCurrentTab: true,
33
- ...constraints,
32
+ ...options,
34
33
  video: {
35
34
  facingMode: 'user',
36
35
  noiseSuppression: true,
@@ -47,14 +46,14 @@ export function useCamera(options = {}) {
47
46
  max: 120,
48
47
  ideal: 90,
49
48
  },
50
- ...(isBoolean(constraints.video) ? {} : constraints.video),
49
+ ...(isBoolean(options.video) ? {} : options.video),
51
50
  },
52
51
  });
53
52
  node.srcObject = result;
54
53
  streamRef.current = result;
55
- setMirror(!isObject(constraints.video)
54
+ setMirror(!isObject(options.video)
56
55
  ? true
57
- : constraints.video.facingMode === 'environment'
56
+ : options.video.facingMode === 'environment'
58
57
  ? false
59
58
  : true);
60
59
  }
@@ -85,7 +84,7 @@ export function useCamera(options = {}) {
85
84
  finally {
86
85
  setLoading(false);
87
86
  }
88
- }, [constraints, desktop]);
87
+ }, [options, desktop]);
89
88
  const close = useCallback(() => {
90
89
  setData(null);
91
90
  setError(null);
@@ -97,7 +96,7 @@ export function useCamera(options = {}) {
97
96
  resolve();
98
97
  });
99
98
  }, [setData, setError]);
100
- const snap = useCallback(async (opts = {}) => {
99
+ const snap = useCallback(async () => {
101
100
  setData(null);
102
101
  setError(null);
103
102
  setSnapping(true);
@@ -107,44 +106,15 @@ export function useCamera(options = {}) {
107
106
  invariant(video, 'Could not find video element');
108
107
  invariant(context, 'Could not get canvas context');
109
108
  video.currentTime = 1;
110
- const videoRect = video.getBoundingClientRect();
111
- const lowResolution = video.videoWidth < 640 || video.videoHeight < 480;
112
- const hdResolution = video.videoWidth >= 1280 && video.videoHeight >= 720;
113
- const highResolution = video.videoWidth >= 1920 && video.videoHeight >= 1080;
114
- const scaleFactor = lowResolution
115
- ? (opts.scale ?? (opts.crop ? 2.15 : 2))
116
- : highResolution
117
- ? opts.crop
118
- ? 1.25
119
- : 1.15
120
- : hdResolution
121
- ? opts.crop
122
- ? 1.5
123
- : 1.25
124
- : opts.crop
125
- ? 1.5
126
- : 2;
127
- let x = 0;
128
- let y = 0;
129
- let w = video.videoWidth;
130
- let h = video.videoHeight;
131
- if (opts.crop) {
132
- const scaleX = video.videoWidth / videoRect.width;
133
- const scaleY = video.videoHeight / videoRect.height;
134
- x = (opts.crop.x - videoRect.x) * scaleX;
135
- y = (opts.crop.y - videoRect.y) * scaleY;
136
- w = opts.crop.width * scaleX;
137
- h = opts.crop.height * scaleY;
138
- }
139
- canvas.width = w * scaleFactor;
140
- canvas.height = h * scaleFactor;
109
+ canvas.width = video.videoWidth;
110
+ canvas.height = video.videoHeight;
141
111
  context.imageSmoothingEnabled = true;
142
112
  context.imageSmoothingQuality = 'high';
143
113
  if (mirror) {
144
114
  context.translate(canvas.width, 0);
145
115
  context.scale(-1, 1);
146
116
  }
147
- context.drawImage(video, x, y, w, h, 0, 0, w * scaleFactor, h * scaleFactor);
117
+ context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
148
118
  canvas.toBlob(async (blob) => {
149
119
  if (!blob) {
150
120
  setSnapping(false);
@@ -170,26 +140,28 @@ export function useCamera(options = {}) {
170
140
  image.onerror = () => reject();
171
141
  });
172
142
  }
173
- const data = isFunction(transform)
174
- ? await transform({
175
- url,
176
- file,
177
- image,
178
- canvas,
179
- })
180
- : {
181
- url,
182
- file,
183
- image,
184
- };
185
- const error = await validate?.(data);
186
- if (error)
187
- setError(error);
188
- else
143
+ const data = {
144
+ url,
145
+ file,
146
+ image,
147
+ };
148
+ if (!options.transform) {
189
149
  setData(data);
150
+ setSnapping(false);
151
+ return;
152
+ }
153
+ const transformResult = await options.transform({
154
+ ...data,
155
+ video,
156
+ canvas,
157
+ });
158
+ if (transformResult.ok)
159
+ setData(transformResult.data);
160
+ else
161
+ setError(transformResult.error);
190
162
  setSnapping(false);
191
163
  }, 'image/jpeg', 1);
192
- }, [mirror, transform, validate]);
164
+ }, [mirror, options]);
193
165
  const reset = useCallback(() => {
194
166
  setData(null);
195
167
  setError(null);
@@ -0,0 +1,4 @@
1
+ import type { SetOptional } from 'type-fest';
2
+ import { type UploadFileInput } from '../../services/file';
3
+ import type { Mutation } from '../../types';
4
+ export declare const useUploadFileMutation: Mutation<string, SetOptional<UploadFileInput, 'id'>>;
@@ -0,0 +1,24 @@
1
+ import { ObjectId } from '@opexa/object-id';
2
+ import { useMutation } from '@tanstack/react-query';
3
+ import invariant from 'tiny-invariant';
4
+ import { uploadFile } from '../../services/file.js';
5
+ import { ObjectType } from '../../services/ObjectType.js';
6
+ import { getUploadFileMutationKey } from '../../utils/mutationKeys.js';
7
+ import { getSession } from '../services/getSession.js';
8
+ export const useUploadFileMutation = (config) => {
9
+ return useMutation({
10
+ ...config,
11
+ mutationKey: getUploadFileMutationKey(),
12
+ mutationFn: async (input) => {
13
+ const session = await getSession();
14
+ invariant(session.status === 'authenticated');
15
+ const id = input.id ?? ObjectId.generate(ObjectType.File).toString();
16
+ await uploadFile({ ...input, id }, {
17
+ headers: {
18
+ Authorization: `Bearer ${session.token}`,
19
+ },
20
+ });
21
+ return id;
22
+ },
23
+ });
24
+ };
@@ -1,4 +1,4 @@
1
1
  import type { SetOptional } from 'type-fest';
2
- import { type UploadPrivateImageFileInput } from '../../services/file';
2
+ import { type UploadFileInput } from '../../services/file';
3
3
  import type { Mutation } from '../../types';
4
- export declare const useUploadImageFileMutation: Mutation<string, SetOptional<UploadPrivateImageFileInput, 'id'>>;
4
+ export declare const useUploadImageFileMutation: Mutation<string, SetOptional<UploadFileInput, 'id'>>;
@@ -1,7 +1,7 @@
1
1
  import { ObjectId } from '@opexa/object-id';
2
2
  import { useMutation } from '@tanstack/react-query';
3
3
  import invariant from 'tiny-invariant';
4
- import { uploadPrivateImageFile, } from '../../services/file.js';
4
+ import { uploadImageFile } from '../../services/file.js';
5
5
  import { ObjectType } from '../../services/ObjectType.js';
6
6
  import { getUploadImageFileMutationKey } from '../../utils/mutationKeys.js';
7
7
  import { getSession } from '../services/getSession.js';
@@ -13,7 +13,7 @@ export const useUploadImageFileMutation = (config) => {
13
13
  const session = await getSession();
14
14
  invariant(session.status === 'authenticated');
15
15
  const id = input.id ?? ObjectId.generate(ObjectType.File).toString();
16
- await uploadPrivateImageFile({ ...input, id }, {
16
+ await uploadImageFile({ ...input, id }, {
17
17
  headers: {
18
18
  Authorization: `Bearer ${session.token}`,
19
19
  },
@@ -0,0 +1,4 @@
1
+ import type { SetOptional } from 'type-fest';
2
+ import { type UploadFileInput } from '../../services/file';
3
+ import type { Mutation } from '../../types';
4
+ export declare const useUploadPrivateImageFileMutation: Mutation<string, SetOptional<UploadFileInput, 'id'>>;
@@ -0,0 +1,24 @@
1
+ import { ObjectId } from '@opexa/object-id';
2
+ import { useMutation } from '@tanstack/react-query';
3
+ import invariant from 'tiny-invariant';
4
+ import { uploadPrivateImageFile, } from '../../services/file.js';
5
+ import { ObjectType } from '../../services/ObjectType.js';
6
+ import { getUploadPrivateImageFileMutationKey } from '../../utils/mutationKeys.js';
7
+ import { getSession } from '../services/getSession.js';
8
+ export const useUploadPrivateImageFileMutation = (config) => {
9
+ return useMutation({
10
+ ...config,
11
+ mutationKey: getUploadPrivateImageFileMutationKey(),
12
+ mutationFn: async (input) => {
13
+ const session = await getSession();
14
+ invariant(session.status === 'authenticated');
15
+ const id = input.id ?? ObjectId.generate(ObjectType.File).toString();
16
+ await uploadPrivateImageFile({ ...input, id }, {
17
+ headers: {
18
+ Authorization: `Bearer ${session.token}`,
19
+ },
20
+ });
21
+ return id;
22
+ },
23
+ });
24
+ };
@@ -17,7 +17,7 @@ export function DisclaimerV2(props) {
17
17
  const acceptTriggerRef = useRef(null);
18
18
  const [isResponsibleGamingOpen, setResponsibleGamingOpen] = useState(false);
19
19
  const [isTermsOfUseOpen, setTermsOfUseOpen] = useState(false);
20
- const [isChecked, setIsChecked] = useState(true);
20
+ const [isChecked, setIsChecked] = useState(false);
21
21
  return (_jsxs(_Fragment, { children: [_jsx(ResponsibleGaming, { responsibleGamingContent: props.responsibleGamingContent, logo: props.logo, open: isResponsibleGamingOpen, onCloseAction: () => setResponsibleGamingOpen(false), responsibleGamingLogo: props.responsibleGamingLogo }), _jsx(TermsOfUse, { termsOfUseContent: props.termsOfUseContent, siteName: props.siteName, logo: props.logo, open: isTermsOfUseOpen, onCloseAction: () => setTermsOfUseOpen(false), responsibleGamingLogo: props.responsibleGamingLogo }), _jsx(Dialog.Root, { open: disclaimer.open, lazyMount: true, unmountOnExit: true, closeOnEscape: false, closeOnInteractOutside: false, initialFocusEl: () => acceptTriggerRef.current, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { children: _jsxs(Dialog.Content, { className: "flex h-full w-full flex-col items-start gap-y-4 overflow-y-auto p-3xl lg:mx-auto lg:h-auto lg:w-[400px] lg:rounded-xl", children: [_jsx(Image, { src: props.logo, alt: "", width: 200, height: 100, className: "mx-auto h-auto w-[120px]", draggable: false }), _jsxs("div", { className: "flex w-full items-center justify-center gap-3xl", children: [_jsx(Image, { src: pagcorLogo, alt: "", height: 43, width: 88, className: "h-[43px] w-auto shrink-0", draggable: false }), _jsx(Image, { src: props.responsibleGamingLogo ?? responsibleGamingLogo, alt: "", height: 50, width: 186, className: "h-[50px] w-auto shrink-0", draggable: false })] }), _jsx("div", { className: "text-sm", children: "The following individuals are prohibited from registering or participating in this online gaming platform." }), _jsx("div", { className: "w-full grow", children: _jsxs(Dialog.Description, { className: "space-y-4 text-sm", children: [_jsxs("ul", { className: "space-y-1.5 text-left", children: [_jsxs("li", { className: "flex gap-4", children: [_jsx(Circle, {}), "Persons below the legal gambling age of 21."] }), _jsxs("li", { className: "flex gap-4", children: [_jsx(Circle, {}), "Any government official or employee directly involved in the operations of the government or its agencies."] }), _jsxs("li", { className: "flex gap-4", children: [_jsx(Circle, {}), "Member of the Armed Forces of the Philippines, including the Army, Navy, Air Force and the Philippine National Police."] }), _jsxs("li", { className: "flex gap-4", children: [_jsx(Circle, {}), "Individuals listed in PAGCOR's National Database of Restricted Persons (NDRP)"] }), _jsxs("li", { className: "flex gap-4", children: [_jsx(Circle, {}), "Holders of a valid Gaming Employment License (GEL)"] })] }), !props.hasNoCheckbox ? (_jsxs(Checkbox.Root, { checked: isChecked, onCheckedChange: (e) => {
22
22
  setIsChecked(!!e.checked);
23
23
  }, children: [_jsx(Checkbox.Control, { children: _jsx(Checkbox.Indicator, { asChild: true, children: _jsx(CheckIcon, {}) }) }), _jsxs(Checkbox.Label, { children: ["I have read and agreed to ", props.siteName, ' ', _jsx("button", { type: "button", className: "text-brand-400 underline underline-offset-2", onClick: () => {
@@ -35,7 +35,7 @@ export function DisclaimerV2(props) {
35
35
  else {
36
36
  window.location.href = props.redirectUrlOnNoConsent;
37
37
  }
38
- }, variant: "outline", children: "Exit" }), _jsx(Button, { ref: acceptTriggerRef, type: "button", onClick: () => {
38
+ }, variant: "outline", children: "Exit" }), _jsx(Button, { ref: acceptTriggerRef, type: "button", disabled: !isChecked, "aria-disabled": !isChecked, onClick: () => {
39
39
  if (isChecked) {
40
40
  disclaimer.close();
41
41
  }
@@ -1,17 +1,13 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import Image, {} from 'next/image';
4
- import { useShallow } from 'zustand/shallow';
5
- import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
6
4
  import pagcorLogo from '../../images/pagcor.png';
7
5
  import responsibleGamingLogo from '../../images/responsible-gaming.png';
8
6
  import { Button } from '../../ui/Button/index.js';
9
7
  import { Dialog } from '../../ui/Dialog/index.js';
10
8
  import { Portal } from '../../ui/Portal/index.js';
11
9
  export function TermsOfUse({ logo, open, onCloseAction, siteName, termsOfUseContent, ...props }) {
12
- const disclaimerStore = useGlobalStore(useShallow((state) => state.disclaimer));
13
10
  return (_jsx(Dialog.Root, { open: open, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, { className: "z-popover" }), _jsx(Dialog.Positioner, { className: "z-popover", children: _jsxs(Dialog.Content, { className: "flex h-full w-full flex-col items-start overflow-y-auto p-3xl lg:mx-auto lg:h-auto lg:w-[400px] lg:rounded-xl", children: [_jsx(Image, { src: logo, alt: "", width: 200, height: 100, className: "mx-auto h-auto w-[120px]", draggable: false }), _jsxs("div", { className: "w-full grow", children: [_jsxs(Dialog.Title, { className: "text-brand-400 mt-3xl text-center text-lg font-semibold", children: [siteName, " Terms of Use"] }), _jsx(Dialog.Description, { className: "leading-2xl mt-xs text-sm", children: termsOfUseContent }), _jsxs("div", { className: "mt-5 flex items-center justify-center gap-3xl", children: [_jsx(Image, { src: pagcorLogo, alt: "", height: 75, width: 88, className: "h-[88px] w-auto shrink-0", draggable: false }), _jsx(Image, { src: props.responsibleGamingLogo ?? responsibleGamingLogo, alt: "", height: 62, width: 186, className: "h-[62px] w-auto shrink-0", draggable: false })] })] }), _jsx("div", { className: "w-full", children: _jsx(Button, { type: "button", className: "mt-3", onClick: () => {
14
11
  onCloseAction();
15
- disclaimerStore.setOpen(true);
16
12
  }, children: "I Agree" }) })] }) })] }) }));
17
13
  }
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { isString } from 'lodash-es';
4
4
  import Image, {} from 'next/image';
5
5
  import Link from 'next/link';
6
- import { useRef, useState } from 'react';
6
+ import { useEffect, useRef, useState } from 'react';
7
7
  import { twMerge } from 'tailwind-merge';
8
8
  import { useDebounceCallback } from 'usehooks-ts';
9
9
  import { useShallow } from 'zustand/shallow';
@@ -64,10 +64,11 @@ export function Search(props) {
64
64
  const classNames = isString(props.className)
65
65
  ? { root: props.className }
66
66
  : (props.className ?? {});
67
+ console.log({ search });
67
68
  return (_jsx(Dialog.Root, { open: globalStore.search.open, onOpenChange: (details) => {
68
69
  globalStore.search.setOpen(details.open);
69
70
  setSearch('');
70
- }, lazyMount: true, unmountOnExit: true, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { children: _jsxs(Dialog.Content, { className: "bg-bg-primary lg:min-h-auto mx-auto min-h-full w-full pt-xl lg:mt-[132px] lg:max-w-[1136px] lg:bg-unset lg:pt-0 overflow-y-auto", children: [_jsx(Dialog.Context, { children: (api) => (_jsxs("div", { className: "flex items-center justify-between px-4 lg:hidden", children: [_jsx(Image, { src: props.logo, alt: "", width: 200, height: 40, className: "h-8 w-auto" }), _jsx(IconButton, { size: "sm", colorScheme: "gray", onClick: () => api.setOpen(false), children: _jsx(XIcon, { className: "text-text-quinary size-6" }) })] })) }), _jsx("div", { className: "flex justify-end", children: _jsx(Dialog.Context, { children: (api) => (_jsx("button", { type: "button", onClick: () => api.setOpen(false), className: "hidden size-9 items-center justify-center rounded-md bg-white/20 lg:flex", children: _jsx(XIcon, { className: "size-5 text-white" }) })) }) }), _jsx("div", { className: "mt-2xl px-4 lg:mt-xl lg:px-0", children: _jsxs("div", { className: "relative", children: [_jsx(SearchLgIcon, { className: "text-text-quinary pointer-events-none absolute left-3.5 top-1/2 size-5 -translate-y-1/2" }), _jsx(DebouncedInput, { ref: inputRef, value: search, placeholder: props.placeholder ?? 'Search', onChange: setSearch }), _jsx(Presence, { present: search.length > 0, asChild: true, lazyMount: true, children: _jsx("button", { type: "button", className: "text-text-secondary-700 absolute right-3.5 top-1/2 -translate-y-1/2 font-semibold", onClick: () => {
71
+ }, lazyMount: true, unmountOnExit: true, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { children: _jsxs(Dialog.Content, { className: "bg-bg-primary lg:min-h-auto mx-auto min-h-full w-full overflow-y-auto pt-xl lg:mt-[132px] lg:max-w-[1136px] lg:bg-unset lg:pt-0", children: [_jsx(Dialog.Context, { children: (api) => (_jsxs("div", { className: "flex items-center justify-between px-4 lg:hidden", children: [_jsx(Image, { src: props.logo, alt: "", width: 200, height: 40, className: "h-8 w-auto" }), _jsx(IconButton, { size: "sm", colorScheme: "gray", onClick: () => api.setOpen(false), children: _jsx(XIcon, { className: "text-text-quinary size-6" }) })] })) }), _jsx("div", { className: "flex justify-end", children: _jsx(Dialog.Context, { children: (api) => (_jsx("button", { type: "button", onClick: () => api.setOpen(false), className: "hidden size-9 items-center justify-center rounded-md bg-white/20 lg:flex", children: _jsx(XIcon, { className: "size-5 text-white" }) })) }) }), _jsx("div", { className: "mt-2xl px-4 lg:mt-xl lg:px-0", children: _jsxs("div", { className: "relative", children: [_jsx(SearchLgIcon, { className: "text-text-quinary pointer-events-none absolute left-3.5 top-1/2 size-5 -translate-y-1/2" }), _jsx(DebouncedInput, { ref: inputRef, value: search, placeholder: props.placeholder ?? 'Search', onChange: setSearch }), _jsx(Presence, { present: search.length > 0, asChild: true, lazyMount: true, children: _jsx("button", { type: "button", className: "text-text-secondary-700 absolute right-3.5 top-1/2 -translate-y-1/2 font-semibold", onClick: () => {
71
72
  setSearch('');
72
73
  inputRef.current?.focus();
73
74
  }, children: "Clear" }) })] }) }), _jsx("div", { className: "lg:bg-bg-primary lg:border-border-primary mt-2xl p-xl pb-3xl lg:mt-2 lg:max-h-[41rem] lg:overflow-y-auto lg:rounded-xl lg:border", children: search.length <= 0 ? (_jsx(Alert, { message: "Search for your favorite game or provider." })) : search.length >= 1 && search.length <= 2 ? (_jsx(Alert, { message: "Search requires at least 3 characters." })) : (_jsxs(_Fragment, { children: [empty && _jsx(Alert, { message: "No results found" }), !empty && (_jsxs(_Fragment, { children: [gameProviders.length > 0 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "text-lg font-semibold", children: "Providers" }), _jsx("div", { className: "mb-3xl mt-3.5 grid grid-cols-3 gap-1.5 lg:grid-cols-9 lg:gap-3.5", children: gameProviders.map((provider) => (_jsx(Link, { href: viewGamesUrl(provider), "aria-label": `View ${provider.name} games`, className: twMerge('bg-brand-800 flex h-14 w-full items-center rounded-md', classNames.providerThumbnailRoot), onClick: () => {
@@ -88,6 +89,9 @@ function DebouncedInput(props) {
88
89
  });
89
90
  const setValueDebounced = useDebounceCallback(setValue, 300);
90
91
  const [internalValue, setInternalValue] = useState(value);
92
+ useEffect(() => {
93
+ setInternalValue(value);
94
+ }, [value]);
91
95
  return (_jsx(Field.Input, { ref: props.ref, type: "search", value: internalValue, onChange: (e) => {
92
96
  setInternalValue(e.target.value);
93
97
  setValueDebounced(e.target.value);
@@ -48,6 +48,7 @@ export function MobileNumberSignIn(props) {
48
48
  description: getFriendlyErrorMessage(err.name, {
49
49
  siteName: props.siteName,
50
50
  accountName: step1Form.getValues('mobileNumber'),
51
+ signInOptions: 'MOBILE_NUMBER',
51
52
  }),
52
53
  });
53
54
  },
@@ -1,6 +1,7 @@
1
1
  type Options = {
2
2
  accountName?: string;
3
3
  siteName?: string;
4
+ signInOptions?: 'MOBILE_NUMBER' | 'NAME_AND_PASSWORD';
4
5
  };
5
6
  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
7
  export declare function getFriendlyErrorMessage(code: ErrorCode, options?: Options): string;
@@ -1,6 +1,7 @@
1
1
  export function getFriendlyErrorMessage(code, options) {
2
2
  const accountName = options?.accountName ?? '';
3
3
  const siteName = options?.siteName ?? '';
4
+ const signInOptions = options?.signInOptions ?? 'NAME_AND_PASSWORD';
4
5
  switch (code) {
5
6
  case 'MEMBER_ACCOUNT_BLACKLISTED':
6
7
  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.`;
@@ -15,6 +16,9 @@ export function getFriendlyErrorMessage(code, options) {
15
16
  case 'NOT_READY_TO_SEND_VERIFICATION_ERROR':
16
17
  return 'Your account is not ready to send verification requests. Please try again later.';
17
18
  case 'Forbidden':
19
+ if (signInOptions === 'MOBILE_NUMBER') {
20
+ return 'Invalid verification code. Please check the code and try again.';
21
+ }
18
22
  return 'Invalid username or password, please try again.';
19
23
  default:
20
24
  return 'Something went wrong. Please try again later.';
@@ -0,0 +1,15 @@
1
+ import { type CSSProperties, type RefObject } from 'react';
2
+ import { type ValidateFileError } from '../../../utils/validateFile';
3
+ import { type UseIdFrontImageFieldProps } from './useIdFrontImageField';
4
+ export interface IdFrontImageFieldProps extends UseIdFrontImageFieldProps {
5
+ ref?: RefObject<HTMLDivElement | null>;
6
+ id?: string;
7
+ style?: CSSProperties;
8
+ className?: string;
9
+ invalid?: boolean;
10
+ readOnly?: boolean;
11
+ disabled?: boolean;
12
+ required?: boolean;
13
+ onError?: (error: ValidateFileError) => void;
14
+ }
15
+ export declare function IdFrontImageField__client(props: IdFrontImageFieldProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,140 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Image from 'next/image';
3
+ import { useEffect, useId, useState, } from 'react';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { Camera01Icon } from '../../../icons/Camera01Icon.js';
6
+ import { CameraOffIcon } from '../../../icons/CameraOffIcon.js';
7
+ import { CheckCircleIcon } from '../../../icons/CheckCircleIcon.js';
8
+ import { RefreshCcw01Icon } from '../../../icons/RefreshCcw01Icon.js';
9
+ import { Spinner02Icon } from '../../../icons/Spinner02Icon.js';
10
+ import { UploadCloud02Icon } from '../../../icons/UploadCloud02Icon.js';
11
+ import { XIcon } from '../../../icons/XIcon.js';
12
+ import { Button } from '../../../ui/Button/index.js';
13
+ import { Dialog } from '../../../ui/Dialog/index.js';
14
+ import { Portal } from '../../../ui/Portal/index.js';
15
+ import { createSplitProps } from '../../../utils/createSplitProps.js';
16
+ import { validateFile, } from '../../../utils/validateFile.js';
17
+ import { IdFrontImageFieldProvider, useIdFrontImageFieldContext, } from './IdFrontImageFieldContext.js';
18
+ import { useIdFrontImageField, } from './useIdFrontImageField.js';
19
+ export function IdFrontImageField__client(props) {
20
+ const [useIdFrontImageFieldProps, localProps] = createSplitProps([
21
+ 'value',
22
+ 'onChange',
23
+ 'defaultValue',
24
+ ])(props);
25
+ const context = useIdFrontImageField(useIdFrontImageFieldProps);
26
+ return (_jsxs(IdFrontImageFieldProvider, { value: context, children: [_jsxs("div", { ref: localProps.ref, id: localProps.id, style: localProps.style, className: twMerge('border-border-primary bg-bg-primary group relative aspect-[352/180] w-full shrink-0 rounded-xl border p-xl', localProps.className), onDragOver: (e) => e.preventDefault(), onDragEnter: (e) => e.preventDefault(), onDragLeave: (e) => e.preventDefault(), onDrop: (e) => {
27
+ e.preventDefault();
28
+ const file = e.dataTransfer.files?.[0];
29
+ if (!file)
30
+ return;
31
+ const error = validateFile(file);
32
+ if (error)
33
+ return localProps.onError?.(error);
34
+ context.mutation.mutate({ file });
35
+ }, "aria-describedby": context.field?.ariaDescribedby, children: [_jsx("input", { id: context.field?.ids?.control, ref: context.inputRef, type: "file", accept: "image/png, image/jpeg", onChange: (e) => {
36
+ const file = e.target.files?.[0];
37
+ if (!file)
38
+ return;
39
+ const error = validateFile(file);
40
+ if (error)
41
+ return localProps.onError?.(error);
42
+ context.mutation.mutate({ file });
43
+ }, disabled: context.field?.disabled ||
44
+ context.query.isLoading ||
45
+ context.mutation.isPending ||
46
+ localProps.disabled, readOnly: context.field?.readOnly || localProps.readOnly, required: context.field?.required || localProps.required, className: "sr-only" }), !context.query.data?.url && (_jsxs("div", { className: "flex size-full flex-col justify-center", 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-placeholder-subtle size-4" }) }), _jsxs("h2", { className: "text-text-tertiary-600 mt-lg", children: [_jsxs("span", { className: "block text-center text-sm", children: [_jsx("button", { type: "button", onClick: () => {
47
+ context.inputRef.current?.click();
48
+ }, disabled: context.field?.disabled ||
49
+ context.field?.readOnly ||
50
+ context.query.isLoading ||
51
+ context.mutation.isPending ||
52
+ localProps.disabled ||
53
+ localProps.readOnly, className: "text-button-secondary-fg font-semibold disabled:opacity-60", children: "Click to upload" }), ' ', "or drag and drop"] }), _jsx("span", { className: "mt-xs block text-center text-xs", children: "PNG, JPG or JPEG (max. 10mb)" }), _jsx("span", { className: "m-txs block text-center text-xs", children: "or" })] }), _jsx(Button, { size: "sm", variant: "outline", className: "mx-auto mt-md w-auto", onClick: () => {
54
+ context.disclosure.setOpen(true);
55
+ }, disabled: context.field?.disabled ||
56
+ context.field?.readOnly ||
57
+ context.query.isLoading ||
58
+ context.mutation.isPending ||
59
+ localProps.disabled ||
60
+ localProps.readOnly, children: "Take a photo" })] })), context.query.data?.url && (_jsxs("div", { className: "relative", children: [_jsx(Image, { src: context.query.data.url, alt: "", width: 600, height: 378.08, unoptimized: true, className: "h-auto w-full rounded-md" }), _jsx("button", { type: "button", className: "backdrop-blur-xs absolute right-2 top-2 rounded-full bg-white/10 p-1", onClick: () => {
61
+ context.setValue('');
62
+ }, children: _jsx(XIcon, { className: "size-5" }) })] }))] }), _jsx(Camera, {})] }));
63
+ }
64
+ /**
65
+ * -------------------------------------
66
+ * Camera
67
+ * -------------------------------------
68
+ */
69
+ function Camera() {
70
+ const context = useIdFrontImageFieldContext();
71
+ return (_jsx(Dialog.Root, { open: context.disclosure.open, onOpenChange: (details) => {
72
+ context.disclosure.setOpen(details.open);
73
+ }, closeOnEscape: false, closeOnInteractOutside: false, onExitComplete: context.camera.close, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, { className: "!z-[calc(var(--z-dialog)+1)]" }), _jsx(Dialog.Positioner, { className: "!z-[calc(var(--z-dialog)+2)] flex items-center justify-center overflow-y-auto py-4", children: _jsxs(Dialog.Content, { className: "bg-bg-primary-alt mx-auto w-[calc(100dvw-1rem)] max-w-[calc(100dvw-1rem)] overflow-y-auto rounded-lg px-4 py-5 lg:w-[747px] lg:max-w-[747px] lg:px-3xl lg:py-4xl", children: [_jsx(Dialog.CloseTrigger, { children: _jsx(XIcon, {}) }), _jsx(Dialog.Title, { className: "text-center font-semibold lg:text-xl", children: "Take a Picture of Your Front ID" }), _jsxs(Dialog.Description, { className: "text-text-tertiary-600 mt-md text-center text-xs lg:text-base", children: ["Make sure your ID is clearly visible, well-lit, and not blurry.", ' ', _jsx("br", { className: "hidden lg:block" }), "Avoid glare or reflections, and ensure all corners are within\u00A0the\u00A0frame."] }), _jsxs("div", { className: "relative mt-5 lg:mt-10 lg:px-3xl", children: [_jsx(Video, {}), context.camera.error && (_jsxs("div", { className: "border-border-disabled flex aspect-[4/3] flex-col items-center justify-center rounded-md border bg-black px-4 lg:aspect-video", children: [_jsx(CameraOffIcon, { className: "text-text-placeholder-subtle size-10 text-center lg:size-12" }), _jsx("h2", { className: "mt-3 text-sm font-semibold lg:mt-4 lg:text-base", children: context.camera.error.name }), _jsx("p", { className: "text-text-tertiary-600 mt-0.5 text-center text-xs lg:mt-1 lg:text-sm", children: context.camera.error.message }), _jsx(Button, { size: "xs", variant: "outline", colorScheme: "gray", fullWidth: false, className: "mt-4 lg:mt-5", onClick: () => {
74
+ context.camera.reopen();
75
+ }, children: "Try again" })] })), context.camera.loading && (_jsx("div", { className: "border-border-disabled flex aspect-[4/3] flex-col items-center justify-center rounded-md border bg-black px-4 lg:aspect-video", children: _jsx(Spinner02Icon, { className: "text-text-placeholder size-8" }) })), context.camera.data && (_jsx("div", { className: "border-border-disabled aspect-[4/3] w-full overflow-hidden rounded-md border bg-black lg:aspect-video", children: _jsx(Image, { src: context.camera.data.url, alt: "", width: 600, height: 378.08, unoptimized: true, className: "mx-auto mt-2 block h-[calc(100%-1rem)] w-auto" }) }))] }), !context.camera.data && (_jsx("div", { className: "mt-5 lg:mt-10", children: _jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto w-full px-xl py-lg lg:w-[10rem]", onClick: () => {
76
+ context.camera.snap();
77
+ }, disabled: context.camera.snapping ||
78
+ context.camera.loading ||
79
+ !!context.camera.error, children: [context.camera.snapping ? (_jsx(Spinner02Icon, { className: "size-5" })) : (_jsx(Camera01Icon, { className: "size-5" })), "Capture"] }) })), context.camera.data && (_jsxs("div", { className: "mt-5 flex gap-3 lg:mt-10 lg:justify-center", children: [_jsxs(Button, { onClick: () => {
80
+ const file = context.camera.data?.file;
81
+ if (!file)
82
+ return;
83
+ context.mutation.mutate({ file });
84
+ context.disclosure.setOpen(false);
85
+ }, className: "w-full lg:w-[10rem]", children: [_jsx(CheckCircleIcon, { className: "size-5" }), "Use Photo"] }), _jsxs(Button, { variant: "outline", colorScheme: "gray", onClick: () => {
86
+ context.camera.reset();
87
+ }, className: "w-full lg:w-[10rem]", children: [_jsx(RefreshCcw01Icon, { className: "size-5" }), " Retake"] })] }))] }) })] }) }));
88
+ }
89
+ /**
90
+ * -------------------------------------
91
+ * Video
92
+ * -------------------------------------
93
+ */
94
+ function Video() {
95
+ const id = useId();
96
+ const context = useIdFrontImageFieldContext();
97
+ const [maskBox, setMaskBox] = useState({
98
+ x: 0,
99
+ y: 0,
100
+ width: 0,
101
+ height: 0,
102
+ });
103
+ useEffect(() => {
104
+ const update = () => {
105
+ const guide = context.guideRef.current;
106
+ const mask = context.maskRef.current;
107
+ if (!guide || !mask)
108
+ return;
109
+ const guideRect = guide.getBoundingClientRect();
110
+ const maskRect = mask.getBoundingClientRect();
111
+ const x = guideRect.left - maskRect.left;
112
+ const y = guideRect.top - maskRect.top;
113
+ const width = guideRect.width;
114
+ const height = guideRect.height;
115
+ if (width && height) {
116
+ setMaskBox({
117
+ x,
118
+ y,
119
+ width,
120
+ height,
121
+ });
122
+ }
123
+ };
124
+ const observer = new ResizeObserver(update);
125
+ if (context.guideRef.current)
126
+ observer.observe(context.guideRef.current);
127
+ if (context.maskRef.current)
128
+ observer.observe(context.maskRef.current);
129
+ requestAnimationFrame(update);
130
+ return () => observer.disconnect();
131
+ }, [context.guideRef, context.maskRef]);
132
+ return (_jsxs("div", { className: twMerge('relative h-auto w-full overflow-hidden', !context.camera.data && !context.camera.error && !context.camera.loading
133
+ ? 'block'
134
+ : 'hidden'), children: [_jsx("video", { ...context.camera.videoProps }), _jsx("svg", { className: "pointer-events-none absolute -inset-4 size-[200%]", children: _jsx("defs", { children: _jsxs("mask", { id: id, maskUnits: "userSpaceOnUse", children: [_jsx("rect", { x: "0", y: "0", width: "200%", height: "200%", fill: "white" }), _jsx("rect", { x: maskBox.x, y: maskBox.y, width: maskBox.width, height: maskBox.height, fill: "black", rx: "8", ry: "8" })] }) }) }), _jsx("div", { ref: context.maskRef, className: "absolute -inset-4 size-[200%] bg-black/60 backdrop-blur-sm", style: {
135
+ mask: `url(#${id})`,
136
+ WebkitMask: `url(#${id})`,
137
+ maskRepeat: 'no-repeat',
138
+ WebkitMaskRepeat: 'no-repeat',
139
+ } }), _jsx("div", { ref: context.guideRef, className: "border-utility-warning-700 pointer-events-none absolute left-1/2 top-1/2 z-10 aspect-[85.6/53.98] w-[calc(100%-1rem)] max-w-[24.688rem] -translate-x-1/2 -translate-y-1/2 rounded-md border-4 border-dashed transition-all duration-200" })] }));
140
+ }
@@ -0,0 +1,2 @@
1
+ import type { IdFrontImageFieldProps } from './IdFrontImageField.client';
2
+ export declare function IdFrontImageField(props: IdFrontImageFieldProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import dynamic from 'next/dynamic';
4
+ import { Spinner02Icon } from '../../../icons/Spinner02Icon.js';
5
+ const Component = dynamic(() => import('./IdFrontImageField.client.js').then((mod) => mod.IdFrontImageField__client), {
6
+ ssr: false,
7
+ loading: () => (_jsx("div", { className: "border-border-primary bg-bg-primary flex aspect-[352/180] w-full shrink-0 items-center justify-center rounded-xl border", children: _jsx(Spinner02Icon, { className: "text-text-quinary size-8" }) })),
8
+ });
9
+ export function IdFrontImageField(props) {
10
+ return _jsx(Component, { ...props });
11
+ }
@@ -0,0 +1,6 @@
1
+ import type { UseIdFrontImageFieldReturn } from './useIdFrontImageField';
2
+ export declare const IdFrontImageFieldProvider: (props: {
3
+ value: UseIdFrontImageFieldReturn;
4
+ } & {
5
+ children?: import("react").ReactNode | undefined;
6
+ }) => React.ReactNode, useIdFrontImageFieldContext: () => UseIdFrontImageFieldReturn;
@@ -0,0 +1,2 @@
1
+ import { createContext } from '../../../client/utils/createContext.js';
2
+ export const [IdFrontImageFieldProvider, useIdFrontImageFieldContext] = createContext();
@@ -0,0 +1 @@
1
+ export * from './IdFrontImageField';
@@ -0,0 +1 @@
1
+ export * from './IdFrontImageField.js';
@@ -0,0 +1,22 @@
1
+ export interface UseIdFrontImageFieldProps {
2
+ value?: string;
3
+ onChange?: (value: string) => void;
4
+ defaultValue?: string;
5
+ }
6
+ export interface UseIdFrontImageFieldReturn extends ReturnType<typeof useIdFrontImageField> {
7
+ }
8
+ export declare function useIdFrontImageField(props: UseIdFrontImageFieldProps): {
9
+ field: import("@ark-ui/react").UseFieldContext;
10
+ value: string;
11
+ setValue: import("react").Dispatch<import("react").SetStateAction<string>>;
12
+ query: import("@tanstack/react-query").UseQueryResult<import("../../../types").File | null, Error>;
13
+ mutation: import("@tanstack/react-query").UseMutationResult<string, Error, {
14
+ file: globalThis.File;
15
+ id?: string | undefined;
16
+ }, unknown>;
17
+ camera: import("../../../client/hooks/useCamera").UseCameraReturn<never>;
18
+ disclosure: import("../../../client/hooks/useDisclosure").UseDisclosureReturn;
19
+ inputRef: import("react").RefObject<HTMLInputElement | null>;
20
+ guideRef: import("react").RefObject<HTMLDivElement | null>;
21
+ maskRef: import("react").RefObject<HTMLDivElement | null>;
22
+ };