@opexa/portal-components 0.0.680 → 0.0.681

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.
@@ -11,8 +11,9 @@ export interface CameraData {
11
11
  }
12
12
  export interface UseCameraReturn<T extends string = never> {
13
13
  open(): Promise<void>;
14
+ openNativeCamera(): Promise<CameraData | null>;
14
15
  close(): Promise<void>;
15
- snap(): CameraData | null;
16
+ snap(): Promise<CameraData | null>;
16
17
  reopen(): Promise<void>;
17
18
  reset(): Promise<void>;
18
19
  data: CameraData | null;
@@ -1,5 +1,6 @@
1
1
  import { isBoolean } from 'lodash-es';
2
2
  import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
3
+ import invariant from 'tiny-invariant';
3
4
  import { useMediaQuery } from 'usehooks-ts';
4
5
  export function useCamera(options = {}) {
5
6
  const videoRef = useRef(null);
@@ -78,6 +79,67 @@ export function useCamera(options = {}) {
78
79
  setLoading(false);
79
80
  }
80
81
  }, [options, desktop]);
82
+ const openNativeCamera = useCallback(async () => {
83
+ setData(null);
84
+ setError(null);
85
+ setSnapping(true);
86
+ try {
87
+ // Dynamically import only when running native
88
+ const { Camera, CameraResultType } = await import('@capacitor/camera');
89
+ const photo = await Camera.getPhoto({
90
+ quality: 90,
91
+ resultType: CameraResultType.Uri,
92
+ saveToGallery: false,
93
+ allowEditing: false,
94
+ });
95
+ if (!photo.webPath) {
96
+ throw new Error('No photo returned from native camera');
97
+ }
98
+ console.log(photo, 'photo');
99
+ const response = await fetch(photo.webPath);
100
+ const blob = await response.blob();
101
+ const file = new File([blob], `${crypto.randomUUID()}.jpeg`, {
102
+ type: blob.type,
103
+ lastModified: Date.now(),
104
+ });
105
+ // Convert blob → base64 data URL
106
+ const url = await new Promise((resolve, reject) => {
107
+ const reader = new FileReader();
108
+ reader.onloadend = () => {
109
+ if (typeof reader.result === 'string')
110
+ resolve(reader.result);
111
+ else
112
+ reject(new Error('Failed to read image as data URL'));
113
+ };
114
+ reader.onerror = reject;
115
+ reader.readAsDataURL(blob);
116
+ });
117
+ const image = new Image();
118
+ image.src = url;
119
+ if (!image.complete || image.naturalWidth === 0) {
120
+ await new Promise((resolve, reject) => {
121
+ image.onload = () => resolve();
122
+ image.onerror = () => reject(new Error('Failed to load preview image'));
123
+ });
124
+ }
125
+ const data = {
126
+ url,
127
+ file,
128
+ image,
129
+ };
130
+ setData(data);
131
+ setSnapping(false);
132
+ return data;
133
+ }
134
+ catch (e) {
135
+ setError({
136
+ name: 'CameraError',
137
+ message: e instanceof Error ? e.message : 'Failed to open native camera',
138
+ });
139
+ setSnapping(false);
140
+ return null;
141
+ }
142
+ }, [setData, setError, setSnapping]);
81
143
  const close = useCallback(() => {
82
144
  setData(null);
83
145
  setError(null);
@@ -89,33 +151,76 @@ export function useCamera(options = {}) {
89
151
  resolve();
90
152
  });
91
153
  }, []);
92
- const snap = useCallback(() => {
154
+ const snap = useCallback(async () => {
155
+ setData(null);
156
+ setError(null);
157
+ setSnapping(true);
93
158
  const video = videoRef.current;
94
- if (!video)
95
- return null;
96
159
  const canvas = document.createElement('canvas');
97
160
  const context = canvas.getContext('2d');
98
- if (!context)
99
- return null;
161
+ invariant(video, 'Could not find video element');
162
+ invariant(context, 'Could not get canvas context');
163
+ video.currentTime = 1;
100
164
  canvas.width = video.videoWidth;
101
165
  canvas.height = video.videoHeight;
102
- context.drawImage(video, 0, 0, canvas.width, canvas.height);
103
- const url = canvas.toDataURL('image/jpeg', 0.9);
104
- const arr = atob(url.split(',')[1]);
105
- const u8arr = new Uint8Array(arr.length);
106
- for (let i = 0; i < arr.length; i++)
107
- u8arr[i] = arr.charCodeAt(i);
108
- const file = new File([u8arr], `${crypto.randomUUID()}.jpeg`, {
109
- type: 'image/jpeg',
166
+ context.imageSmoothingEnabled = true;
167
+ context.imageSmoothingQuality = 'high';
168
+ context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
169
+ return new Promise((resolve) => {
170
+ canvas.toBlob(async (blob) => {
171
+ if (!blob) {
172
+ setSnapping(false);
173
+ resolve(null);
174
+ return setError({
175
+ name: 'CameraError',
176
+ message: "'canvas.toBlob' failed to create blob",
177
+ });
178
+ }
179
+ const url = canvas.toDataURL('image/jpeg', 1);
180
+ const file = new File([blob], `${crypto.randomUUID()}.jpeg`, {
181
+ type: 'image/jpeg',
182
+ endings: 'native',
183
+ lastModified: Date.now(),
184
+ });
185
+ const image = new Image();
186
+ image.src = url;
187
+ image.alt = '';
188
+ image.width = canvas.width;
189
+ image.height = canvas.height;
190
+ if (!image.complete || image.naturalWidth === 0) {
191
+ await new Promise((resolve, reject) => {
192
+ image.onload = () => resolve();
193
+ image.onerror = () => reject();
194
+ });
195
+ }
196
+ const data = {
197
+ url,
198
+ file,
199
+ image,
200
+ };
201
+ if (!options.transform) {
202
+ setData(data);
203
+ setSnapping(false);
204
+ resolve(data);
205
+ return;
206
+ }
207
+ const transformResult = await options.transform({
208
+ ...data,
209
+ video,
210
+ canvas,
211
+ });
212
+ if (transformResult.ok) {
213
+ setData(transformResult.data);
214
+ resolve(transformResult.data);
215
+ }
216
+ else {
217
+ setError(transformResult.error);
218
+ resolve(null);
219
+ }
220
+ setSnapping(false);
221
+ }, 'image/jpeg', 1);
110
222
  });
111
- const image = new Image();
112
- image.src = url;
113
- image.width = canvas.width;
114
- image.height = canvas.height;
115
- const data = { url, file, image };
116
- setData(data);
117
- return data;
118
- }, []);
223
+ }, [options]);
119
224
  const reset = useCallback(() => {
120
225
  setData(null);
121
226
  setError(null);
@@ -157,6 +262,7 @@ export function useCamera(options = {}) {
157
262
  return {
158
263
  snap,
159
264
  open,
265
+ openNativeCamera,
160
266
  close,
161
267
  reopen,
162
268
  reset,
@@ -1,5 +1,5 @@
1
1
  import { addDays, isAfter } from 'date-fns';
2
- const SERVER = `com.${process.env.NEXT_PUBLIC_PLATFORM_CODE}.app`;
2
+ const SERVER = `${process.env.NEXT_PUBLIC_PLATFORM_CODE?.toLocaleLowerCase()}.app`;
3
3
  export const BIOMETRIC_STORAGE_KEY = `${process.env.NEXT_PUBLIC_PLATFORM_CODE}__BiometricEnabled`;
4
4
  export var BiometryType;
5
5
  (function (BiometryType) {
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { ark } from '@ark-ui/react/factory';
4
4
  import { BiometricAuthError } from 'capacitor-native-biometric';
5
+ import { useState } from 'react';
5
6
  import { useShallow } from 'zustand/shallow';
6
7
  import { useCreateGameSessionMutation } from '../../client/hooks/useCreateGameSessionMutation.js';
7
8
  import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
@@ -9,7 +10,7 @@ import { useMemberVerificationQuery } from '../../client/hooks/useMemberVerifica
9
10
  import { useSessionQuery } from '../../client/hooks/useSessionQuery.js';
10
11
  import { getSession } from '../../client/services/getSession.js';
11
12
  import { signIn } from '../../client/services/signIn.js';
12
- import { getBiometricCredentials, getBiometricInfo, hasSavedBiometry, performBiometricVerification, saveBiometricCredentials, } from '../../client/utils/biometric.js';
13
+ import { deleteBiometricCredentials, getBiometricCredentials, getBiometricInfo, hasSavedBiometry, performBiometricVerification, saveBiometricCredentials, } from '../../client/utils/biometric.js';
13
14
  import { toaster } from '../../client/utils/toaster.js';
14
15
  import { createSingleUseToken } from '../../services/auth.js';
15
16
  import { getQueryClient } from '../../utils/getQueryClient.js';
@@ -23,6 +24,7 @@ export function GameLaunchTrigger(props) {
23
24
  gameLaunch: ctx.gameLaunch,
24
25
  kycVerificationStatus: ctx.kycVerificationStatus,
25
26
  })));
27
+ const [hasCancelledBiometric, setHasCancelledBiometric] = useState(false);
26
28
  const verificationStatus = verificationQuery.data?.status ?? 'UNVERIFIED';
27
29
  const currentHour = new Date().getHours();
28
30
  const between3amAnd3pm = currentHour >= 15 || currentHour < 3;
@@ -44,7 +46,7 @@ export function GameLaunchTrigger(props) {
44
46
  : 'open', ...props, disabled: disabled, onClick: async (e) => {
45
47
  props.onClick?.(e);
46
48
  if (sessionQuery.data?.status === 'unauthenticated') {
47
- if (hasSavedBiometry()) {
49
+ if (hasSavedBiometry() && !hasCancelledBiometric) {
48
50
  const ok = await performBiometricVerification({
49
51
  reason: 'Login to your account',
50
52
  title: 'Login',
@@ -56,7 +58,10 @@ export function GameLaunchTrigger(props) {
56
58
  const info = await getBiometricInfo();
57
59
  if (info.errorCode === BiometricAuthError.APP_CANCEL ||
58
60
  info.errorCode === BiometricAuthError.USER_CANCEL ||
59
- info.errorCode === BiometricAuthError.SYSTEM_CANCEL) {
61
+ info.errorCode === BiometricAuthError.SYSTEM_CANCEL ||
62
+ info.errorCode === undefined ||
63
+ info.errorCode === null) {
64
+ setHasCancelledBiometric(true);
60
65
  console.log('Biometric verification cancelled');
61
66
  }
62
67
  else {
@@ -71,10 +76,21 @@ export function GameLaunchTrigger(props) {
71
76
  globalStore.signIn.setOpen(!globalStore.signIn.open);
72
77
  return;
73
78
  }
74
- await signIn({
75
- type: 'SINGLE_USE_TOKEN',
76
- token: credentials.password,
77
- });
79
+ try {
80
+ console.log('Signing in using biometric credentials');
81
+ await signIn({
82
+ type: 'SINGLE_USE_TOKEN',
83
+ token: credentials.password,
84
+ });
85
+ }
86
+ catch (error) {
87
+ toaster.error({
88
+ title: 'Biometric sign-in token has expired.',
89
+ description: 'Please sign in with your mobile number or username to re-enable biometric login.',
90
+ });
91
+ deleteBiometricCredentials();
92
+ globalStore.signIn.setOpen(!globalStore.signIn.open);
93
+ }
78
94
  getQueryClient().invalidateQueries({
79
95
  queryKey: getSessionQueryKey(),
80
96
  });
@@ -95,19 +111,56 @@ export function GameLaunchTrigger(props) {
95
111
  else {
96
112
  console.warn('Failed to updated biometric credentials');
97
113
  globalStore.signIn.setOpen(!globalStore.signIn.open);
98
- return;
99
114
  }
100
115
  }
101
116
  else {
102
117
  console.error('Failed to create token');
103
118
  globalStore.signIn.setOpen(!globalStore.signIn.open);
104
- return;
105
119
  }
106
120
  }
107
121
  else {
108
- globalStore.signIn.setOpen(true);
109
- return;
122
+ // still update biometric credentials if user has cancelled biometric once
123
+ if (hasCancelledBiometric) {
124
+ const credentials = await getBiometricCredentials();
125
+ if (!credentials) {
126
+ toaster.error({ description: 'Biometric verification failed' });
127
+ globalStore.signIn.setOpen(!globalStore.signIn.open);
128
+ return;
129
+ }
130
+ await signIn({
131
+ type: 'SINGLE_USE_TOKEN',
132
+ token: credentials.password,
133
+ });
134
+ getQueryClient().invalidateQueries({
135
+ queryKey: getSessionQueryKey(),
136
+ });
137
+ const session = await getSession();
138
+ const r = await createSingleUseToken({
139
+ headers: {
140
+ Authorization: `Bearer ${session.token}`,
141
+ },
142
+ });
143
+ if (r.token) {
144
+ const saved = await saveBiometricCredentials({
145
+ username: credentials.username,
146
+ password: r.token,
147
+ });
148
+ if (saved) {
149
+ console.info('Biometric credentials has been updated');
150
+ }
151
+ else {
152
+ console.warn('Failed to updated biometric credentials');
153
+ globalStore.signIn.setOpen(!globalStore.signIn.open);
154
+ }
155
+ }
156
+ else {
157
+ console.error('Failed to create token');
158
+ globalStore.signIn.setOpen(!globalStore.signIn.open);
159
+ }
160
+ }
161
+ globalStore.signIn.setOpen(!globalStore.signIn.open);
110
162
  }
163
+ return props.onClick?.(e);
111
164
  }
112
165
  //handle new kyc process to play only on verified members only
113
166
  if (verificationStatus === 'PENDING' ||
@@ -10,6 +10,7 @@ import { useGlobalStore } from '../../../client/hooks/useGlobalStore.js';
10
10
  import { useMemberVerificationQuery } from '../../../client/hooks/useMemberVerificationQuery.js';
11
11
  import { useSignOutMutation } from '../../../client/hooks/useSignOutMutation.js';
12
12
  import { useUpdateMemberVerificationMutation } from '../../../client/hooks/useUpdateMemberVerificationMutation.js';
13
+ import { BIOMETRIC_STORAGE_KEY } from '../../../client/utils/biometric.js';
13
14
  import { toaster } from '../../../client/utils/toaster.js';
14
15
  import { CheckIcon } from '../../../icons/CheckIcon.js';
15
16
  import { Button } from '../../../ui/Button/index.js';
@@ -38,7 +39,16 @@ export function PersonalInformation() {
38
39
  const router = useRouter();
39
40
  const signOutMutation = useSignOutMutation({
40
41
  onSuccess() {
41
- localStorage.clear();
42
+ const keep = new Set([BIOMETRIC_STORAGE_KEY]);
43
+ for (let i = 0; i < localStorage.length;) {
44
+ const key = localStorage.key(i);
45
+ if (key && !keep.has(key)) {
46
+ localStorage.removeItem(key);
47
+ }
48
+ else {
49
+ i++;
50
+ }
51
+ }
42
52
  sessionStorage.clear();
43
53
  router.replace('/');
44
54
  },
@@ -28,16 +28,14 @@ export function KYCReminder(props) {
28
28
  const signOutMutation = useSignOutMutation({
29
29
  onSuccess() {
30
30
  // Clear everything except the 'biometric' entry
31
- {
32
- const keep = new Set([BIOMETRIC_STORAGE_KEY]);
33
- for (let i = 0; i < localStorage.length;) {
34
- const key = localStorage.key(i);
35
- if (key && !keep.has(key)) {
36
- localStorage.removeItem(key);
37
- }
38
- else {
39
- i++;
40
- }
31
+ const keep = new Set([BIOMETRIC_STORAGE_KEY]);
32
+ for (let i = 0; i < localStorage.length;) {
33
+ const key = localStorage.key(i);
34
+ if (key && !keep.has(key)) {
35
+ localStorage.removeItem(key);
36
+ }
37
+ else {
38
+ i++;
41
39
  }
42
40
  }
43
41
  sessionStorage.clear();
@@ -33,6 +33,7 @@ export function KycOpenOnHomeMount(props) {
33
33
  !verification?.placeOfBirth ||
34
34
  !verification?.address;
35
35
  useEffect(() => {
36
+ console.log(hasntSubmittedCompliantDocs, hasntCompletedKYC);
36
37
  if (!verificationLoading && !accountLoading) {
37
38
  // Handle pending case with feature flag
38
39
  if (isPending) {
@@ -45,7 +45,9 @@ export function RegisterBiometrics() {
45
45
  const info = await getBiometricInfo();
46
46
  if (info.errorCode === BiometricAuthError.APP_CANCEL ||
47
47
  info.errorCode === BiometricAuthError.USER_CANCEL ||
48
- info.errorCode === BiometricAuthError.SYSTEM_CANCEL) {
48
+ info.errorCode === BiometricAuthError.SYSTEM_CANCEL ||
49
+ info.errorCode === undefined ||
50
+ info.errorCode === null) {
49
51
  return;
50
52
  }
51
53
  else {
@@ -1,4 +1,4 @@
1
- import type { ComponentPropsWithRef } from 'react';
1
+ import { type ComponentPropsWithRef } from 'react';
2
2
  interface SignInTriggerProps extends ComponentPropsWithRef<'button'> {
3
3
  asChild?: boolean;
4
4
  }
@@ -1,22 +1,24 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { ark } from '@ark-ui/react/factory';
4
+ import { useState } from 'react';
4
5
  import { useShallow } from 'zustand/shallow';
5
6
  import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
6
7
  import { getSession } from '../../client/services/getSession.js';
7
8
  import { signIn } from '../../client/services/signIn.js';
8
- import { BiometricAuthError, getBiometricCredentials, getBiometricInfo, hasSavedBiometry, performBiometricVerification, saveBiometricCredentials, } from '../../client/utils/biometric.js';
9
+ import { BiometricAuthError, deleteBiometricCredentials, getBiometricCredentials, getBiometricInfo, hasSavedBiometry, performBiometricVerification, saveBiometricCredentials, } from '../../client/utils/biometric.js';
9
10
  import { toaster } from '../../client/utils/toaster.js';
10
11
  import { createSingleUseToken } from '../../services/auth.js';
11
12
  import { getQueryClient } from '../../utils/getQueryClient.js';
12
13
  import { getSessionQueryKey } from '../../utils/queryKeys.js';
13
14
  export function SignInTrigger(props) {
15
+ const [hasCancelledBiometric, setHasCancelledBiometric] = useState(false);
14
16
  const globalStore = useGlobalStore(useShallow((ctx) => ({
15
17
  signIn: ctx.signIn,
16
18
  registerBiometrics: ctx.registerBiometrics,
17
19
  })));
18
20
  return (_jsx(ark.button, { type: "button", "aria-label": "Sign in", "data-state": globalStore.signIn.open ? 'open' : 'closed', ...props, onClick: async (e) => {
19
- if (hasSavedBiometry()) {
21
+ if (hasSavedBiometry() && !hasCancelledBiometric) {
20
22
  const ok = await performBiometricVerification({
21
23
  reason: 'Login to your account',
22
24
  title: 'Login',
@@ -28,7 +30,10 @@ export function SignInTrigger(props) {
28
30
  const info = await getBiometricInfo();
29
31
  if (info.errorCode === BiometricAuthError.APP_CANCEL ||
30
32
  info.errorCode === BiometricAuthError.USER_CANCEL ||
31
- info.errorCode === BiometricAuthError.SYSTEM_CANCEL) {
33
+ info.errorCode === BiometricAuthError.SYSTEM_CANCEL ||
34
+ info.errorCode === undefined ||
35
+ info.errorCode === null) {
36
+ setHasCancelledBiometric(true);
32
37
  console.log('Biometric verification cancelled');
33
38
  }
34
39
  else {
@@ -43,10 +48,21 @@ export function SignInTrigger(props) {
43
48
  globalStore.signIn.setOpen(!globalStore.signIn.open);
44
49
  return;
45
50
  }
46
- await signIn({
47
- type: 'SINGLE_USE_TOKEN',
48
- token: credentials.password,
49
- });
51
+ try {
52
+ await signIn({
53
+ type: 'SINGLE_USE_TOKEN',
54
+ token: credentials.password,
55
+ });
56
+ }
57
+ catch (err) {
58
+ console.log(err);
59
+ toaster.error({
60
+ title: 'Biometric removed or token expired.',
61
+ description: 'Please sign in with your mobile number or username to re-enable biometric login.',
62
+ });
63
+ deleteBiometricCredentials();
64
+ globalStore.signIn.setOpen(!globalStore.signIn.open);
65
+ }
50
66
  getQueryClient().invalidateQueries({
51
67
  queryKey: getSessionQueryKey(),
52
68
  });
@@ -75,6 +91,45 @@ export function SignInTrigger(props) {
75
91
  }
76
92
  }
77
93
  else {
94
+ // still update biometric credentials if user has cancelled biometric once
95
+ if (hasCancelledBiometric) {
96
+ const credentials = await getBiometricCredentials();
97
+ if (!credentials) {
98
+ toaster.error({ description: 'Biometric verification failed' });
99
+ globalStore.signIn.setOpen(!globalStore.signIn.open);
100
+ return;
101
+ }
102
+ await signIn({
103
+ type: 'SINGLE_USE_TOKEN',
104
+ token: credentials.password,
105
+ });
106
+ getQueryClient().invalidateQueries({
107
+ queryKey: getSessionQueryKey(),
108
+ });
109
+ const session = await getSession();
110
+ const r = await createSingleUseToken({
111
+ headers: {
112
+ Authorization: `Bearer ${session.token}`,
113
+ },
114
+ });
115
+ if (r.token) {
116
+ const saved = await saveBiometricCredentials({
117
+ username: credentials.username,
118
+ password: r.token,
119
+ });
120
+ if (saved) {
121
+ console.info('Biometric credentials has been updated');
122
+ }
123
+ else {
124
+ console.warn('Failed to updated biometric credentials');
125
+ globalStore.signIn.setOpen(!globalStore.signIn.open);
126
+ }
127
+ }
128
+ else {
129
+ console.error('Failed to create token');
130
+ globalStore.signIn.setOpen(!globalStore.signIn.open);
131
+ }
132
+ }
78
133
  globalStore.signIn.setOpen(!globalStore.signIn.open);
79
134
  }
80
135
  return props.onClick?.(e);
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Capacitor } from '@capacitor/core';
2
3
  import Image from 'next/image';
3
4
  import { useRef } from 'react';
4
5
  import { twMerge } from 'tailwind-merge';
@@ -52,8 +53,16 @@ export function IdFrontImageField__client(props) {
52
53
  context.query.isLoading ||
53
54
  context.mutation.isPending ||
54
55
  localProps.disabled ||
55
- localProps.readOnly, className: "font-semibold text-button-secondary-fg 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: () => {
56
- context.disclosure.setOpen(true);
56
+ localProps.readOnly, className: "font-semibold text-button-secondary-fg 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: async () => {
57
+ if (Capacitor.isNativePlatform()) {
58
+ const data = await context.camera.openNativeCamera();
59
+ if (!data?.file)
60
+ return;
61
+ context.mutation.mutate({ file: data.file });
62
+ }
63
+ else {
64
+ context.disclosure.setOpen(true);
65
+ }
57
66
  }, disabled: context.field?.disabled ||
58
67
  context.field?.readOnly ||
59
68
  context.query.isLoading ||
@@ -70,6 +79,7 @@ export function IdFrontImageField__client(props) {
70
79
  */
71
80
  function Camera() {
72
81
  const context = useIdFrontImageFieldContext();
82
+ console.log(context.camera, 'context.camera');
73
83
  return (_jsx(Dialog.Root, { open: context.disclosure.open, onOpenChange: (details) => {
74
84
  context.disclosure.setOpen(details.open);
75
85
  }, 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: "mx-auto w-[calc(100dvw-1rem)] max-w-[calc(100dvw-1rem)] overflow-y-auto rounded-lg bg-bg-primary-alt 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: "mt-md text-center text-text-tertiary-600 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: "flex aspect-[4/3] flex-col items-center justify-center rounded-md border border-border-disabled bg-black px-4 lg:aspect-video", children: [_jsx(CameraOffIcon, { className: "size-10 text-center text-text-placeholder-subtle lg:size-12" }), _jsx("h2", { className: "mt-3 font-semibold text-sm lg:mt-4 lg:text-base", children: context.camera.error.name }), _jsx("p", { className: "mt-0.5 text-center text-text-tertiary-600 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: () => {
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Capacitor } from '@capacitor/core';
2
3
  import Image from 'next/image';
3
4
  import { useRef } from 'react';
4
5
  import { twMerge } from 'tailwind-merge';
@@ -52,8 +53,16 @@ export function SelfieImageField__client(props) {
52
53
  context.query.isLoading ||
53
54
  context.mutation.isPending ||
54
55
  localProps.disabled ||
55
- localProps.readOnly, className: "font-semibold text-button-secondary-fg 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: () => {
56
- context.disclosure.setOpen(true);
56
+ localProps.readOnly, className: "font-semibold text-button-secondary-fg 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: async () => {
57
+ if (Capacitor.isNativePlatform()) {
58
+ const data = await context.camera.openNativeCamera();
59
+ if (!data?.file)
60
+ return;
61
+ context.mutation.mutate({ file: data.file });
62
+ }
63
+ else {
64
+ context.disclosure.setOpen(true);
65
+ }
57
66
  }, disabled: context.field?.disabled ||
58
67
  context.field?.readOnly ||
59
68
  context.query.isLoading ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opexa/portal-components",
3
- "version": "0.0.680",
3
+ "version": "0.0.681",
4
4
  "exports": {
5
5
  "./ui/*": {
6
6
  "types": "./dist/ui/*/index.d.ts",
@@ -79,6 +79,7 @@
79
79
  "@ark-ui/react": "^5.16.0",
80
80
  "@capacitor/android": "^7.4.3",
81
81
  "@capacitor/app": "^7.0.2",
82
+ "@capacitor/camera": "^7.0.2",
82
83
  "@capacitor/cli": "^7.4.3",
83
84
  "@capacitor/core": "^7.4.3",
84
85
  "@capacitor/file-transfer": "^1.0.5",