@opexa/portal-components 0.0.108 → 0.0.110

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 (36) hide show
  1. package/dist/client/hooks/useCreateGCashDepositMutation.js +19 -1
  2. package/dist/client/hooks/useCreateMayaAppDepositMutation.js +19 -1
  3. package/dist/client/hooks/useCreateMayaDepositMutation.js +19 -1
  4. package/dist/client/hooks/useCreateOnlineBankDepositMutation.js +19 -1
  5. package/dist/client/hooks/useCreateQRPHDepositMutation.js +19 -1
  6. package/dist/client/hooks/useRemainingTime.d.ts +12 -1
  7. package/dist/client/hooks/useRemainingTime.js +37 -6
  8. package/dist/components/KYC/CaptureIdDocument.js +196 -2
  9. package/dist/components/KYC/DisplayImage.d.ts +2 -0
  10. package/dist/components/KYC/DisplayImage.js +10 -0
  11. package/dist/components/KYC/IdentityVerification.js +4 -3
  12. package/dist/components/KYC/useKYC.d.ts +2 -0
  13. package/dist/components/KYC/useKYC.js +3 -0
  14. package/dist/components/NewsFeed/NewsFeed.d.ts +4 -18
  15. package/dist/components/NewsFeed/NewsFeed.js +10 -10
  16. package/dist/components/NewsFeed/NewsFeedCarousel.d.ts +21 -0
  17. package/dist/components/NewsFeed/NewsFeedCarousel.js +47 -0
  18. package/dist/components/NewsFeed/NewsFeedList.d.ts +19 -0
  19. package/dist/components/NewsFeed/NewsFeedList.js +14 -0
  20. package/dist/components/Quests/DailyCheckInQuest.d.ts +1 -0
  21. package/dist/components/Quests/DailyCheckInQuest.js +96 -0
  22. package/dist/components/Quests/OnboardingQuest.d.ts +1 -0
  23. package/dist/components/Quests/OnboardingQuest.js +47 -0
  24. package/dist/components/Quests/Quests.client.d.ts +2 -0
  25. package/dist/components/Quests/Quests.client.js +6 -6
  26. package/dist/components/Quests/RemainingTime.d.ts +1 -0
  27. package/dist/components/Quests/RemainingTime.js +19 -0
  28. package/dist/components/Quests/WageringQuest.d.ts +1 -0
  29. package/dist/components/Quests/WageringQuest.js +36 -0
  30. package/dist/icons/Tv03Icon.d.ts +2 -0
  31. package/dist/icons/Tv03Icon.js +4 -0
  32. package/dist/utils/dataUrlToBlob.d.ts +1 -0
  33. package/dist/utils/dataUrlToBlob.js +11 -0
  34. package/dist/utils/resizeImageSize.d.ts +1 -0
  35. package/dist/utils/resizeImageSize.js +6 -0
  36. package/package.json +2 -1
@@ -1,12 +1,14 @@
1
1
  import { ObjectId } from '@opexa/object-id';
2
2
  import { useMutation } from '@tanstack/react-query';
3
+ import { clamp } from 'lodash-es';
3
4
  import invariant from 'tiny-invariant';
4
5
  import { ObjectType } from '../../services/ObjectType.js';
5
6
  import { createGCashDeposit, getDeposit, } from '../../services/wallet.js';
6
7
  import { createPoll } from '../../utils/createPoll.js';
7
8
  import { getQueryClient } from '../../utils/getQueryClient.js';
8
9
  import { getCreateGCashDepositMutationKey } from '../../utils/mutationKeys.js';
9
- import { getDepositsCountQueryKey } from '../../utils/queryKeys.js';
10
+ import { parseDecimal } from '../../utils/parseDecimal.js';
11
+ import { getAvailableQuestsQueryKey, getDepositsCountQueryKey, } from '../../utils/queryKeys.js';
10
12
  import { getSession } from '../services/getSession.js';
11
13
  export const useCreateGCashDepositMutation = (config) => {
12
14
  const queryClient = getQueryClient();
@@ -44,6 +46,22 @@ export const useCreateGCashDepositMutation = (config) => {
44
46
  error.message = 'Failed to process GCash deposit';
45
47
  throw error;
46
48
  }
49
+ const depositsCount = queryClient.getQueryData(getDepositsCountQueryKey()) ?? 0;
50
+ if (depositsCount <= 0) {
51
+ queryClient.setQueryData(getAvailableQuestsQueryKey(), (prev) => {
52
+ return prev?.map((quest) => {
53
+ const progressPercentage = clamp(parseDecimal(quest.progressPercentage, 0) + 50, 100);
54
+ if (quest.type === 'ONBOARDING') {
55
+ return {
56
+ ...quest,
57
+ status: progressPercentage >= 100 ? 'COMPLETED' : quest.status,
58
+ progressPercentage: progressPercentage.toFixed(2),
59
+ };
60
+ }
61
+ return quest;
62
+ });
63
+ });
64
+ }
47
65
  queryClient.setQueryData(getDepositsCountQueryKey(), (prev) => prev ? prev + 1 : 1);
48
66
  return res;
49
67
  },
@@ -1,12 +1,14 @@
1
1
  import { ObjectId } from '@opexa/object-id';
2
2
  import { useMutation } from '@tanstack/react-query';
3
+ import { clamp } from 'lodash-es';
3
4
  import invariant from 'tiny-invariant';
4
5
  import { ObjectType } from '../../services/ObjectType.js';
5
6
  import { createMayaAppDeposit, getDeposit, } from '../../services/wallet.js';
6
7
  import { createPoll } from '../../utils/createPoll.js';
7
8
  import { getQueryClient } from '../../utils/getQueryClient.js';
8
9
  import { getCreateMayaAppDepositMutationKey } from '../../utils/mutationKeys.js';
9
- import { getDepositsCountQueryKey } from '../../utils/queryKeys.js';
10
+ import { parseDecimal } from '../../utils/parseDecimal.js';
11
+ import { getAvailableQuestsQueryKey, getDepositsCountQueryKey, } from '../../utils/queryKeys.js';
10
12
  import { getSession } from '../services/getSession.js';
11
13
  export const useCreateMayaAppDepositMutation = (config) => {
12
14
  const queryClient = getQueryClient();
@@ -44,6 +46,22 @@ export const useCreateMayaAppDepositMutation = (config) => {
44
46
  error.message = 'Failed to process Maya deposit';
45
47
  throw error;
46
48
  }
49
+ const depositsCount = queryClient.getQueryData(getDepositsCountQueryKey()) ?? 0;
50
+ if (depositsCount <= 0) {
51
+ queryClient.setQueryData(getAvailableQuestsQueryKey(), (prev) => {
52
+ return prev?.map((quest) => {
53
+ const progressPercentage = clamp(parseDecimal(quest.progressPercentage, 0) + 50, 100);
54
+ if (quest.type === 'ONBOARDING') {
55
+ return {
56
+ ...quest,
57
+ status: progressPercentage >= 100 ? 'COMPLETED' : quest.status,
58
+ progressPercentage: progressPercentage.toFixed(2),
59
+ };
60
+ }
61
+ return quest;
62
+ });
63
+ });
64
+ }
47
65
  queryClient.setQueryData(getDepositsCountQueryKey(), (prev) => prev ? prev + 1 : 1);
48
66
  return res;
49
67
  },
@@ -1,12 +1,14 @@
1
1
  import { ObjectId } from '@opexa/object-id';
2
2
  import { useMutation } from '@tanstack/react-query';
3
+ import { clamp } from 'lodash-es';
3
4
  import invariant from 'tiny-invariant';
4
5
  import { ObjectType } from '../../services/ObjectType.js';
5
6
  import { createMayaDeposit, getDeposit, } from '../../services/wallet.js';
6
7
  import { createPoll } from '../../utils/createPoll.js';
7
8
  import { getQueryClient } from '../../utils/getQueryClient.js';
8
9
  import { getCreateMayaDepositMutationKey } from '../../utils/mutationKeys.js';
9
- import { getDepositsCountQueryKey } from '../../utils/queryKeys.js';
10
+ import { parseDecimal } from '../../utils/parseDecimal.js';
11
+ import { getAvailableQuestsQueryKey, getDepositsCountQueryKey, } from '../../utils/queryKeys.js';
10
12
  import { getSession } from '../services/getSession.js';
11
13
  export const useCreateMayaDepositMutation = (config) => {
12
14
  const queryClient = getQueryClient();
@@ -44,6 +46,22 @@ export const useCreateMayaDepositMutation = (config) => {
44
46
  error.message = 'Failed to process Maya deposit';
45
47
  throw error;
46
48
  }
49
+ const depositsCount = queryClient.getQueryData(getDepositsCountQueryKey()) ?? 0;
50
+ if (depositsCount <= 0) {
51
+ queryClient.setQueryData(getAvailableQuestsQueryKey(), (prev) => {
52
+ return prev?.map((quest) => {
53
+ const progressPercentage = clamp(parseDecimal(quest.progressPercentage, 0) + 50, 100);
54
+ if (quest.type === 'ONBOARDING') {
55
+ return {
56
+ ...quest,
57
+ status: progressPercentage >= 100 ? 'COMPLETED' : quest.status,
58
+ progressPercentage: progressPercentage.toFixed(2),
59
+ };
60
+ }
61
+ return quest;
62
+ });
63
+ });
64
+ }
47
65
  queryClient.setQueryData(getDepositsCountQueryKey(), (prev) => prev ? prev + 1 : 1);
48
66
  return res;
49
67
  },
@@ -1,12 +1,14 @@
1
1
  import { ObjectId } from '@opexa/object-id';
2
2
  import { useMutation } from '@tanstack/react-query';
3
+ import { clamp } from 'lodash-es';
3
4
  import invariant from 'tiny-invariant';
4
5
  import { ObjectType } from '../../services/ObjectType.js';
5
6
  import { createAIOOnlineBankDeposit, getDeposit, } from '../../services/wallet.js';
6
7
  import { createPoll } from '../../utils/createPoll.js';
7
8
  import { getQueryClient } from '../../utils/getQueryClient.js';
8
9
  import { getCreateOnlineBankDepositMutationKey } from '../../utils/mutationKeys.js';
9
- import { getDepositsCountQueryKey } from '../../utils/queryKeys.js';
10
+ import { parseDecimal } from '../../utils/parseDecimal.js';
11
+ import { getAvailableQuestsQueryKey, getDepositsCountQueryKey, } from '../../utils/queryKeys.js';
10
12
  import { getSession } from '../services/getSession.js';
11
13
  export const useCreateOnlineBankDepositMutation = (config) => {
12
14
  const queryClient = getQueryClient();
@@ -47,6 +49,22 @@ export const useCreateOnlineBankDepositMutation = (config) => {
47
49
  error.message = 'Failed to process online bank deposit';
48
50
  throw error;
49
51
  }
52
+ const depositsCount = queryClient.getQueryData(getDepositsCountQueryKey()) ?? 0;
53
+ if (depositsCount <= 0) {
54
+ queryClient.setQueryData(getAvailableQuestsQueryKey(), (prev) => {
55
+ return prev?.map((quest) => {
56
+ const progressPercentage = clamp(parseDecimal(quest.progressPercentage, 0) + 50, 100);
57
+ if (quest.type === 'ONBOARDING') {
58
+ return {
59
+ ...quest,
60
+ status: progressPercentage >= 100 ? 'COMPLETED' : quest.status,
61
+ progressPercentage: progressPercentage.toFixed(2),
62
+ };
63
+ }
64
+ return quest;
65
+ });
66
+ });
67
+ }
50
68
  queryClient.setQueryData(getDepositsCountQueryKey(), (prev) => prev ? prev + 1 : 1);
51
69
  return res;
52
70
  },
@@ -1,12 +1,14 @@
1
1
  import { ObjectId } from '@opexa/object-id';
2
2
  import { useMutation } from '@tanstack/react-query';
3
+ import { clamp } from 'lodash-es';
3
4
  import invariant from 'tiny-invariant';
4
5
  import { ObjectType } from '../../services/ObjectType.js';
5
6
  import { createAIOQRPHDeposit, getDeposit, } from '../../services/wallet.js';
6
7
  import { createPoll } from '../../utils/createPoll.js';
7
8
  import { getQueryClient } from '../../utils/getQueryClient.js';
8
9
  import { getCreateQRPHDepositMutationKey } from '../../utils/mutationKeys.js';
9
- import { getDepositsCountQueryKey } from '../../utils/queryKeys.js';
10
+ import { parseDecimal } from '../../utils/parseDecimal.js';
11
+ import { getAvailableQuestsQueryKey, getDepositsCountQueryKey, } from '../../utils/queryKeys.js';
10
12
  import { getSession } from '../services/getSession.js';
11
13
  export const useCreateQRPHDepositMutation = (config) => {
12
14
  const queryClient = getQueryClient();
@@ -47,6 +49,22 @@ export const useCreateQRPHDepositMutation = (config) => {
47
49
  error.message = 'Failed to process QR PH deposit';
48
50
  throw error;
49
51
  }
52
+ const depositsCount = queryClient.getQueryData(getDepositsCountQueryKey()) ?? 0;
53
+ if (depositsCount <= 0) {
54
+ queryClient.setQueryData(getAvailableQuestsQueryKey(), (prev) => {
55
+ return prev?.map((quest) => {
56
+ const progressPercentage = clamp(parseDecimal(quest.progressPercentage, 0) + 50, 100);
57
+ if (quest.type === 'ONBOARDING') {
58
+ return {
59
+ ...quest,
60
+ status: progressPercentage >= 100 ? 'COMPLETED' : quest.status,
61
+ progressPercentage: progressPercentage.toFixed(2),
62
+ };
63
+ }
64
+ return quest;
65
+ });
66
+ });
67
+ }
50
68
  queryClient.setQueryData(getDepositsCountQueryKey(), (prev) => prev ? prev + 1 : 1);
51
69
  return res;
52
70
  },
@@ -1,7 +1,18 @@
1
- export declare function useRemainingTime(value: Date | string | number): {
1
+ type EventDetails = {
2
+ type: 'EXPIRED';
3
+ };
4
+ type UnsubscribeFn = () => void;
5
+ type Subscriber = (details: EventDetails) => UnsubscribeFn;
6
+ interface UseRemainingTimeOptions {
7
+ onExpired?: () => void;
8
+ }
9
+ export declare function useRemainingTime(value: Date | string | number, options?: UseRemainingTimeOptions): {
2
10
  days: number;
3
11
  hours: number;
4
12
  minutes: number;
5
13
  expired: boolean;
14
+ seconds: number;
6
15
  toString(): string;
16
+ subscribe: (subscriber: Subscriber) => () => void;
7
17
  };
18
+ export {};
@@ -1,8 +1,25 @@
1
- import { differenceInDays, differenceInHours, differenceInMinutes, isAfter, } from 'date-fns';
2
- import { isNumber, isString } from 'lodash-es';
3
- import { useState } from 'react';
1
+ import { differenceInDays, differenceInHours, differenceInMinutes, differenceInSeconds, isAfter, } from 'date-fns';
2
+ import { isNumber, isString, noop } from 'lodash-es';
3
+ import { useRef, useState } from 'react';
4
4
  import { useInterval } from 'usehooks-ts';
5
- export function useRemainingTime(value) {
5
+ export function useRemainingTime(value, options = {}) {
6
+ const subscribers = useRef([
7
+ () => {
8
+ options.onExpired?.();
9
+ return noop;
10
+ },
11
+ ]);
12
+ const subscribe = (subscriber) => {
13
+ subscribers.current.push(subscriber);
14
+ return () => {
15
+ subscribers.current = subscribers.current.filter((item) => item !== subscriber);
16
+ };
17
+ };
18
+ const triggerEvent = (details) => {
19
+ subscribers.current.forEach((subscriber) => {
20
+ subscriber(details);
21
+ });
22
+ };
6
23
  const [now, setNow] = useState(new Date());
7
24
  const expiration = isString(value) || isNumber(value) ? new Date(value) : value;
8
25
  const expired = isAfter(now, expiration);
@@ -11,14 +28,28 @@ export function useRemainingTime(value) {
11
28
  const minutes = expired
12
29
  ? 0
13
30
  : differenceInMinutes(expiration, now) - days * 24 * 60 - hours * 60;
14
- useInterval(() => setNow(new Date()), expired ? null : 1000);
31
+ const seconds = expired
32
+ ? 0
33
+ : differenceInSeconds(expiration, now) -
34
+ days * 24 * 60 * 60 -
35
+ hours * 60 * 60 -
36
+ minutes * 60;
37
+ useInterval(() => {
38
+ const d = new Date();
39
+ setNow(d);
40
+ if (isAfter(d, expiration)) {
41
+ triggerEvent({ type: 'EXPIRED' });
42
+ }
43
+ }, expired ? null : 1000);
15
44
  return {
16
45
  days,
17
46
  hours,
18
47
  minutes,
19
48
  expired,
49
+ seconds,
20
50
  toString() {
21
- return `${days}d:${hours}h:${minutes}m`;
51
+ return `${days}d:${hours}h:${minutes}m:${seconds}s`;
22
52
  },
53
+ subscribe,
23
54
  };
24
55
  }
@@ -1,11 +1,205 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { useUploadImageFileMutation } from '../../client/hooks/useUploadImageFileMutation.js';
2
6
  import { ArrowLeftIcon } from '../../icons/ArrowLeftIcon.js';
3
7
  import { Camera01Icon } from '../../icons/Camera01Icon.js';
8
+ import { CheckCircleIcon } from '../../icons/CheckCircleIcon.js';
4
9
  import { XIcon } from '../../icons/XIcon.js';
5
10
  import { Button } from '../../ui/Button/index.js';
6
11
  import { Dialog } from '../../ui/Dialog/index.js';
12
+ import { dataURLtoBlob } from '../../utils/dataUrlToBlob.js';
13
+ import { resizeFile } from '../../utils/resizeImageSize.js';
7
14
  import { useKYCContext } from './KYCContext.js';
8
15
  export function CaptureIdDocument() {
9
16
  const kyc = useKYCContext();
10
- return (_jsxs("div", { className: "w-full p-4 lg:w-[640px] lg:px-12 lg:py-4xl", children: [_jsxs("button", { type: "button", className: "flex items-center gap-lg lg:absolute lg:right-5 lg:top-5 lg:p-md", onClick: () => kyc.setCapturing(null), children: [_jsx(XIcon, { className: "text-text-senary-300 hidden size-5 lg:block" }), _jsx(ArrowLeftIcon, { className: "text-text-senary-300 size-5 lg:hidden" }), _jsx("span", { className: "text-lg font-semibold lg:hidden", children: "Back" })] }), _jsx(Dialog.Title, { className: "mt-6 text-center text-lg font-semibold lg:mt-0", children: "Take a Picture of Your Front ID" }), _jsxs(Dialog.Description, { className: "lg:max-w-auto text-text-tertiary-600 mx-auto mt-sm max-w-[19rem] text-center text-sm lg:max-w-full", 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 the frame."] }), _jsx("div", { className: "border-border-disabled mt-6 aspect-video rounded-xl border bg-black lg:mt-8" }), _jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-6 px-xl disabled:opacity-100 lg:mt-8 lg:w-fit", disabled: true, children: ["Capture", _jsx(Camera01Icon, { className: "size-5" })] })] }));
17
+ const { mutate, isPending } = useUploadImageFileMutation();
18
+ const [videoStream, setVideoStream] = useState(null);
19
+ const [cameraError, setCameraError] = useState(null);
20
+ const [isLoading, setIsLoading] = useState(false);
21
+ const [selectedImage, setSelectedImage] = useState(null);
22
+ const [captureInProgress, setCaptureInProgress] = useState(false);
23
+ const [proccessImageCanvas, setProccessImageCanvas] = useState(null);
24
+ const videoElement = useRef(null);
25
+ const containerRef = useRef(null);
26
+ function stopCamera() {
27
+ if (videoStream) {
28
+ videoStream.getTracks().forEach((track) => track.stop());
29
+ setVideoStream(null);
30
+ }
31
+ }
32
+ useEffect(() => {
33
+ return () => {
34
+ stopCamera();
35
+ };
36
+ }, [videoStream]);
37
+ async function startCamera() {
38
+ try {
39
+ setIsLoading(true);
40
+ setCameraError(null);
41
+ setCaptureInProgress(true);
42
+ console.log('Starting camera...');
43
+ const stream = await navigator.mediaDevices.getUserMedia({
44
+ video: {
45
+ facingMode: 'environment',
46
+ aspectRatio: 16 / 9,
47
+ width: { ideal: 1920, max: 1920 },
48
+ height: { ideal: 1080, max: 1080 },
49
+ frameRate: {
50
+ ideal: 30,
51
+ max: 60,
52
+ },
53
+ },
54
+ audio: false,
55
+ });
56
+ // Set the stream directly to the video element
57
+ if (videoElement.current) {
58
+ videoElement.current.srcObject = stream;
59
+ videoElement.current.onloadedmetadata = () => {
60
+ console.log('Video metadata loaded, starting playback...');
61
+ videoElement.current
62
+ ?.play()
63
+ .then(() => {
64
+ setVideoStream(stream);
65
+ setIsLoading(false);
66
+ })
67
+ .catch(() => {
68
+ setCameraError('Failed to start camera stream. Please try again.');
69
+ setIsLoading(false);
70
+ // Stop the stream if playback fails
71
+ stream.getTracks().forEach((track) => track.stop());
72
+ });
73
+ };
74
+ videoElement.current.onerror = (err) => {
75
+ console.error('Video element error:', err);
76
+ setCameraError('Video playback error. Please try again.');
77
+ setIsLoading(false);
78
+ stream.getTracks().forEach((track) => track.stop());
79
+ };
80
+ }
81
+ else {
82
+ console.error('Video element not available');
83
+ setCameraError('Camera initialization failed. Please try again.');
84
+ setCaptureInProgress(false);
85
+ setIsLoading(false);
86
+ stream.getTracks().forEach((track) => track.stop());
87
+ }
88
+ }
89
+ catch (error) {
90
+ setIsLoading(false);
91
+ if (error instanceof DOMException) {
92
+ console.error('Error accessing camera:', error.message);
93
+ setCameraError(error.message);
94
+ }
95
+ else {
96
+ console.error('Unknown error accessing camera:', error);
97
+ setCameraError('An unknown error occurred. Please check permissions and try again.');
98
+ }
99
+ setCaptureInProgress(false);
100
+ }
101
+ }
102
+ function captureAndProcessImage() {
103
+ if (!videoElement.current || !videoStream) {
104
+ console.error('Video element or stream not available for capture');
105
+ return;
106
+ }
107
+ try {
108
+ const canvas = document.createElement('canvas');
109
+ const parentDiv = document.getElementById('parent');
110
+ canvas.width = parentDiv?.clientWidth || videoElement.current.width;
111
+ canvas.height = parentDiv?.clientHeight || videoElement.current.height;
112
+ const ctx = canvas.getContext('2d');
113
+ if (!ctx) {
114
+ console.error('Could not get 2D context for canvas');
115
+ setCameraError('Failed to process image. Please try again.');
116
+ setCaptureInProgress(false);
117
+ return;
118
+ }
119
+ ctx.drawImage(videoElement.current, 0, 0, canvas.width, canvas.height);
120
+ const imageSrc = canvas.toDataURL('image/png', 1.0);
121
+ setSelectedImage({ src: imageSrc });
122
+ setCaptureInProgress(false);
123
+ stopCamera();
124
+ const newImg = document.createElement('img');
125
+ newImg.src = imageSrc;
126
+ //@ts-expect-error jscanify is not typed
127
+ const scanner = new jscanify();
128
+ newImg.onload = () => {
129
+ try {
130
+ if (containerRef.current) {
131
+ // containerRef.current.innerHTML = '';
132
+ const paperWidth = parentDiv?.clientWidth || videoElement.current?.videoWidth;
133
+ const paperHeight = parentDiv?.clientHeight || videoElement.current?.videoHeight;
134
+ //@ts-expect-error jscanify is not typed
135
+ const contour = scanner.findPaperContour(cv.imread(newImg));
136
+ const cornerPoints = scanner.getCornerPoints(contour);
137
+ const resultCanvas = scanner.extractPaper(newImg, paperWidth, paperHeight, cornerPoints);
138
+ newImg.className =
139
+ 'absolute inset-0 m-auto w-full h-auto md:max-h-[333px] md:max-w-[643px] aspect-video rounded-lg object-contain';
140
+ setProccessImageCanvas(resultCanvas);
141
+ containerRef.current.appendChild(resultCanvas);
142
+ setCaptureInProgress(false);
143
+ setSelectedImage({ src: resultCanvas.toDataURL('image/png') });
144
+ }
145
+ }
146
+ catch (error) {
147
+ console.error('Error processing image:', error);
148
+ setCameraError('Failed to process image. Please try again.');
149
+ }
150
+ };
151
+ newImg.onerror = () => {
152
+ console.error('Error loading image');
153
+ setCameraError('Failed to load image. Please try again.');
154
+ };
155
+ }
156
+ catch (error) {
157
+ console.error('Error capturing image:', error);
158
+ setCameraError('Failed to capture image. Please try again.');
159
+ setCaptureInProgress(false);
160
+ }
161
+ }
162
+ async function uploadPhoto() {
163
+ if (!proccessImageCanvas) {
164
+ console.error('Failed to process the image. Please try again.');
165
+ return;
166
+ }
167
+ const blob = proccessImageCanvas.toDataURL('image/png', 1.0);
168
+ const processedBlob = dataURLtoBlob(blob);
169
+ const img = new File([processedBlob], 'frontImage', { type: 'image/jpeg' });
170
+ const resizedImage = (await resizeFile(img));
171
+ if (resizedImage) {
172
+ await mutate({
173
+ file: resizedImage,
174
+ }, {
175
+ onSuccess: (data) => {
176
+ kyc.setFrontImageId(data);
177
+ kyc.setCapturing(null);
178
+ },
179
+ });
180
+ }
181
+ }
182
+ function retakePhoto() {
183
+ setSelectedImage(null);
184
+ setCaptureInProgress(true);
185
+ setCameraError(null);
186
+ if (containerRef.current) {
187
+ const canvas = containerRef.current.querySelector('canvas');
188
+ if (canvas) {
189
+ canvas.width = canvas.width;
190
+ }
191
+ containerRef.current.innerHTML = '';
192
+ }
193
+ if (videoElement.current) {
194
+ videoElement.current.play();
195
+ }
196
+ startCamera();
197
+ }
198
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "w-full p-4 lg:w-[640px] lg:px-12 lg:py-4xl", children: [_jsxs("button", { type: "button", className: "flex items-center gap-lg lg:absolute lg:right-5 lg:top-5 lg:p-md", onClick: () => {
199
+ stopCamera();
200
+ kyc.setCapturing(null);
201
+ }, children: [_jsx(XIcon, { className: "text-text-senary-300 hidden size-5 lg:block" }), _jsx(ArrowLeftIcon, { className: "text-text-senary-300 size-5 lg:hidden" }), _jsx("span", { className: "text-lg font-semibold lg:hidden", children: "Back" })] }), _jsx(Dialog.Title, { className: "mt-6 text-center text-lg font-semibold lg:mt-0", children: "Take a Picture of Your Front ID" }), _jsxs(Dialog.Description, { className: "lg:max-w-auto text-text-tertiary-600 mx-auto mt-sm max-w-[19rem] text-center text-sm lg:max-w-full", 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 the frame."] }), _jsx("div", { id: "parent", className: "border-border-disabled mt-6 aspect-video rounded-xl border bg-black lg:mt-8", children: cameraError ? (_jsx("div", { className: "flex h-full items-center justify-center p-4", children: _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-red-500 mb-2 text-sm", children: "Camera Error" }), _jsx("p", { className: "text-gray-400 text-xs", children: cameraError })] }) })) : captureInProgress ? (_jsxs("div", { className: "relative flex h-fit w-full flex-col items-center justify-center", children: [_jsx("video", { ref: videoElement, autoPlay: true, muted: true, playsInline: true, className: "relative h-[60vh] w-full rounded-lg object-cover md:aspect-video md:max-h-[333px]", width: "100%", height: "100%" }), _jsxs("div", { className: "pointer-events-none absolute inset-0 z-50", children: [_jsx("div", { className: twMerge('absolute inset-0 w-full bg-black/60 backdrop-blur-sm [clip-path:polygon(0%_0%,0%_100%,calc(50%-158px)_100%,calc(50%-158px)_calc(50%-100px),calc(50%+158px)_calc(50%-100px),calc(50%+158px)_calc(50%+100px),calc(50%-158px)_calc(50%+100px),calc(50%-158px)_100%,100%_100%,100%_0%)] md:[clip-path:polygon(0%_0%,0%_100%,calc(50%-225px)_100%,calc(50%-225px)_calc(50%-142px),calc(50%+225px)_calc(50%-142px),calc(50%+225px)_calc(50%+142px),calc(50%-225px)_calc(50%+142px),calc(50%-225px)_100%,100%_100%,100%_0%)]') }), _jsx("div", { className: "absolute inset-0 z-[60] mx-auto flex items-center justify-center", children: _jsx("div", { className: "relative aspect-[1011/638] h-[200px] md:h-[295px] md:w-[450px]", children: _jsx("div", { className: "h-full w-full rounded-lg border-4 border-dashed border-[#B54708]" }) }) })] })] })) : (_jsx("div", { ref: containerRef, className: "relative grid h-full w-full place-items-center rounded-lg object-contain md:max-h-[333px]" })) }), cameraError ? (_jsx("div", { className: "mt-4 text-center", children: _jsx(Button, { className: "mx-auto px-xl lg:w-fit", onClick: () => {
202
+ setCameraError(null);
203
+ startCamera();
204
+ }, children: "Try Again" }) })) : captureInProgress ? (_jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-6 px-xl disabled:opacity-100 lg:mt-8 lg:w-fit", onClick: captureAndProcessImage, children: ["Capture Image", _jsx(Camera01Icon, { className: "size-5" })] })) : selectedImage && selectedImage.src ? (_jsxs("div", { className: "mt-4 flex flex-col items-center justify-center gap-2 md:flex-row", children: [_jsxs(Button, { className: "lg:w-fit", type: "button", onClick: retakePhoto, children: ["Retake Photo ", _jsx(Camera01Icon, { className: "ml-1" })] }), _jsxs(Button, { className: "lg:w-fit", type: "button", onClick: uploadPhoto, disabled: isPending, children: ["Use Photo ", _jsx(CheckCircleIcon, { className: "ml-1" })] })] })) : (_jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-6 px-xl disabled:opacity-100 lg:mt-8 lg:w-fit", onClick: startCamera, disabled: isLoading || !!videoStream, children: [isLoading ? 'Starting Camera...' : 'Start Camera', _jsx(Camera01Icon, { className: "size-5" })] }))] }) }));
11
205
  }
@@ -0,0 +1,2 @@
1
+ declare function DisplayImage(): import("react/jsx-runtime").JSX.Element;
2
+ export default DisplayImage;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Image from 'next/image';
3
+ import { useFileQuery } from '../../client/hooks/useFileQuery.js';
4
+ import { useKYCContext } from './KYCContext.js';
5
+ function DisplayImage() {
6
+ const kyc = useKYCContext();
7
+ const { data } = useFileQuery(kyc.idFrontImageId ?? '');
8
+ return (_jsx("div", { className: "border-border-primary bg-bg-primary min-h-[11.25rem]", children: data?.url && (_jsx(Image, { src: data.url, alt: "Uploaded", className: "w-full object-contain", width: 500, height: 500 })) }));
9
+ }
10
+ export default DisplayImage;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useShallow } from 'zustand/shallow';
3
3
  import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
4
4
  import { HelpCircleIcon } from '../../icons/HelpCircleIcon.js';
@@ -7,6 +7,7 @@ import { Button } from '../../ui/Button/index.js';
7
7
  import { Dialog } from '../../ui/Dialog/index.js';
8
8
  import { Field } from '../../ui/Field/index.js';
9
9
  import { Portal } from '../../ui/Portal/index.js';
10
+ import DisplayImage from './DisplayImage.js';
10
11
  import { useKYCContext } from './KYCContext.js';
11
12
  export function IdentityVerification() {
12
13
  const kyc = useKYCContext();
@@ -24,9 +25,9 @@ export function IdentityVerification() {
24
25
  }
25
26
  function UploadIdDocument() {
26
27
  const kyc = useKYCContext();
27
- 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: [_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: () => {
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, {})) : (_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: () => {
28
29
  kyc.setCapturing('ID_DOCUMENT');
29
- }, children: "Take a photo" })] }));
30
+ }, children: kyc.idFrontImageId ? 'Rekate Photo' : 'Take a Photo' })] }));
30
31
  }
31
32
  function UploadSelfie() {
32
33
  const kyc = useKYCContext();
@@ -17,6 +17,8 @@ export interface UseKYCReturn {
17
17
  setCapturing: (capturing: CaptureSubject | null) => void;
18
18
  subscribe: (subscriber: Subscriber) => UnsubscribeFn;
19
19
  triggerEvent: (detail: EventDetail) => void;
20
+ idFrontImageId: string | null;
21
+ setFrontImageId: (id: string | null) => void;
20
22
  }
21
23
  export declare function useKYC(): UseKYCReturn;
22
24
  export {};
@@ -2,6 +2,7 @@ import { useRef, useState } from 'react';
2
2
  export function useKYC() {
3
3
  const [step, setStep] = useState(1);
4
4
  const [done, setDone] = useState(false);
5
+ const [idFrontImageId, setFrontImageId] = useState(null);
5
6
  const [capturing, setCapturing] = useState(null);
6
7
  const subscribers = useRef([]);
7
8
  const subscribe = (subscriber) => {
@@ -22,6 +23,8 @@ export function useKYC() {
22
23
  setDone,
23
24
  capturing,
24
25
  setCapturing,
26
+ idFrontImageId,
27
+ setFrontImageId,
25
28
  subscribe,
26
29
  triggerEvent,
27
30
  };
@@ -1,18 +1,4 @@
1
- import type { CSSProperties } from 'react';
2
- import type { News } from '../../types';
3
- export interface NewsFeedProps {
4
- newsFeed: News[];
5
- /** @default 'News' */
6
- heading?: string;
7
- /**
8
- * @default '/news'
9
- * @example
10
- * ```ts
11
- * '/custom-path' // '/custom-path/123'
12
- * ```
13
- */
14
- viewDetailsUrl?: string;
15
- className?: string;
16
- style?: CSSProperties;
17
- }
18
- export declare function NewsFeed(props: NewsFeedProps): import("react/jsx-runtime").JSX.Element;
1
+ import { type NewsFeedCarouselProps } from './NewsFeedCarousel';
2
+ import { type NewsFeedListProps } from './NewsFeedList';
3
+ export type NewsFeedProps = NewsFeedListProps | NewsFeedCarouselProps;
4
+ export declare function NewsFeed(props: NewsFeedProps): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -1,11 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import Image from 'next/image';
3
- import Link from 'next/link';
4
- import { Button } from '../../ui/Button/index.js';
5
- export function NewsFeed(props) {
6
- const heading = props.heading ?? 'News';
7
- const viewDetailsUrl = props.viewDetailsUrl ?? '/news';
8
- return (_jsxs("div", { style: props.style, className: props.className, children: [_jsx("h2", { className: "text-lg font-semibold", children: heading }), _jsx("div", { className: "mt-lg grid gap-3xl lg:grid-cols-3 lg:gap-2xl", children: props.newsFeed.map((news) => (_jsxs("div", { className: "bg-bg-primary-alt border-border-primary flex flex-col overflow-hidden rounded-2xl border shadow-md", children: [_jsx(Image, { src: news.image, alt: "", width: 356, height: 244, className: "block h-auto w-full" }), _jsxs("div", { className: "flex grow flex-col p-3xl", children: [_jsxs("div", { className: "grow", children: [_jsx("p", { className: "text-brand-400 text-sm font-semibold", children: news.category }), _jsx("h2", { className: "mt-2 text-lg font-semibold", children: news.title }), _jsx("p", { className: "text-text-tertiary-600 mt-xs line-clamp-3", dangerouslySetInnerHTML: {
9
- __html: news.content,
10
- } })] }), _jsx(Button, { asChild: true, className: "mt-2xl", children: _jsx(Link, { href: `${viewDetailsUrl}/${news.id}`, children: "Read More" }) })] })] }, news.id))) })] }));
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { NewsFeedCarousel, } from './NewsFeedCarousel.js';
3
+ import { NewsFeedList } from './NewsFeedList.js';
4
+ export async function NewsFeed(props) {
5
+ if (props.layout === 'list') {
6
+ return _jsx(NewsFeedList, { ...props });
7
+ }
8
+ else {
9
+ return _jsx(NewsFeedCarousel, { ...props });
10
+ }
11
11
  }
@@ -0,0 +1,21 @@
1
+ import { type CSSProperties } from 'react';
2
+ import type { News } from '../../types';
3
+ export interface NewsFeedCarouselProps {
4
+ layout: 'carousel';
5
+ newsFeed: News[];
6
+ /** @default 'News' */
7
+ heading?: string;
8
+ /** @default '/news' */
9
+ viewAllUrl?: string;
10
+ /**
11
+ * @default '/news'
12
+ * @example
13
+ * ```ts
14
+ * '/custom-path' // '/custom-path/123'
15
+ * ```
16
+ */
17
+ viewDetailsUrl?: string;
18
+ className?: string;
19
+ style?: CSSProperties;
20
+ }
21
+ export declare function NewsFeedCarousel(props: NewsFeedCarouselProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import useEmblaCarousel, {} from 'embla-carousel-react';
4
+ import Image from 'next/image';
5
+ import Link from 'next/link';
6
+ import { useCallback, useEffect, useState } from 'react';
7
+ import { twMerge } from 'tailwind-merge';
8
+ import { ArrowLeftIcon } from '../../icons/ArrowLeftIcon.js';
9
+ import { ArrowRightIcon } from '../../icons/ArrowRightIcon.js';
10
+ import { ChevronRightIcon } from '../../icons/ChevronRightIcon.js';
11
+ import { Tv03Icon } from '../../icons/Tv03Icon.js';
12
+ import { Button } from '../../ui/Button/index.js';
13
+ import { Empty } from '../shared/Empty.js';
14
+ export function NewsFeedCarousel(props) {
15
+ const empty = props.newsFeed.length <= 0;
16
+ const heading = props.heading ?? 'News';
17
+ const viewAllUrl = props.viewAllUrl ?? '/news';
18
+ const viewDetailsUrl = props.viewDetailsUrl ?? '/news';
19
+ const [emblaRef, emblaApi] = useEmblaCarousel({
20
+ align: 'start',
21
+ slidesToScroll: 1,
22
+ breakpoints: {
23
+ '(min-width: 1024px)': {
24
+ slidesToScroll: 3,
25
+ },
26
+ },
27
+ });
28
+ const [prevBtnDisabled, setPrevBtnDisabled] = useState(true);
29
+ const [nextBtnDisabled, setNextBtnDisabled] = useState(true);
30
+ const onPrevButtonClick = useCallback(() => emblaApi?.scrollPrev(), [emblaApi]);
31
+ const onNextButtonClick = useCallback(async () => {
32
+ emblaApi?.scrollNext();
33
+ }, [emblaApi]);
34
+ const onSelect = useCallback((emblaApi) => {
35
+ setPrevBtnDisabled(!emblaApi?.canScrollPrev());
36
+ setNextBtnDisabled(!emblaApi?.canScrollNext());
37
+ }, []);
38
+ useEffect(() => {
39
+ if (!emblaApi)
40
+ return;
41
+ onSelect(emblaApi);
42
+ emblaApi.on('reInit', onSelect).on('select', onSelect);
43
+ }, [emblaApi, onSelect]);
44
+ return (_jsxs("div", { style: props.style, className: props.className, children: [_jsxs("div", { className: "flex items-center", children: [_jsx("h2", { className: "text-lg font-semibold", children: heading }), _jsx("div", { className: "grow" }), _jsxs("div", { className: "flex items-center justify-center gap-xl", children: [_jsxs(Link, { href: viewAllUrl, className: "text-button-tertiary-fg flex gap-sm text-sm font-semibold", children: ["See All", _jsx(ChevronRightIcon, { className: "size-5 lg:hidden" })] }), _jsxs("div", { className: "hidden lg:flex", children: [_jsx(Button, { variant: "outline", colorScheme: "gray", className: "rounded-r-none border-r-0", onClick: onPrevButtonClick, disabled: prevBtnDisabled, "aria-label": "Previous", children: _jsx(ArrowLeftIcon, { className: "size-5" }) }), _jsx(Button, { variant: "outline", colorScheme: "gray", className: "rounded-l-none", onClick: onNextButtonClick, disabled: nextBtnDisabled, "aria-label": "Next", children: _jsx(ArrowRightIcon, { className: "size-5" }) })] })] })] }), empty && (_jsx(Empty, { icon: Tv03Icon, title: "No News", message: "No news is currently available.", className: "mt-8" })), !empty && (_jsx(_Fragment, { children: _jsx("div", { ref: emblaRef, className: "relative mt-lg lg:overflow-hidden", children: _jsx("div", { className: twMerge('grid auto-cols-[100%] grid-flow-col gap-1.5 lg:auto-cols-[calc((100%-(1.25rem*2))/3)] lg:gap-2xl'), children: props.newsFeed.map((news) => (_jsxs("div", { className: "bg-bg-primary-alt border-border-primary flex flex-col overflow-hidden rounded-2xl border shadow-md", children: [_jsx(Image, { src: news.image, alt: "", width: 356, height: 244, className: "block h-auto w-full" }), _jsxs("div", { className: "flex grow flex-col p-3xl", children: [_jsxs("div", { className: "grow", children: [_jsx("p", { className: "text-brand-400 text-sm font-semibold", children: news.category }), _jsx("h2", { className: "mt-2 text-lg font-semibold", children: news.title }), _jsx("p", { className: "text-text-tertiary-600 mt-xs line-clamp-3", dangerouslySetInnerHTML: {
45
+ __html: news.content,
46
+ } })] }), _jsx(Button, { asChild: true, className: "mt-2xl", children: _jsx(Link, { href: `${viewDetailsUrl}/${news.id}`, children: "Read More" }) })] })] }, news.id))) }) }) }))] }));
47
+ }
@@ -0,0 +1,19 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { News } from '../../types';
3
+ export interface NewsFeedListProps {
4
+ layout: 'list';
5
+ newsFeed: News[];
6
+ /** @default 'News' */
7
+ heading?: string;
8
+ /**
9
+ * @default '/news'
10
+ * @example
11
+ * ```ts
12
+ * '/custom-path' // '/custom-path/123'
13
+ * ```
14
+ */
15
+ viewDetailsUrl?: string;
16
+ className?: string;
17
+ style?: CSSProperties;
18
+ }
19
+ export declare function NewsFeedList(props: NewsFeedListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Image from 'next/image';
3
+ import Link from 'next/link';
4
+ import { Tv03Icon } from '../../icons/Tv03Icon.js';
5
+ import { Button } from '../../ui/Button/index.js';
6
+ import { Empty } from '../shared/Empty.js';
7
+ export function NewsFeedList(props) {
8
+ const empty = props.newsFeed.length <= 0;
9
+ const heading = props.heading ?? 'News';
10
+ const viewDetailsUrl = props.viewDetailsUrl ?? '/news';
11
+ return (_jsxs("div", { style: props.style, className: props.className, children: [_jsx("h2", { className: "text-lg font-semibold", children: heading }), empty && (_jsx(Empty, { icon: Tv03Icon, title: "No News", message: "No news is currently available.", className: "mt-8" })), !empty && (_jsx("div", { className: "mt-lg grid gap-3xl lg:grid-cols-3 lg:gap-2xl", children: props.newsFeed.map((news) => (_jsxs("div", { className: "bg-bg-primary-alt border-border-primary flex flex-col overflow-hidden rounded-2xl border shadow-md", children: [_jsx(Image, { src: news.image, alt: "", width: 356, height: 244, className: "block h-auto w-full" }), _jsxs("div", { className: "flex grow flex-col p-3xl", children: [_jsxs("div", { className: "grow", children: [_jsx("p", { className: "text-brand-400 text-sm font-semibold", children: news.category }), _jsx("h2", { className: "mt-2 text-lg font-semibold", children: news.title }), _jsx("p", { className: "text-text-tertiary-600 mt-xs line-clamp-3", dangerouslySetInnerHTML: {
12
+ __html: news.content,
13
+ } })] }), _jsx(Button, { asChild: true, className: "mt-2xl", children: _jsx(Link, { href: `${viewDetailsUrl}/${news.id}`, children: "Read More" }) })] })] }, news.id))) }))] }));
14
+ }
@@ -0,0 +1 @@
1
+ export declare function DailyCheckInQuest(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,96 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { addDays, isEqual, startOfDay } from 'date-fns';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { useCheckInDailyCheckInQuestMutation } from '../../client/hooks/useCheckInDailyCheckInQuestMutation.js';
6
+ import { useControllableState } from '../../client/hooks/useControllableState.js';
7
+ import { useDisclosure } from '../../client/hooks/useDisclosure.js';
8
+ import { useLocaleInfo } from '../../client/hooks/useLocaleInfo.js';
9
+ import { useRemainingTime } from '../../client/hooks/useRemainingTime.js';
10
+ import { toaster } from '../../client/utils/toaster.js';
11
+ import { CalendarIcon } from '../../icons/CalendarIcon.js';
12
+ import { CheckIcon } from '../../icons/CheckIcon.js';
13
+ import { InfoCircleIcon } from '../../icons/InfoCircleIcon.js';
14
+ import { XIcon } from '../../icons/XIcon.js';
15
+ import { Button } from '../../ui/Button/index.js';
16
+ import { Dialog } from '../../ui/Dialog/index.js';
17
+ import { Portal } from '../../ui/Portal/index.js';
18
+ import { dataAttr } from '../../utils/dataAttr.js';
19
+ import { formatNumber } from '../../utils/formatNumber.js';
20
+ import { getQueryClient } from '../../utils/getQueryClient.js';
21
+ import { parseDecimal } from '../../utils/parseDecimal.js';
22
+ import { getAvailableQuestsQueryKey } from '../../utils/queryKeys.js';
23
+ import { useQuestContext } from './QuestsContext.js';
24
+ import { RemainingTime } from './RemainingTime.js';
25
+ export function DailyCheckInQuest() {
26
+ const quest = useQuestContext();
27
+ const disclosure = useDisclosure();
28
+ const localeInfo = useLocaleInfo();
29
+ const { name, description, status, lastCheckInDate, progressPercentage, checkInStreak, thirdDayBonusAmount, sixthDayBonusAmount, seventhDayBonusAmount, endDateTime, id, } = quest;
30
+ const today = startOfDay(new Date());
31
+ const hasCheckedInToday = lastCheckInDate
32
+ ? isEqual(today, startOfDay(new Date(lastCheckInDate)))
33
+ : false;
34
+ const checkedInRewards = {
35
+ 3: parseDecimal(thirdDayBonusAmount, 0),
36
+ 6: parseDecimal(sixthDayBonusAmount, 0),
37
+ 7: parseDecimal(seventhDayBonusAmount, 0),
38
+ };
39
+ const dailyCheckedInData = Array.from({ length: 7 }, (_, i) => {
40
+ const day = i + 1;
41
+ return {
42
+ day,
43
+ reward: checkedInRewards[day] ? checkedInRewards[day] : null,
44
+ };
45
+ });
46
+ const checkInDailyQuestMutation = useCheckInDailyCheckInQuestMutation({
47
+ onSuccess() {
48
+ toaster.success({
49
+ title: 'Check-in successful 🎉',
50
+ description: 'You’ve successfully checked in for today! Keep it up to maintain your streak!',
51
+ });
52
+ },
53
+ onError(e) {
54
+ toaster.error({
55
+ title: 'Check-in successful 🎉',
56
+ description: e.message ?? 'Check-In Failed',
57
+ });
58
+ },
59
+ });
60
+ const remainingTime = useRemainingTime(endDateTime);
61
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "border-border-primary bg-bg-tertiary flex h-full w-full flex-col space-y-6 rounded-2xl border px-4 pb-6 pt-5", children: [_jsxs("div", { className: "flex flex-col space-y-2", children: [_jsxs("div", { className: "flex max-h-7.5 items-start justify-between", children: [_jsx(RemainingTime, {}), _jsx("button", { type: "button", className: "bg-bg-primary border-border-secondary flex size-9 cursor-pointer items-center justify-center rounded-md border", onClick: () => disclosure.setOpen(true), "aria-label": "View Rules", children: _jsx(InfoCircleIcon, { className: "text-text-disabled size-5" }) })] }), _jsx("p", { className: "text-xl font-semibold", children: name }), _jsx("p", { className: "text-text-secondary-700 text-wrap text-sm font-normal", dangerouslySetInnerHTML: { __html: description } })] }), _jsxs("div", { className: "flex w-full flex-col space-y-2", children: [_jsx("div", { className: "grid grid-cols-7", children: dailyCheckedInData.map((item) => {
62
+ const reward = item.reward
63
+ ? `+${formatNumber(item.reward, {
64
+ currency: localeInfo.currency.code,
65
+ minDecimalPlaces: 0,
66
+ maxDecimalPlaces: 0,
67
+ })}`
68
+ : '-';
69
+ return (_jsx("div", { className: twMerge('flex items-center justify-center', item.day <= (checkInStreak || 0)
70
+ ? 'text-brand-400'
71
+ : 'text-text-secondary-700'), children: _jsx("p", { children: reward }) }, item.day));
72
+ }) }), _jsx("div", { className: "bg-bg-primary h-2 w-full overflow-hidden rounded-full", children: _jsx("div", { className: "bg-utility-brand-600 h-full rounded-full transition-all duration-300", style: { width: `${progressPercentage}%` } }) }), _jsx("div", { className: "grid grid-cols-7 place-items-center", children: dailyCheckedInData.map((item) => (_jsxs("div", { className: "text-text-secondary-700 ui-active:text-brand-400 group relative size-fit", "data-active": dataAttr(item.day <= (checkInStreak ?? 0)), children: [_jsx(CalendarIcon, { className: "h-4.5 w-auto" }), _jsx("p", { className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-2xs leading-none", children: item.day })] }, item.day))) })] }), _jsx(Button, { onClick: () => checkInDailyQuestMutation.mutate(id), className: "disabled:bg-bg-primary disabled:text-text-disabled", disabled: status === 'COMPLETED' ||
73
+ status === 'FAILED' ||
74
+ remainingTime.expired ||
75
+ hasCheckedInToday ||
76
+ checkInDailyQuestMutation.isPending, children: hasCheckedInToday && status === 'IN_PROGRESS' ? (_jsx(RemainingTimeTilTomorrow, {})) : (_jsx("span", { children: "Check-In" })) })] }), _jsx(Rules, { open: disclosure.open, onOpenChange: disclosure.setOpen })] }));
77
+ }
78
+ function RemainingTimeTilTomorrow() {
79
+ const queryClient = getQueryClient();
80
+ const remainingTime = useRemainingTime(startOfDay(addDays(new Date(), 1)), {
81
+ onExpired() {
82
+ queryClient.invalidateQueries({
83
+ queryKey: getAvailableQuestsQueryKey(),
84
+ });
85
+ },
86
+ });
87
+ return (_jsxs("div", { className: "flex items-center space-x-0.5", children: [_jsx(CheckIcon, { className: "size-5" }), _jsx("p", { children: remainingTime.toString() })] }));
88
+ }
89
+ function Rules(props) {
90
+ const [open, setOpen] = useControllableState({
91
+ value: props.open,
92
+ defaultValue: props.defaultOpen ?? false,
93
+ onChange: props.onOpenChange,
94
+ });
95
+ return (_jsx(Dialog.Root, { lazyMount: true, unmountOnExit: true, open: open, onOpenChange: (details) => setOpen(details.open), closeOnEscape: false, closeOnInteractOutside: false, 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", children: _jsxs(Dialog.Content, { className: "min-h-auto mx-auto max-w-[343px] rounded-xl px-4 pb-0 pt-5", children: [_jsx(Dialog.CloseTrigger, { children: _jsx(XIcon, {}) }), _jsxs("div", { className: "flex flex-col space-y-5", children: [_jsx("h2", { className: "text-text-primary-900 text-lg font-semibold", children: "Daily Check-In Rules" }), _jsxs("ol", { className: "text-text-tertiary-600 group list-disc text-wrap pl-4 text-sm font-normal", children: [_jsx("li", { children: "Earn rewards just for checking in each day!" }), _jsx("li", { children: "Stay consistent to unlock special rewards on the 3rd, 5th, and 7th day." }), _jsx("li", { children: "Missing a day will reset your streak, starting again from Day 1." }), _jsx("li", { children: "Each check-in unlocks the next one on the following day." }), _jsx("li", { children: "Successfully check in for 7 consecutive days to complete the quest and reset the cycle." })] })] }), _jsx("div", { className: "pb-4 pt-6", children: _jsx(Button, { onClick: () => setOpen(false), children: "Ok" }) })] }) })] }) }));
96
+ }
@@ -0,0 +1 @@
1
+ export declare function OnboardingQuest(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { noop } from 'lodash-es';
3
+ import { useShallow } from 'zustand/shallow';
4
+ import { useControllableState } from '../../client/hooks/useControllableState.js';
5
+ import { useDisclosure } from '../../client/hooks/useDisclosure.js';
6
+ import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
7
+ import { useRemainingTime } from '../../client/hooks/useRemainingTime.js';
8
+ import { CheckIcon } from '../../icons/CheckIcon.js';
9
+ import { CoinsHandIcon } from '../../icons/CoinsHandIcon.js';
10
+ import { FileCheck02Icon } from '../../icons/FileCheck02Icon.js';
11
+ import { InfoCircleIcon } from '../../icons/InfoCircleIcon.js';
12
+ import { XIcon } from '../../icons/XIcon.js';
13
+ import { Button } from '../../ui/Button/index.js';
14
+ import { Checkbox } from '../../ui/Checkbox/index.js';
15
+ import { Dialog } from '../../ui/Dialog/index.js';
16
+ import { Portal } from '../../ui/Portal/index.js';
17
+ import { useQuestContext } from './QuestsContext.js';
18
+ import { RemainingTime } from './RemainingTime.js';
19
+ export function OnboardingQuest() {
20
+ const quest = useQuestContext();
21
+ const disclosure = useDisclosure();
22
+ const globalStore = useGlobalStore(useShallow((ctx) => ({
23
+ kyc: ctx.kyc,
24
+ depositWithdrawal: ctx.depositWithdrawal,
25
+ })));
26
+ const remainingTime = useRemainingTime(quest.endDateTime);
27
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "border-border-primary bg-bg-tertiary flex h-full w-full flex-col space-y-6 rounded-2xl border px-4 pb-6 pt-5", children: [_jsxs("div", { className: "flex flex-col space-y-2", children: [_jsxs("div", { className: "flex max-h-7.5 items-start justify-between", children: [_jsx(RemainingTime, {}), _jsx("button", { type: "button", className: "bg-bg-primary border-border-secondary flex size-9 cursor-pointer items-center justify-center rounded-md border", onClick: () => disclosure.setOpen(true), "aria-label": "View Rules", children: _jsx(InfoCircleIcon, { className: "text-text-disabled size-5" }) })] }), _jsx("h2", { className: "text-xl font-semibold", children: quest.name }), _jsx("p", { className: "text-text-secondary-700 text-wrap text-sm font-normal", dangerouslySetInnerHTML: { __html: quest.description } })] }), _jsxs("div", { className: "w-full grow space-y-2", children: [_jsxs(Checkbox.Root, { checked: quest.accountVerificationCompleted, onCheckedChange: noop, children: [_jsx(Checkbox.Control, { className: "bg-transparent", children: _jsx(Checkbox.Indicator, { asChild: true, children: _jsx(CheckIcon, {}) }) }), _jsx(Checkbox.Label, { className: "text-sm font-medium leading-tight", children: "Complete personal verification" }), _jsx(Checkbox.HiddenInput, {})] }), _jsxs(Checkbox.Root, { checked: quest.firstDepositCompleted, onCheckedChange: noop, children: [_jsx(Checkbox.Control, { className: "bg-transparent", children: _jsx(Checkbox.Indicator, { asChild: true, children: _jsx(CheckIcon, {}) }) }), _jsx(Checkbox.Label, { className: "text-sm font-medium leading-tight", children: "Make your first deposit" }), _jsx(Checkbox.HiddenInput, {})] })] }), _jsxs("div", { className: "flex gap-lg", children: [_jsxs(Button, { size: "sm", onClick: () => {
28
+ globalStore.kyc.setOpen(true);
29
+ }, disabled: quest.accountVerificationCompleted ||
30
+ quest.status === 'COMPLETED' ||
31
+ quest.status === 'FAILED' ||
32
+ remainingTime.expired, className: "disabled:bg-bg-primary disabled:text-text-disabled", children: [quest.accountVerificationCompleted ? (_jsx(CheckIcon, { className: "size-5" })) : (_jsx(FileCheck02Icon, { className: "size-5" })), "Verify"] }), _jsxs(Button, { size: "sm", onClick: () => {
33
+ globalStore.depositWithdrawal.setTab('DEPOSIT');
34
+ globalStore.depositWithdrawal.setOpen(true);
35
+ }, disabled: quest.firstDepositCompleted ||
36
+ quest.status === 'COMPLETED' ||
37
+ quest.status === 'FAILED' ||
38
+ remainingTime.expired, className: "disabled:bg-bg-primary disabled:text-text-disabled", children: [_jsx(CoinsHandIcon, { className: "size-5" }), "Deposit"] })] })] }), _jsx(Rules, { open: disclosure.open, onOpenChange: disclosure.setOpen })] }));
39
+ }
40
+ function Rules(props) {
41
+ const [open, setOpen] = useControllableState({
42
+ value: props.open,
43
+ defaultValue: props.defaultOpen ?? false,
44
+ onChange: props.onOpenChange,
45
+ });
46
+ return (_jsx(Dialog.Root, { lazyMount: true, unmountOnExit: true, open: open, onOpenChange: (details) => setOpen(details.open), closeOnEscape: false, closeOnInteractOutside: false, 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", children: _jsxs(Dialog.Content, { className: "min-h-auto mx-auto max-w-[343px] rounded-xl px-4 pb-0 pt-5", children: [_jsx(Dialog.CloseTrigger, { children: _jsx(XIcon, {}) }), _jsxs("div", { className: "flex flex-col space-y-5", children: [_jsx("h2", { className: "text-text-primary-900 text-lg font-semibold", children: "Welcome Quest Rules" }), _jsxs("ol", { className: "text-text-tertiary-600 group list-disc text-wrap pl-4 text-sm font-normal", children: [_jsx("li", { children: "Begin your quest whenever you're ready" }), _jsx("li", { children: "Tackle the tasks in any order you like" }), _jsx("li", { children: "Finish all tasks and we'll credit your reward automatically!" })] })] }), _jsx("div", { className: "pb-4 pt-6", children: _jsx(Button, { onClick: () => setOpen(false), children: "Ok" }) })] }) })] }) }));
47
+ }
@@ -1,6 +1,8 @@
1
+ import { type CSSProperties } from 'react';
1
2
  export interface QuestsProps {
2
3
  /** @default "Quests" */
3
4
  heading?: string;
4
5
  className?: string;
6
+ style?: CSSProperties;
5
7
  }
6
8
  export declare function Quests__client(props: QuestsProps): import("react/jsx-runtime").JSX.Element;
@@ -7,10 +7,10 @@ import { useAvailableQuestsQuery } from '../../client/hooks/useAvailableQuestsQu
7
7
  import { SpinnerIcon } from '../../icons/SpinnerIcon.js';
8
8
  import closeChest from '../../images/close-chest.png';
9
9
  import { SegmentGroup } from '../../ui/SegmentGroup/index.js';
10
- import { DailyCheckInQuest } from './DailyCheckInQuest/DailyCheckInQuest.js';
11
- import { OnboardingQuest } from './OnboardingQuest/OnboardingQuest.js';
10
+ import { DailyCheckInQuest } from './DailyCheckInQuest.js';
11
+ import { OnboardingQuest } from './OnboardingQuest.js';
12
12
  import { QuestContext } from './QuestsContext.js';
13
- import { WageringQuest } from './WageringQuest/WageringQuest.js';
13
+ import { WageringQuest } from './WageringQuest.js';
14
14
  const QUEST_COMPONENT_MAP = {
15
15
  DAILY_CHECKIN: DailyCheckInQuest,
16
16
  ONBOARDING: OnboardingQuest,
@@ -28,12 +28,12 @@ export function Quests__client(props) {
28
28
  : quest.status === 'IN_PROGRESS';
29
29
  });
30
30
  if (questsQuery.isLoading) {
31
- return (_jsxs("div", { className: props.className, children: [_jsx("h2", { className: "mb-3 text-lg font-semibold", children: props.heading ?? 'Quests' }), _jsx(SpinnerIcon, { className: "mx-auto size-5 h-[50vh]" })] }));
31
+ return (_jsxs("div", { style: props.style, className: props.className, children: [_jsx("h2", { className: "mb-3 text-lg font-semibold", children: props.heading ?? 'Quests' }), _jsx(SpinnerIcon, { className: "mx-auto size-5 h-[50vh]" })] }));
32
32
  }
33
33
  if (!questsQuery.data?.length) {
34
- return (_jsxs("div", { className: props.className, children: [_jsx("h2", { className: "mb-3 text-lg font-semibold", children: props.heading ?? 'Quests' }), _jsxs("div", { className: "mt-11 flex flex-col items-center justify-center", children: [_jsx(Image, { src: closeChest, width: 100, height: 100, alt: "No Quests Available", className: "mix-blend-luminosity", quality: 100 }), _jsx("div", { className: "text-text-primary-900 mt-4 text-base font-semibold", children: props.heading ?? 'Quests' }), _jsx("div", { className: "text-text-tertiary-600 mt-1 text-center text-sm", children: "No available quests. Please check back later!" })] })] }));
34
+ return (_jsxs("div", { style: props.style, className: props.className, children: [_jsx("h2", { className: "mb-3 text-lg font-semibold", children: props.heading ?? 'Quests' }), _jsxs("div", { className: "mt-11 flex flex-col items-center justify-center", children: [_jsx(Image, { src: closeChest, width: 100, height: 100, alt: "No Quests Available", className: "mix-blend-luminosity", quality: 100 }), _jsx("div", { className: "text-text-primary-900 mt-4 text-base font-semibold", children: props.heading ?? 'Quests' }), _jsx("div", { className: "text-text-tertiary-600 mt-1 text-center text-sm", children: "No available quests. Please check back later!" })] })] }));
35
35
  }
36
- return (_jsxs("div", { className: props.className, children: [_jsx("h2", { className: "text-lg font-semibold", children: props.heading ?? 'Quests' }), _jsxs(SegmentGroup.Root, { value: tab, onValueChange: (details) => {
36
+ return (_jsxs("div", { style: props.style, className: props.className, children: [_jsx("h2", { className: "text-lg font-semibold", children: props.heading ?? 'Quests' }), _jsxs(SegmentGroup.Root, { value: tab, onValueChange: (details) => {
37
37
  setTab(TabDefinition.catch('AVAILABLE').parse(details.value));
38
38
  }, className: "mt-3 w-full overflow-x-auto lg:mt-4.5 lg:w-fit", children: [_jsxs(SegmentGroup.Item, { value: "AVAILABLE", className: "w-full lg:w-fit", children: [_jsx(SegmentGroup.ItemText, { children: "Available" }), _jsx(SegmentGroup.ItemControl, {}), _jsx(SegmentGroup.ItemHiddenInput, {})] }), _jsxs(SegmentGroup.Item, { value: "COMPLETED", className: "w-full lg:w-fit", children: [_jsx(SegmentGroup.ItemText, { children: "Completed" }), _jsx(SegmentGroup.ItemControl, {}), _jsx(SegmentGroup.ItemHiddenInput, {})] }), _jsx(SegmentGroup.Indicator, {})] }), _jsx("div", { className: "mt-3xl grid gap-3xl lg:mt-lg lg:grid-cols-3", children: quests.map((quest) => {
39
39
  const QuestComponent = QUEST_COMPONENT_MAP[quest.type];
@@ -0,0 +1 @@
1
+ export declare function RemainingTime(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRemainingTime } from '../../client/hooks/useRemainingTime.js';
3
+ import { ClockStopWatchIcon } from '../../icons/ClockStopWatchIcon.js';
4
+ import { Badge } from '../../ui/Badge/index.js';
5
+ import { useQuestContext } from './QuestsContext.js';
6
+ export function RemainingTime() {
7
+ const quest = useQuestContext();
8
+ const remainingTime = useRemainingTime(quest.endDateTime);
9
+ if (quest.status === 'COMPLETED') {
10
+ return (_jsx(Badge.Root, { size: "lg", colorScheme: "success", round: false, children: _jsx(Badge.Label, { children: "Completed" }) }));
11
+ }
12
+ if (quest.status === 'FAILED') {
13
+ return (_jsx(Badge.Root, { size: "lg", colorScheme: "danger", round: false, children: _jsx(Badge.Label, { children: "Failed" }) }));
14
+ }
15
+ if (remainingTime.expired) {
16
+ return (_jsx(Badge.Root, { size: "lg", colorScheme: "danger", round: false, children: _jsx(Badge.Label, { children: "Expired" }) }));
17
+ }
18
+ return (_jsxs(Badge.Root, { size: "lg", colorScheme: "brand", round: false, children: [_jsx(Badge.Icon, { children: _jsx(ClockStopWatchIcon, {}) }), _jsx(Badge.Label, { children: remainingTime.toString() })] }));
19
+ }
@@ -0,0 +1 @@
1
+ export declare function WageringQuest(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { twMerge } from 'tailwind-merge';
4
+ import { useControllableState } from '../../client/hooks/useControllableState.js';
5
+ import { useDisclosure } from '../../client/hooks/useDisclosure.js';
6
+ import { useLocaleInfo } from '../../client/hooks/useLocaleInfo.js';
7
+ import { useRemainingTime } from '../../client/hooks/useRemainingTime.js';
8
+ import { InfoCircleIcon } from '../../icons/InfoCircleIcon.js';
9
+ import { XIcon } from '../../icons/XIcon.js';
10
+ import { Button } from '../../ui/Button/index.js';
11
+ import { Dialog } from '../../ui/Dialog/index.js';
12
+ import { Portal } from '../../ui/Portal/index.js';
13
+ import { parseDecimal } from '../../utils/parseDecimal.js';
14
+ import { useQuestContext } from './QuestsContext.js';
15
+ import { RemainingTime } from './RemainingTime.js';
16
+ export function WageringQuest() {
17
+ const quest = useQuestContext();
18
+ const disclosure = useDisclosure();
19
+ const localeInfo = useLocaleInfo();
20
+ const { progressPercentage, turnover, targetTurnover, name, description, endDateTime, bonus, status, } = quest;
21
+ const remainingTime = useRemainingTime(endDateTime);
22
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "border-border-primary bg-bg-tertiary flex h-full w-full flex-col space-y-6 rounded-2xl border px-4 pb-6 pt-5", children: [_jsxs("div", { className: "flex flex-col space-y-2", children: [_jsxs("div", { className: "flex max-h-7.5 items-start justify-between", children: [_jsx(RemainingTime, {}), _jsx("button", { type: "button", className: "bg-bg-primary border-border-secondary flex size-9 cursor-pointer items-center justify-center rounded-md border", onClick: () => disclosure.setOpen(true), "aria-label": "View Rules", children: _jsx(InfoCircleIcon, { className: "text-text-disabled size-5" }) })] }), _jsx("p", { className: "text-xl font-semibold", children: name }), _jsx("p", { className: "text-text-secondary-700 text-wrap text-sm font-normal", dangerouslySetInnerHTML: { __html: description } })] }), _jsxs("div", { className: "flex w-full flex-col space-y-2", children: [_jsxs("div", { className: "text-text-primary-brand group flex items-center justify-between", children: [_jsxs("p", { children: ["Progress ", parseDecimal(progressPercentage, 0).toFixed(2), "%"] }), _jsxs("p", { children: [turnover, _jsxs("span", { className: "text-text-quarterary-brand", children: ["/", targetTurnover, " ", localeInfo.currency.code] })] })] }), _jsx("div", { className: "bg-bg-primary h-2 w-full overflow-hidden rounded-full", children: _jsx("div", { className: "bg-utility-brand-600 h-full rounded-full transition-all duration-300", style: { width: `${progressPercentage}%` } }) })] }), _jsx("div", { className: "flex h-full items-end", children: _jsx("div", { className: "bg-button-secondary-bg border-bg-primary-hover w-full rounded-md border px-3.5 py-2.5 text-center text-white", children: _jsxs("p", { className: twMerge(remainingTime.expired ||
23
+ status === 'COMPLETED' ||
24
+ status === 'FAILED'
25
+ ? 'opacity-50 grayscale'
26
+ : ''), children: ["Complete to get ", _jsxs("span", { className: "text-brand-400", children: ["\u20B1", bonus] }), ' ', "bonus!"] }) }) })] }), _jsx(Rules, { open: disclosure.open, onOpenChange: disclosure.setOpen })] }));
27
+ }
28
+ function Rules(props) {
29
+ const quest = useQuestContext();
30
+ const [open, setOpen] = useControllableState({
31
+ value: props.open,
32
+ defaultValue: props.defaultOpen ?? false,
33
+ onChange: props.onOpenChange,
34
+ });
35
+ return (_jsx(Dialog.Root, { lazyMount: true, unmountOnExit: true, open: open, onOpenChange: (details) => setOpen(details.open), closeOnEscape: false, closeOnInteractOutside: false, 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", children: _jsxs(Dialog.Content, { className: "min-h-auto mx-auto max-w-[343px] rounded-xl px-4 pb-0 pt-5", children: [_jsx(Dialog.CloseTrigger, { children: _jsx(XIcon, {}) }), _jsxs("div", { className: "flex flex-col space-y-5", children: [_jsx("h2", { className: "text-text-primary-900 text-lg font-semibold", children: quest.name }), _jsxs("ol", { className: "text-text-tertiary-600 group list-disc text-wrap pl-4 text-sm font-normal", children: [_jsx("li", { children: "Earn extra rewards just by playing Slots, every bet counts, win or lose!" }), _jsx("li", { children: "Reach the wagering requirement in bets to complete the quest and claim your reward." })] })] }), _jsx("div", { className: "pb-4 pt-6", children: _jsx(Button, { onClick: () => setOpen(false), children: "Ok" }) })] }) })] }) }));
36
+ }
@@ -0,0 +1,2 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ export declare function Tv03Icon(props: ComponentPropsWithRef<'svg'>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function Tv03Icon(props) {
3
+ return (_jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: _jsx("path", { d: "M17 3L12 7L7 3M6.8 21H17.2C18.8802 21 19.7202 21 20.362 20.673C20.9265 20.3854 21.3854 19.9265 21.673 19.362C22 18.7202 22 17.8802 22 16.2V11.8C22 10.1198 22 9.27976 21.673 8.63803C21.3854 8.07354 20.9265 7.6146 20.362 7.32698C19.7202 7 18.8802 7 17.2 7H6.8C5.11984 7 4.27976 7 3.63803 7.32698C3.07354 7.6146 2.6146 8.07354 2.32698 8.63803C2 9.27976 2 10.1198 2 11.8V16.2C2 17.8802 2 18.7202 2.32698 19.362C2.6146 19.9265 3.07354 20.3854 3.63803 20.673C4.27976 21 5.11984 21 6.8 21Z" }) }));
4
+ }
@@ -0,0 +1 @@
1
+ export declare function dataURLtoBlob(dataURL: string): Blob;
@@ -0,0 +1,11 @@
1
+ export function dataURLtoBlob(dataURL) {
2
+ const parts = dataURL.split(';base64,');
3
+ const contentType = parts[0].split(':')[1];
4
+ const raw = window.atob(parts[1]);
5
+ const rawLength = raw.length;
6
+ const uInt8Array = new Uint8Array(rawLength);
7
+ for (let i = 0; i < rawLength; ++i) {
8
+ uInt8Array[i] = raw.charCodeAt(i);
9
+ }
10
+ return new Blob([uInt8Array], { type: contentType });
11
+ }
@@ -0,0 +1 @@
1
+ export declare const resizeFile: (file: File) => Promise<unknown>;
@@ -0,0 +1,6 @@
1
+ import Resizer from 'react-image-file-resizer';
2
+ export const resizeFile = (file) => new Promise((resolve) => {
3
+ Resizer.imageFileResizer(file, 300, 300, 'jpeg', 100, 0, (uri) => {
4
+ resolve(uri);
5
+ }, 'file');
6
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opexa/portal-components",
3
- "version": "0.0.108",
3
+ "version": "0.0.110",
4
4
  "exports": {
5
5
  "./ui/*": {
6
6
  "types": "./dist/ui/*/index.d.ts",
@@ -86,6 +86,7 @@
86
86
  "embla-carousel-react": "^8.6.0",
87
87
  "is-mobile": "^5.0.0",
88
88
  "lodash-es": "^4.17.21",
89
+ "react-image-file-resizer": "^0.4.8",
89
90
  "tailwind-merge": "^3.3.1",
90
91
  "tailwind-variants": "^1.0.0",
91
92
  "tailwind-zag": "^1.0.2",