finance-shared 0.0.20 → 0.0.21

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.
@@ -4,7 +4,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4
4
  import i18next from "i18next";
5
5
  import { default as default2 } from "i18next";
6
6
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
7
- import { Dialog as Dialog$1, DialogTitle, DialogContent as DialogContent$1, DialogContentText, DialogActions as DialogActions$1, Button as Button$1, Typography as Typography$1, IconButton as IconButton$1, Box as Box$1, Tooltip, Zoom, TextField as TextField$1, Paper as Paper$1, Grid as Grid$1, styled as styled$3, CircularProgress as CircularProgress$1, Autocomplete as Autocomplete$1, Badge, Checkbox as Checkbox$1, Chip as Chip$1, Divider, RadioGroup, FormControlLabel, Radio, Grid2, Popover as Popover$1 } from "@mui/material";
7
+ import { Dialog as Dialog$1, DialogTitle, DialogContent as DialogContent$1, DialogContentText, DialogActions as DialogActions$1, Button as Button$1, Typography as Typography$1, IconButton as IconButton$1, Box as Box$1, Tooltip, Zoom, TextField as TextField$1, Paper as Paper$1, Grid as Grid$1, styled as styled$3, CircularProgress as CircularProgress$1, Autocomplete as Autocomplete$1, Badge, Checkbox as Checkbox$1, Chip as Chip$1, Divider, RadioGroup, FormControlLabel, Radio, Grid2, InputAdornment as InputAdornment$1, Popover as Popover$1 } from "@mui/material";
8
8
  import { useTranslation } from "react-i18next";
9
9
  import * as React from "react";
10
10
  import React__default, { useState, isValidElement, cloneElement, Children, useEffect, memo, createElement, useRef, useCallback, useMemo, createContext, useContext } from "react";
@@ -13,6 +13,7 @@ import emStyled from "@emotion/styled";
13
13
  import { ThemeContext } from "@emotion/react";
14
14
  import * as ReactDOM from "react-dom";
15
15
  import ReactDOM__default from "react-dom";
16
+ import { VisibilityOff, Visibility } from "@mui/icons-material";
16
17
  const confirmDialog$1 = { "cancel": "Cancel", "ok": "Ok", "confirm": "Confirm", "delete": "Delete", "save": "Save", "yes": "Yes", "no": "No" };
17
18
  const en$1 = {
18
19
  confirmDialog: confirmDialog$1
@@ -91,7 +92,16 @@ const locale$3 = {
91
92
  DURATION: "Duration",
92
93
  STAMP_DUTY: "Stamp Duty",
93
94
  LEGAL_FEES: "Legal Fees",
94
- MONTHS: "Months"
95
+ MONTHS: "Months",
96
+ ENTER_SIGNING_OTP: "Enter OTP",
97
+ ENTER_SIGNING_OTP_DESC: "Enter the OTP sent to your registered mobile number to confirm signing.",
98
+ ENTER_SIGNING_PIN: "Enter PIN",
99
+ ENTER_SIGNING_PIN_DESC: "Enter your PIN to confirm signing.",
100
+ OTP: "OTP",
101
+ PIN: "PIN",
102
+ RESEND_OTP: "Resend OTP",
103
+ RESEND_OTP_IN: "Resend OTP in {{seconds}}s",
104
+ VERIFY_AND_SIGN: "Verify & Sign"
95
105
  };
96
106
  const locale$2 = {
97
107
  CHANGE_DETECT_TITLE: "Adakah anda pasti mahu membuang perubahan?",
@@ -160,7 +170,16 @@ const locale$2 = {
160
170
  DURATION: "Tempoh",
161
171
  STAMP_DUTY: "Duti Setem",
162
172
  LEGAL_FEES: "Yuran Guaman",
163
- MONTHS: "Bulan"
173
+ MONTHS: "Bulan",
174
+ ENTER_SIGNING_OTP: "Masukkan OTP",
175
+ ENTER_SIGNING_OTP_DESC: "Masukkan OTP yang dihantar ke nombor telefon berdaftar anda untuk mengesahkan tandatangan.",
176
+ ENTER_SIGNING_PIN: "Masukkan PIN",
177
+ ENTER_SIGNING_PIN_DESC: "Masukkan PIN anda untuk mengesahkan tandatangan.",
178
+ OTP: "OTP",
179
+ PIN: "PIN",
180
+ RESEND_OTP: "Hantar Semula OTP",
181
+ RESEND_OTP_IN: "Hantar semula OTP dalam {{seconds}}s",
182
+ VERIFY_AND_SIGN: "Sahkan & Tandatangan"
164
183
  };
165
184
  i18next.addResourceBundle("en", "Dialog", locale$3);
166
185
  i18next.addResourceBundle("ms", "Dialog", locale$2);
@@ -5856,11 +5875,14 @@ function CustomDialog({
5856
5875
  content,
5857
5876
  size,
5858
5877
  maxWidth: maxWidth2,
5859
- hideCloseIcon = false
5878
+ hideCloseIcon = false,
5879
+ disableBackdropClose = false,
5880
+ disableEscapeKeyDown = false,
5881
+ closeIconDisabled = false
5860
5882
  }) {
5861
5883
  const dialogSize = maxWidth2 !== void 0 ? maxWidth2 : size || "md";
5862
5884
  const handleClose = (_event, reason) => {
5863
- if (hideCloseIcon && reason && reason === "backdropClick") return;
5885
+ if (reason === "backdropClick" && (hideCloseIcon || disableBackdropClose)) return;
5864
5886
  onClose();
5865
5887
  };
5866
5888
  return /* @__PURE__ */ jsxs(
@@ -5870,6 +5892,7 @@ function CustomDialog({
5870
5892
  onClose: handleClose,
5871
5893
  maxWidth: dialogSize,
5872
5894
  fullWidth: true,
5895
+ disableEscapeKeyDown,
5873
5896
  children: [
5874
5897
  /* @__PURE__ */ jsxs(DialogTitle, { children: [
5875
5898
  content.title,
@@ -5879,6 +5902,7 @@ function CustomDialog({
5879
5902
  {
5880
5903
  "aria-label": "close",
5881
5904
  onClick: onClose,
5905
+ disabled: closeIconDisabled,
5882
5906
  sx: {
5883
5907
  position: "absolute",
5884
5908
  right: 8,
@@ -10396,13 +10420,30 @@ const DigitalProcessSignature = (request_data) => async (dispatch) => {
10396
10420
  }
10397
10421
  dispatch(setDocumentSignJPDF(""));
10398
10422
  dispatch(setSignedDocumentJPDF(response.result));
10423
+ return true;
10399
10424
  }
10425
+ return false;
10400
10426
  } catch (error) {
10401
10427
  dispatch(showMessage({ message: error.message, variant: "error" }));
10428
+ return false;
10402
10429
  } finally {
10403
10430
  dispatch(setIsProcessSignatureLoading(false));
10404
10431
  }
10405
10432
  };
10433
+ const RequestSigningOtp = (financeApplicationId) => async (dispatch) => {
10434
+ try {
10435
+ dispatch(setIsRequestSigningOtpLoading(true));
10436
+ await api.post(`/v1/DigitalSignature/RequestSigningOtp`, {
10437
+ FinanceApplicationId: financeApplicationId
10438
+ });
10439
+ return true;
10440
+ } catch (error) {
10441
+ dispatch(showMessage({ message: error.message, variant: "error" }));
10442
+ return false;
10443
+ } finally {
10444
+ dispatch(setIsRequestSigningOtpLoading(false));
10445
+ }
10446
+ };
10406
10447
  const generateFromJPDF = (financeApplicationId) => async (dispatch) => {
10407
10448
  dispatch(setDocumentSignJPDFLoading(true));
10408
10449
  try {
@@ -10441,6 +10482,7 @@ const setIsChangeStatusDialogOpen = (value) => setLoadingState$1("isChangeStatus
10441
10482
  const setIsUserChangeStatusFormLoading = (value) => setLoadingState$1("isUserChangeStatusFormLoading", value);
10442
10483
  const setIsDocumentSignatureDialogOpen = (value) => setLoadingState$1("isDocumentSignatureDialogOpen", value);
10443
10484
  const setIsProcessSignatureLoading = (value) => setLoadingState$1("isProcessSignatureLoading", value);
10485
+ const setIsRequestSigningOtpLoading = (value) => setLoadingState$1("isRequestSigningOtpLoading", value);
10444
10486
  const setDocumentSignJPDFLoading = (value) => setLoadingState$1("isDocumentSignJPDFLoading", value);
10445
10487
  const setIsPdfDialogOpen = (value) => setLoadingState$1("isPdfDialogOpen", value);
10446
10488
  const setIsPopoverOpen = (value) => setLoadingState$1("isPopoverOpen", value);
@@ -10456,6 +10498,7 @@ const getIsChangeStatusDialogOpen = getLoadingStateFactory("isChangeStatusDialog
10456
10498
  const getIsUserChangeStatusFormLoading = getLoadingStateFactory("isUserChangeStatusFormLoading");
10457
10499
  const getIsDocumentSignatureDialogOpen = getLoadingStateFactory("isDocumentSignatureDialogOpen");
10458
10500
  const getIsProcessSignatureLoading = getLoadingStateFactory("isProcessSignatureLoading");
10501
+ const getIsRequestSigningOtpLoading = getLoadingStateFactory("isRequestSigningOtpLoading");
10459
10502
  const getDocumentSignJPDFLoading = getLoadingStateFactory("isDocumentSignJPDFLoading");
10460
10503
  const getIsPdfDialogOpen = getLoadingStateFactory("isPdfDialogOpen");
10461
10504
  const getIsPopoverOpen = getLoadingStateFactory("isPopoverOpen");
@@ -10495,6 +10538,7 @@ const initialState$1 = {
10495
10538
  isUserChangeStatusFormLoading: false,
10496
10539
  isDocumentSignatureDialogOpen: false,
10497
10540
  isProcessSignatureLoading: false,
10541
+ isRequestSigningOtpLoading: false,
10498
10542
  isDocumentSignJPDFLoading: false,
10499
10543
  isPdfDialogOpen: false,
10500
10544
  isPopoverOpen: false
@@ -17730,6 +17774,10 @@ const loginUserRoles = {
17730
17774
  SuperAdmin: "super-admin"
17731
17775
  };
17732
17776
  const IMAGE_TYPES = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"];
17777
+ const AuthFactorMode = {
17778
+ OTP: "OTP",
17779
+ PIN: "PIN"
17780
+ };
17733
17781
  dayjs.extend(customParseFormat);
17734
17782
  function decodeUrlAndGetFileType(url) {
17735
17783
  try {
@@ -26043,6 +26091,122 @@ const PDFPreviewForTheSignature = ({
26043
26091
  ) }) })
26044
26092
  ] }) });
26045
26093
  };
26094
+ const OTP_LENGTH = 6;
26095
+ const MAX_PIN_LENGTH = 12;
26096
+ const RESEND_SECONDS = 30;
26097
+ const AuthFactorStep = ({
26098
+ open,
26099
+ mode,
26100
+ loading = false,
26101
+ error,
26102
+ onClose,
26103
+ onSubmit,
26104
+ onResendOtp,
26105
+ resendLoading = false,
26106
+ ColoredSubmitButton
26107
+ }) => {
26108
+ const { t: t2 } = useTranslation("Dialog");
26109
+ const [value, setValue] = useState("");
26110
+ const [secondsLeft, setSecondsLeft] = useState(RESEND_SECONDS);
26111
+ const [showValue, setShowValue] = useState(false);
26112
+ useEffect(() => {
26113
+ if (!open) return;
26114
+ setValue("");
26115
+ setSecondsLeft(RESEND_SECONDS);
26116
+ setShowValue(false);
26117
+ }, [open, mode]);
26118
+ useEffect(() => {
26119
+ if (mode !== AuthFactorMode.OTP || !open || secondsLeft <= 0) return;
26120
+ const timer = setTimeout(() => setSecondsLeft((s4) => s4 - 1), 1e3);
26121
+ return () => clearTimeout(timer);
26122
+ }, [mode, open, secondsLeft]);
26123
+ const handleChange = (newValue) => {
26124
+ const digitsOnly = newValue.replace(/\D/g, "");
26125
+ setValue(digitsOnly.slice(0, mode === AuthFactorMode.OTP ? OTP_LENGTH : MAX_PIN_LENGTH));
26126
+ };
26127
+ const handleResend = () => {
26128
+ setSecondsLeft(RESEND_SECONDS);
26129
+ onResendOtp == null ? void 0 : onResendOtp();
26130
+ };
26131
+ const isValueValid = mode === AuthFactorMode.OTP ? value.length === OTP_LENGTH : value.length > 0;
26132
+ return /* @__PURE__ */ jsx(
26133
+ CustomDialog,
26134
+ {
26135
+ open,
26136
+ onClose,
26137
+ maxWidth: "xs",
26138
+ disableBackdropClose: true,
26139
+ disableEscapeKeyDown: true,
26140
+ closeIconDisabled: loading,
26141
+ content: {
26142
+ title: mode === AuthFactorMode.OTP ? t2("ENTER_SIGNING_OTP") : t2("ENTER_SIGNING_PIN"),
26143
+ description: mode === AuthFactorMode.OTP ? t2("ENTER_SIGNING_OTP_DESC") : t2("ENTER_SIGNING_PIN_DESC"),
26144
+ component: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-16", children: [
26145
+ /* @__PURE__ */ jsx(
26146
+ CommonTextField,
26147
+ {
26148
+ autoFocus: true,
26149
+ label: mode === AuthFactorMode.OTP ? t2("OTP") : t2("PIN"),
26150
+ type: mode === AuthFactorMode.PIN && !showValue ? "password" : "text",
26151
+ inputMode: "numeric",
26152
+ value,
26153
+ onChange: handleChange,
26154
+ error: !!error,
26155
+ helperText: error,
26156
+ isTextUpperCase: false,
26157
+ fullWidth: true,
26158
+ InputProps: mode === AuthFactorMode.PIN ? {
26159
+ endAdornment: /* @__PURE__ */ jsx(InputAdornment$1, { position: "end", children: /* @__PURE__ */ jsx(
26160
+ IconButton$1,
26161
+ {
26162
+ onClick: () => setShowValue((s4) => !s4),
26163
+ edge: "end",
26164
+ tabIndex: -1,
26165
+ children: showValue ? /* @__PURE__ */ jsx(VisibilityOff, { fontSize: "small" }) : /* @__PURE__ */ jsx(Visibility, { fontSize: "small" })
26166
+ }
26167
+ ) })
26168
+ } : void 0,
26169
+ "data-test-id": "auth-factor-input"
26170
+ }
26171
+ ),
26172
+ mode === AuthFactorMode.OTP && /* @__PURE__ */ jsx(Box$1, { className: "flex justify-end", children: /* @__PURE__ */ jsx(
26173
+ ColoredSubmitButton,
26174
+ {
26175
+ variant: "text",
26176
+ onClick: handleResend,
26177
+ disabled: secondsLeft > 0 || resendLoading,
26178
+ isLoading: resendLoading,
26179
+ text: secondsLeft > 0 ? t2("RESEND_OTP_IN", { seconds: secondsLeft }) : t2("RESEND_OTP")
26180
+ }
26181
+ ) }),
26182
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-10", children: [
26183
+ /* @__PURE__ */ jsx(
26184
+ ColoredSubmitButton,
26185
+ {
26186
+ variant: "outlined",
26187
+ onClick: onClose,
26188
+ disabled: loading,
26189
+ className: "rounded-6",
26190
+ text: t2("CANCEL")
26191
+ }
26192
+ ),
26193
+ /* @__PURE__ */ jsx(
26194
+ ColoredSubmitButton,
26195
+ {
26196
+ variant: "contained",
26197
+ onClick: () => onSubmit(value),
26198
+ disabled: !isValueValid || loading,
26199
+ isLoading: loading,
26200
+ className: "rounded-6",
26201
+ text: t2("VERIFY_AND_SIGN")
26202
+ }
26203
+ )
26204
+ ] })
26205
+ ] })
26206
+ }
26207
+ }
26208
+ );
26209
+ };
26046
26210
  i18next.addResourceBundle("en", "Dialog", locale$3);
26047
26211
  i18next.addResourceBundle("ms", "Dialog", locale$2);
26048
26212
  function DocumentSigningDialog({
@@ -26052,30 +26216,66 @@ function DocumentSigningDialog({
26052
26216
  ColoredSubmitButton,
26053
26217
  financeCalculation,
26054
26218
  showConfirmCheckbox = false,
26055
- showTitle = true
26219
+ showTitle = true,
26220
+ requireAuthFactor = false,
26221
+ isOfficer
26056
26222
  }) {
26057
26223
  const { t: t2 } = useTranslation("Dialog");
26058
26224
  const open = useSelector(getIsDocumentSignatureDialogOpen);
26059
26225
  const documentSignJPDF = useSelector(getDocumentSignJPDF);
26060
26226
  const signedDocumentJPDF = useSelector(getSignedDocumentJPDF);
26061
26227
  const isProcessSignatureLoading = useSelector(getIsProcessSignatureLoading);
26228
+ const isRequestSigningOtpLoading = useSelector(getIsRequestSigningOtpLoading);
26062
26229
  const activeFinanceDetail = useSelector(getActiveFinanceDetail);
26063
26230
  const dispatch = useDispatch();
26231
+ const [authFactorOpen, setAuthFactorOpen] = useState(false);
26232
+ const getSignatureRole = () => {
26233
+ if (loginUserDetail.data.slug === loginUserRoles.Admin) {
26234
+ return digitalSignatureRole.Admin;
26235
+ }
26236
+ if (loginUserDetail.data.slug === loginUserRoles.Lawyer) {
26237
+ return digitalSignatureRole.Lawyer;
26238
+ }
26239
+ return digitalSignatureRole.User;
26240
+ };
26064
26241
  const onClose = () => {
26242
+ setAuthFactorOpen(false);
26065
26243
  dispatch(setIsDocumentSignatureDialogOpen(false));
26066
26244
  };
26067
26245
  const handleButtonClick = () => {
26068
- let signatureRole = digitalSignatureRole.User;
26069
- if (loginUserDetail.data.slug === loginUserRoles.Admin) {
26070
- signatureRole = digitalSignatureRole.Admin;
26071
- } else if (loginUserDetail.data.slug === loginUserRoles.Lawyer) {
26072
- signatureRole = digitalSignatureRole.Lawyer;
26246
+ if (!requireAuthFactor) {
26247
+ const request_data = {
26248
+ FinanceApplicationId: financeDetail.id,
26249
+ SignatureRole: getSignatureRole()
26250
+ };
26251
+ dispatch(DigitalProcessSignature(request_data));
26252
+ return;
26253
+ }
26254
+ if (isOfficer) {
26255
+ setAuthFactorOpen(true);
26256
+ return;
26073
26257
  }
26258
+ dispatch(RequestSigningOtp(financeDetail.id)).then((success) => {
26259
+ if (success) {
26260
+ setAuthFactorOpen(true);
26261
+ }
26262
+ });
26263
+ };
26264
+ const handleAuthFactorClose = () => {
26265
+ setAuthFactorOpen(false);
26266
+ };
26267
+ const handleAuthFactorSubmit = async (authFactorValue) => {
26074
26268
  const request_data = {
26075
26269
  FinanceApplicationId: financeDetail.id,
26076
- SignatureRole: signatureRole
26270
+ AuthFactor: authFactorValue
26077
26271
  };
26078
- dispatch(DigitalProcessSignature(request_data));
26272
+ const success = await dispatch(DigitalProcessSignature(request_data));
26273
+ if (success) {
26274
+ setAuthFactorOpen(false);
26275
+ }
26276
+ };
26277
+ const handleResendOtp = () => {
26278
+ dispatch(RequestSigningOtp(financeDetail.id));
26079
26279
  };
26080
26280
  const handleClose = (_event, reason) => {
26081
26281
  if (reason && reason === "backdropClick") return;
@@ -26094,11 +26294,13 @@ function DocumentSigningDialog({
26094
26294
  }
26095
26295
  };
26096
26296
  const handlePDFPreviewClose = () => {
26297
+ setAuthFactorOpen(false);
26097
26298
  dispatch(setDocumentSignJPDF(""));
26098
26299
  dispatch(setActiveFinanceDetail(null));
26099
26300
  dispatch(setIsDocumentSignatureDialogOpen(false));
26100
26301
  };
26101
26302
  const handleSignedPDFClose = () => {
26303
+ setAuthFactorOpen(false);
26102
26304
  dispatch(setSignedDocumentJPDF(""));
26103
26305
  dispatch(setActiveFinanceDetail(null));
26104
26306
  dispatch(setIsDocumentSignatureDialogOpen(false));
@@ -26166,7 +26368,7 @@ function DocumentSigningDialog({
26166
26368
  {
26167
26369
  filePath: signedDocumentJPDF || documentSignJPDF,
26168
26370
  open: !!(signedDocumentJPDF || documentSignJPDF),
26169
- isButtonLoading: isProcessSignatureLoading,
26371
+ isButtonLoading: isProcessSignatureLoading || isRequestSigningOtpLoading,
26170
26372
  onClose: signedDocumentJPDF ? handleSignedPDFClose : handlePDFPreviewClose,
26171
26373
  bottomButtonLabel: t2("SIGN_DOCUMENT"),
26172
26374
  onConfirm: handleButtonClick,
@@ -26180,6 +26382,19 @@ function DocumentSigningDialog({
26180
26382
  showConfirmCheckbox,
26181
26383
  pdfOnly: !!signedDocumentJPDF
26182
26384
  }
26385
+ ),
26386
+ requireAuthFactor && /* @__PURE__ */ jsx(
26387
+ AuthFactorStep,
26388
+ {
26389
+ open: authFactorOpen,
26390
+ mode: isOfficer ? AuthFactorMode.PIN : AuthFactorMode.OTP,
26391
+ loading: isProcessSignatureLoading,
26392
+ onClose: handleAuthFactorClose,
26393
+ onSubmit: handleAuthFactorSubmit,
26394
+ onResendOtp: isOfficer ? void 0 : handleResendOtp,
26395
+ resendLoading: isRequestSigningOtpLoading,
26396
+ ColoredSubmitButton
26397
+ }
26183
26398
  )
26184
26399
  ] })
26185
26400
  ] }) }) });
@@ -46686,9 +46901,9 @@ const useDesktopPicker = (_ref) => {
46686
46901
  additionalViewProps: {},
46687
46902
  wrapperVariant: "desktop"
46688
46903
  }));
46689
- const InputAdornment$1 = (_slots$inputAdornment = slots.inputAdornment) != null ? _slots$inputAdornment : InputAdornment;
46904
+ const InputAdornment$12 = (_slots$inputAdornment = slots.inputAdornment) != null ? _slots$inputAdornment : InputAdornment;
46690
46905
  const _useSlotProps = useSlotProps({
46691
- elementType: InputAdornment$1,
46906
+ elementType: InputAdornment$12,
46692
46907
  externalSlotProps: innerSlotProps == null ? void 0 : innerSlotProps.inputAdornment,
46693
46908
  additionalProps: {
46694
46909
  position: "end"
@@ -46731,7 +46946,7 @@ const useDesktopPicker = (_ref) => {
46731
46946
  if (hasUIView) {
46732
46947
  fieldProps.InputProps = _extends({}, fieldProps.InputProps, {
46733
46948
  ref: containerRef,
46734
- [`${inputAdornmentProps.position}Adornment`]: /* @__PURE__ */ jsx(InputAdornment$1, _extends({}, inputAdornmentProps, {
46949
+ [`${inputAdornmentProps.position}Adornment`]: /* @__PURE__ */ jsx(InputAdornment$12, _extends({}, inputAdornmentProps, {
46735
46950
  children: /* @__PURE__ */ jsx(OpenPickerButton, _extends({}, openPickerButtonProps, {
46736
46951
  children: /* @__PURE__ */ jsx(OpenPickerIcon, _extends({}, innerSlotProps == null ? void 0 : innerSlotProps.openPickerIcon))
46737
46952
  }))