formanitor 0.0.48 → 0.0.50
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.
- package/dist/index.cjs +867 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +129 -5
- package/dist/index.d.ts +129 -5
- package/dist/index.mjs +865 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -67,8 +67,25 @@ function fieldMetaRequestsMarMedicationOrdersButton(meta) {
|
|
|
67
67
|
|
|
68
68
|
// src/core/validate.ts
|
|
69
69
|
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
70
|
-
function validateField(value, field) {
|
|
70
|
+
function validateField(value, field, allValues) {
|
|
71
71
|
if (field.hidden || field.disabled) return null;
|
|
72
|
+
if (field.type === "otp_input") {
|
|
73
|
+
if (field.required) {
|
|
74
|
+
if (!value || typeof value !== "object" || value.verified !== true) {
|
|
75
|
+
return "Please verify your mobile number with OTP";
|
|
76
|
+
}
|
|
77
|
+
const otpField = field;
|
|
78
|
+
const phoneFieldId = otpField.meta?.phoneFieldId ?? "phone";
|
|
79
|
+
if (allValues) {
|
|
80
|
+
const phoneRaw = allValues[phoneFieldId] ?? "";
|
|
81
|
+
const e164 = phoneRaw.startsWith("+") ? phoneRaw : `+91${phoneRaw}`;
|
|
82
|
+
if (value.phone && value.phone !== e164) {
|
|
83
|
+
return "Mobile number changed \u2014 please re-verify";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
72
89
|
if (field.required) {
|
|
73
90
|
if (value === null || value === void 0 || value === "") {
|
|
74
91
|
return "This field is required";
|
|
@@ -129,7 +146,7 @@ function validateForm(values, fields) {
|
|
|
129
146
|
const errors = {};
|
|
130
147
|
for (const [key, field] of Object.entries(fields)) {
|
|
131
148
|
if (field.type === "static_text") continue;
|
|
132
|
-
const error = validateField(values[key], field);
|
|
149
|
+
const error = validateField(values[key], field, values);
|
|
133
150
|
if (error) {
|
|
134
151
|
errors[key] = error;
|
|
135
152
|
}
|
|
@@ -1206,6 +1223,9 @@ var FormStore = class {
|
|
|
1206
1223
|
persistence;
|
|
1207
1224
|
prefillData = {};
|
|
1208
1225
|
config;
|
|
1226
|
+
// Widget-owned errors that schema validation cannot express (e.g. "OTP not sent yet").
|
|
1227
|
+
// Merged on top of validateForm output in evaluate().
|
|
1228
|
+
externalErrors = {};
|
|
1209
1229
|
// Cache for debounce
|
|
1210
1230
|
saveTimeout = null;
|
|
1211
1231
|
draftCallbackTimeout = null;
|
|
@@ -1325,6 +1345,9 @@ var FormStore = class {
|
|
|
1325
1345
|
getSchema() {
|
|
1326
1346
|
return this.schema;
|
|
1327
1347
|
}
|
|
1348
|
+
getConfig() {
|
|
1349
|
+
return this.config;
|
|
1350
|
+
}
|
|
1328
1351
|
getFieldDef(fieldId) {
|
|
1329
1352
|
return this.fieldMap[fieldId];
|
|
1330
1353
|
}
|
|
@@ -1372,6 +1395,17 @@ var FormStore = class {
|
|
|
1372
1395
|
this.notify();
|
|
1373
1396
|
this.scheduleSave();
|
|
1374
1397
|
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Let a widget report an error that schema validation cannot express
|
|
1400
|
+
* (e.g. "Send OTP to continue" until the API call succeeds).
|
|
1401
|
+
* Pass `null` to clear a previously set external error.
|
|
1402
|
+
*/
|
|
1403
|
+
setExternalError(fieldId, message) {
|
|
1404
|
+
if ((this.externalErrors[fieldId] ?? null) === message) return;
|
|
1405
|
+
this.externalErrors = { ...this.externalErrors, [fieldId]: message };
|
|
1406
|
+
this.evaluate();
|
|
1407
|
+
this.notify();
|
|
1408
|
+
}
|
|
1375
1409
|
setTouched(fieldId) {
|
|
1376
1410
|
if (this.state.touched[fieldId]) return;
|
|
1377
1411
|
this.state.touched = { ...this.state.touched, [fieldId]: true };
|
|
@@ -1500,16 +1534,42 @@ var FormStore = class {
|
|
|
1500
1534
|
result[field.id] = parts.length > 0 ? parts.join("\n\n") : null;
|
|
1501
1535
|
return;
|
|
1502
1536
|
}
|
|
1503
|
-
if (field.type === "
|
|
1537
|
+
if (field.type === "otp_input") {
|
|
1504
1538
|
const raw = values[field.id];
|
|
1505
|
-
if (
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1539
|
+
if (typeof raw === "string") {
|
|
1540
|
+
result[field.id] = raw;
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw) && raw.verified === true) {
|
|
1544
|
+
const code = raw.code;
|
|
1545
|
+
result[field.id] = typeof code === "string" ? code : null;
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
result[field.id] = null;
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
if (field.type === "image_upload" || field.type === "media_upload" || field.type === "signature" || field.type === "file_upload") {
|
|
1552
|
+
const raw = values[field.id];
|
|
1553
|
+
if (raw === void 0 || raw === null || raw === "") {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
const wrapKey = (key2) => {
|
|
1557
|
+
if (typeof key2 !== "string" || !key2) return null;
|
|
1558
|
+
return { _type: "s3_key", value: key2 };
|
|
1559
|
+
};
|
|
1560
|
+
if (Array.isArray(raw)) {
|
|
1561
|
+
const wrapped2 = raw.map(
|
|
1562
|
+
(item) => typeof item === "string" ? wrapKey(item) : wrapKey(item.s3_key ?? item.value)
|
|
1563
|
+
).filter(Boolean);
|
|
1564
|
+
if (wrapped2.length > 0) {
|
|
1565
|
+
result[field.id] = wrapped2;
|
|
1512
1566
|
}
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
const key = typeof raw === "string" ? raw : raw.s3_key ?? raw.value ?? null;
|
|
1570
|
+
const wrapped = wrapKey(key);
|
|
1571
|
+
if (wrapped) {
|
|
1572
|
+
result[field.id] = wrapped;
|
|
1513
1573
|
}
|
|
1514
1574
|
}
|
|
1515
1575
|
});
|
|
@@ -1616,8 +1676,12 @@ var FormStore = class {
|
|
|
1616
1676
|
};
|
|
1617
1677
|
});
|
|
1618
1678
|
const errors = validateForm(this.state.values, effectiveFields);
|
|
1619
|
-
|
|
1620
|
-
|
|
1679
|
+
const merged = { ...errors };
|
|
1680
|
+
for (const [id, msg] of Object.entries(this.externalErrors)) {
|
|
1681
|
+
if (msg) merged[id] = msg;
|
|
1682
|
+
}
|
|
1683
|
+
this.state.errors = merged;
|
|
1684
|
+
this.state.isValid = Object.keys(merged).length === 0;
|
|
1621
1685
|
}
|
|
1622
1686
|
// --- Workflow ---
|
|
1623
1687
|
getAvailableTransitions() {
|
|
@@ -3743,6 +3807,190 @@ var SignatureUploadWidget = ({ fieldId }) => {
|
|
|
3743
3807
|
showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
|
|
3744
3808
|
] });
|
|
3745
3809
|
};
|
|
3810
|
+
function Upload3({
|
|
3811
|
+
value,
|
|
3812
|
+
accept,
|
|
3813
|
+
multiple,
|
|
3814
|
+
disabled,
|
|
3815
|
+
onChange,
|
|
3816
|
+
className,
|
|
3817
|
+
formatsLabel,
|
|
3818
|
+
placeholder = "Upload File",
|
|
3819
|
+
beforeUploadContent,
|
|
3820
|
+
id: idProp
|
|
3821
|
+
}) {
|
|
3822
|
+
const inputRef = React15__namespace.default.useRef(null);
|
|
3823
|
+
const generatedId = React15__namespace.default.useId();
|
|
3824
|
+
const inputId = idProp ?? generatedId;
|
|
3825
|
+
const handleFileChange = (e) => {
|
|
3826
|
+
onChange(e.target.files ?? null);
|
|
3827
|
+
};
|
|
3828
|
+
React15__namespace.default.useEffect(() => {
|
|
3829
|
+
if (!value && inputRef.current) {
|
|
3830
|
+
inputRef.current.value = "";
|
|
3831
|
+
}
|
|
3832
|
+
}, [value]);
|
|
3833
|
+
const count = value?.length ?? 0;
|
|
3834
|
+
const formatsText = formatsLabel ?? accept;
|
|
3835
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3836
|
+
"div",
|
|
3837
|
+
{
|
|
3838
|
+
className: cn(
|
|
3839
|
+
"border border-dashed bg-[#F7F7F7] border-gray-200 rounded-lg py-5 px-4 flex items-center justify-center text-[#989898]",
|
|
3840
|
+
disabled && "opacity-50 pointer-events-none",
|
|
3841
|
+
className
|
|
3842
|
+
),
|
|
3843
|
+
children: [
|
|
3844
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3845
|
+
"input",
|
|
3846
|
+
{
|
|
3847
|
+
type: "file",
|
|
3848
|
+
ref: inputRef,
|
|
3849
|
+
accept,
|
|
3850
|
+
id: inputId,
|
|
3851
|
+
className: "hidden",
|
|
3852
|
+
multiple,
|
|
3853
|
+
disabled,
|
|
3854
|
+
onChange: handleFileChange
|
|
3855
|
+
}
|
|
3856
|
+
),
|
|
3857
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3858
|
+
"label",
|
|
3859
|
+
{
|
|
3860
|
+
htmlFor: inputId,
|
|
3861
|
+
className: cn("cursor-pointer w-full", disabled && "cursor-not-allowed"),
|
|
3862
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { children: count > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center text-sm font-semibold text-gray-700", children: [
|
|
3863
|
+
count,
|
|
3864
|
+
" file",
|
|
3865
|
+
count === 1 ? "" : "s",
|
|
3866
|
+
" selected"
|
|
3867
|
+
] }) : beforeUploadContent ? beforeUploadContent : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center", children: [
|
|
3868
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { size: 16, className: "rotate-180" }),
|
|
3869
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs font-semibold mt-0.5 text-gray-700", children: placeholder }),
|
|
3870
|
+
formatsText ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 text-[10px]", children: [
|
|
3871
|
+
"Supported formats - ",
|
|
3872
|
+
formatsText
|
|
3873
|
+
] }) : null
|
|
3874
|
+
] }) })
|
|
3875
|
+
}
|
|
3876
|
+
)
|
|
3877
|
+
]
|
|
3878
|
+
}
|
|
3879
|
+
);
|
|
3880
|
+
}
|
|
3881
|
+
function fileListFromArray(files) {
|
|
3882
|
+
const dt = new DataTransfer();
|
|
3883
|
+
files.forEach((f) => dt.items.add(f));
|
|
3884
|
+
return dt.files;
|
|
3885
|
+
}
|
|
3886
|
+
function matchesAccept(file, accept) {
|
|
3887
|
+
if (!accept || accept === "*/*") return true;
|
|
3888
|
+
const tokens = accept.split(",").map((t) => t.trim().toLowerCase());
|
|
3889
|
+
const name = file.name.toLowerCase();
|
|
3890
|
+
const type = file.type.toLowerCase();
|
|
3891
|
+
return tokens.some((token) => {
|
|
3892
|
+
if (token.startsWith(".")) return name.endsWith(token);
|
|
3893
|
+
if (token.endsWith("/*")) {
|
|
3894
|
+
const prefix = token.slice(0, -1);
|
|
3895
|
+
return type.startsWith(prefix);
|
|
3896
|
+
}
|
|
3897
|
+
return type === token;
|
|
3898
|
+
});
|
|
3899
|
+
}
|
|
3900
|
+
var FormFileUploadWidget = ({
|
|
3901
|
+
fieldId
|
|
3902
|
+
}) => {
|
|
3903
|
+
const { fieldDef, value, setValue, setTouched, error, disabled, touched } = useField(fieldId);
|
|
3904
|
+
const store = useFormStore();
|
|
3905
|
+
const { state } = useForm();
|
|
3906
|
+
const [localFiles, setLocalFiles] = React15.useState(null);
|
|
3907
|
+
const [isUploading, setIsUploading] = React15.useState(false);
|
|
3908
|
+
const showError = !!error && (touched || state.submitAttempted);
|
|
3909
|
+
if (!fieldDef || fieldDef.type !== "file_upload") return null;
|
|
3910
|
+
const def = fieldDef;
|
|
3911
|
+
const accept = def.accept ?? "*/*";
|
|
3912
|
+
const maxSize = def.maxSize ?? 10 * 1024 * 1024;
|
|
3913
|
+
const allowMultiple = def.multiple === true;
|
|
3914
|
+
const uploadHandler = store.getUploadHandler();
|
|
3915
|
+
if (!uploadHandler) {
|
|
3916
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
3917
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
|
|
3918
|
+
fieldDef.label,
|
|
3919
|
+
fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
|
|
3920
|
+
] }),
|
|
3921
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-2 border-red-300 rounded-lg p-4 bg-red-50", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-red-600", children: "Upload handler not configured. Please provide an onUpload function in FormProvider config." }) })
|
|
3922
|
+
] });
|
|
3923
|
+
}
|
|
3924
|
+
const hasStoredValue = value !== null && value !== void 0 && value !== "" && !(Array.isArray(value) && value.length === 0);
|
|
3925
|
+
const handleChange = async (files) => {
|
|
3926
|
+
setTouched();
|
|
3927
|
+
if (!files || files.length === 0) {
|
|
3928
|
+
setLocalFiles(null);
|
|
3929
|
+
setValue(null);
|
|
3930
|
+
return;
|
|
3931
|
+
}
|
|
3932
|
+
const selected = Array.from(files);
|
|
3933
|
+
for (const file of selected) {
|
|
3934
|
+
if (!matchesAccept(file, accept)) {
|
|
3935
|
+
alert(`File type not allowed: ${file.name}`);
|
|
3936
|
+
return;
|
|
3937
|
+
}
|
|
3938
|
+
if (file.size > maxSize) {
|
|
3939
|
+
alert(
|
|
3940
|
+
`File must be less than ${Math.round(maxSize / (1024 * 1024))}MB: ${file.name}`
|
|
3941
|
+
);
|
|
3942
|
+
return;
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
const toUpload = allowMultiple ? selected : [selected[0]];
|
|
3946
|
+
setLocalFiles(fileListFromArray(toUpload));
|
|
3947
|
+
setIsUploading(true);
|
|
3948
|
+
store.incrementPendingUploads();
|
|
3949
|
+
try {
|
|
3950
|
+
if (allowMultiple) {
|
|
3951
|
+
const keys = [];
|
|
3952
|
+
for (const file of toUpload) {
|
|
3953
|
+
keys.push(await uploadHandler(file, file.name));
|
|
3954
|
+
}
|
|
3955
|
+
setValue(keys);
|
|
3956
|
+
} else {
|
|
3957
|
+
const key = await uploadHandler(toUpload[0], toUpload[0].name);
|
|
3958
|
+
setValue(key);
|
|
3959
|
+
}
|
|
3960
|
+
} catch (err) {
|
|
3961
|
+
console.error("[FormFileUpload] Upload failed:", err);
|
|
3962
|
+
const message = err instanceof Error ? err.message : "Upload failed. Please try again.";
|
|
3963
|
+
alert(message);
|
|
3964
|
+
setLocalFiles(null);
|
|
3965
|
+
setValue(null);
|
|
3966
|
+
} finally {
|
|
3967
|
+
setIsUploading(false);
|
|
3968
|
+
store.decrementPendingUploads();
|
|
3969
|
+
}
|
|
3970
|
+
};
|
|
3971
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
3972
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
|
|
3973
|
+
fieldDef.label,
|
|
3974
|
+
fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
|
|
3975
|
+
] }),
|
|
3976
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3977
|
+
Upload3,
|
|
3978
|
+
{
|
|
3979
|
+
accept,
|
|
3980
|
+
multiple: allowMultiple,
|
|
3981
|
+
value: localFiles,
|
|
3982
|
+
onChange: handleChange,
|
|
3983
|
+
disabled: disabled || isUploading,
|
|
3984
|
+
placeholder: isUploading ? "Uploading..." : def.uploadPlaceholder ?? "Upload File",
|
|
3985
|
+
formatsLabel: def.uploadFormats,
|
|
3986
|
+
className: isUploading ? "opacity-70" : void 0
|
|
3987
|
+
}
|
|
3988
|
+
),
|
|
3989
|
+
hasStoredValue && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-green-600", children: allowMultiple && Array.isArray(value) ? `${value.length} file(s) uploaded` : "File uploaded" }),
|
|
3990
|
+
fieldDef.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500", children: fieldDef.description }),
|
|
3991
|
+
showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
|
|
3992
|
+
] });
|
|
3993
|
+
};
|
|
3746
3994
|
var EditableTableWidget = ({
|
|
3747
3995
|
fieldId
|
|
3748
3996
|
}) => {
|
|
@@ -13810,6 +14058,347 @@ var OBGPathwayWidget = ({ fieldId }) => {
|
|
|
13810
14058
|
] })
|
|
13811
14059
|
] });
|
|
13812
14060
|
};
|
|
14061
|
+
var RESEND_COOLDOWN_SECONDS = 30;
|
|
14062
|
+
var PhoneInputWidget = ({ fieldId }) => {
|
|
14063
|
+
const { fieldDef, error, touched, disabled, value: fieldValue } = useField(fieldId);
|
|
14064
|
+
const { state } = useForm();
|
|
14065
|
+
const store = useFormStore();
|
|
14066
|
+
const rawPhone = fieldValue ?? "";
|
|
14067
|
+
const wasAlreadySent = rawPhone.length === 10 && !error;
|
|
14068
|
+
const [status, setStatus] = React15.useState(wasAlreadySent ? "sent" : "idle");
|
|
14069
|
+
const [resendCooldown, setResendCooldown] = React15.useState(0);
|
|
14070
|
+
const [resendError, setResendError] = React15.useState("");
|
|
14071
|
+
const hasSentRef = React15.useRef(wasAlreadySent);
|
|
14072
|
+
const sentPhoneRef = React15.useRef(wasAlreadySent ? rawPhone : "");
|
|
14073
|
+
const isSendingRef = React15.useRef(false);
|
|
14074
|
+
const isResendingRef = React15.useRef(false);
|
|
14075
|
+
const showError = !!error && (touched || state.submitAttempted) && status !== "sending" && status !== "sent";
|
|
14076
|
+
React15.useEffect(() => {
|
|
14077
|
+
if (resendCooldown <= 0) return;
|
|
14078
|
+
const t = setTimeout(() => setResendCooldown((c) => Math.max(0, c - 1)), 1e3);
|
|
14079
|
+
return () => clearTimeout(t);
|
|
14080
|
+
}, [resendCooldown]);
|
|
14081
|
+
if (!fieldDef) return null;
|
|
14082
|
+
const placeholder = typeof fieldDef.placeholder === "string" && fieldDef.placeholder.trim() ? fieldDef.placeholder : "Enter 10-digit mobile number";
|
|
14083
|
+
const handleChange = (e) => {
|
|
14084
|
+
const digits = e.target.value.replace(/\D/g, "").slice(0, 10);
|
|
14085
|
+
if (hasSentRef.current && digits !== sentPhoneRef.current) {
|
|
14086
|
+
hasSentRef.current = false;
|
|
14087
|
+
sentPhoneRef.current = "";
|
|
14088
|
+
setStatus("idle");
|
|
14089
|
+
setResendCooldown(0);
|
|
14090
|
+
setResendError("");
|
|
14091
|
+
store.setExternalError(fieldId, "Send OTP to continue");
|
|
14092
|
+
const otpFieldId = fieldDef.meta?.otpFieldId;
|
|
14093
|
+
if (otpFieldId) {
|
|
14094
|
+
store.setValue(otpFieldId, "");
|
|
14095
|
+
store.setExternalError(otpFieldId, "Verify OTP to continue");
|
|
14096
|
+
}
|
|
14097
|
+
}
|
|
14098
|
+
store.setValue(fieldId, digits);
|
|
14099
|
+
if (digits.length === 10 && !hasSentRef.current && !isSendingRef.current) {
|
|
14100
|
+
sendOtp(digits);
|
|
14101
|
+
}
|
|
14102
|
+
};
|
|
14103
|
+
const sendOtp = async (phone) => {
|
|
14104
|
+
if (isSendingRef.current) return;
|
|
14105
|
+
isSendingRef.current = true;
|
|
14106
|
+
setStatus("sending");
|
|
14107
|
+
store.setExternalError(fieldId, "Sending OTP...");
|
|
14108
|
+
try {
|
|
14109
|
+
const authCfg = store.getConfig().auth;
|
|
14110
|
+
const base = authCfg?.baseUrl ?? ((typeof process !== "undefined" && process.env?.NEXT_PUBLIC_API_URL) ?? "");
|
|
14111
|
+
const sendPath = authCfg?.sendPath ?? "/api/auth/otp/send/";
|
|
14112
|
+
const countryCode = authCfg?.defaultCountryCode ?? "+91";
|
|
14113
|
+
const res = await fetch(`${base}${sendPath}`, {
|
|
14114
|
+
method: "POST",
|
|
14115
|
+
headers: { "Content-Type": "application/json" },
|
|
14116
|
+
body: JSON.stringify({ mobile_number: `${countryCode}${phone}` })
|
|
14117
|
+
});
|
|
14118
|
+
const data = await res.json().catch(() => ({}));
|
|
14119
|
+
if (res.ok) {
|
|
14120
|
+
hasSentRef.current = true;
|
|
14121
|
+
sentPhoneRef.current = phone;
|
|
14122
|
+
setStatus("sent");
|
|
14123
|
+
setResendCooldown(RESEND_COOLDOWN_SECONDS);
|
|
14124
|
+
store.setExternalError(fieldId, null);
|
|
14125
|
+
} else {
|
|
14126
|
+
setStatus("error");
|
|
14127
|
+
store.setExternalError(fieldId, data.error ?? "Failed to send OTP. Please try again.");
|
|
14128
|
+
}
|
|
14129
|
+
} catch {
|
|
14130
|
+
setStatus("error");
|
|
14131
|
+
store.setExternalError(fieldId, "Network error. Please try again.");
|
|
14132
|
+
} finally {
|
|
14133
|
+
isSendingRef.current = false;
|
|
14134
|
+
}
|
|
14135
|
+
};
|
|
14136
|
+
const handleResend = async () => {
|
|
14137
|
+
if (isResendingRef.current || resendCooldown > 0) return;
|
|
14138
|
+
isResendingRef.current = true;
|
|
14139
|
+
setResendError("");
|
|
14140
|
+
try {
|
|
14141
|
+
const authCfg = store.getConfig().auth;
|
|
14142
|
+
const base = authCfg?.baseUrl ?? ((typeof process !== "undefined" && process.env?.NEXT_PUBLIC_API_URL) ?? "");
|
|
14143
|
+
const resendPath = authCfg?.resendPath ?? "/api/auth/otp/resend/";
|
|
14144
|
+
const countryCode = authCfg?.defaultCountryCode ?? "+91";
|
|
14145
|
+
const res = await fetch(`${base}${resendPath}`, {
|
|
14146
|
+
method: "POST",
|
|
14147
|
+
headers: { "Content-Type": "application/json" },
|
|
14148
|
+
body: JSON.stringify({ mobile_number: `${countryCode}${rawPhone}`, retrytype: "text" })
|
|
14149
|
+
});
|
|
14150
|
+
const data = await res.json().catch(() => ({}));
|
|
14151
|
+
if (res.ok) {
|
|
14152
|
+
setResendCooldown(RESEND_COOLDOWN_SECONDS);
|
|
14153
|
+
setResendError("");
|
|
14154
|
+
} else {
|
|
14155
|
+
setResendError(data.error ?? "Failed to resend OTP. Please try again.");
|
|
14156
|
+
}
|
|
14157
|
+
} catch {
|
|
14158
|
+
setResendError("Network error. Please try again.");
|
|
14159
|
+
} finally {
|
|
14160
|
+
isResendingRef.current = false;
|
|
14161
|
+
}
|
|
14162
|
+
};
|
|
14163
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
14164
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
|
|
14165
|
+
fieldDef.label,
|
|
14166
|
+
" ",
|
|
14167
|
+
fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
|
|
14168
|
+
] }),
|
|
14169
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
14170
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14171
|
+
Input,
|
|
14172
|
+
{
|
|
14173
|
+
id: fieldId,
|
|
14174
|
+
type: "tel",
|
|
14175
|
+
inputMode: "numeric",
|
|
14176
|
+
value: rawPhone,
|
|
14177
|
+
onChange: handleChange,
|
|
14178
|
+
onBlur: () => store.setTouched(fieldId),
|
|
14179
|
+
disabled: disabled || status === "sending",
|
|
14180
|
+
placeholder,
|
|
14181
|
+
maxLength: 10,
|
|
14182
|
+
className: cn(
|
|
14183
|
+
showError && "border-red-500",
|
|
14184
|
+
status === "sent" && "border-green-500 pr-9",
|
|
14185
|
+
status === "sending" && "pr-9"
|
|
14186
|
+
)
|
|
14187
|
+
}
|
|
14188
|
+
),
|
|
14189
|
+
status === "sending" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-3 flex items-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14190
|
+
"svg",
|
|
14191
|
+
{
|
|
14192
|
+
className: "animate-spin h-4 w-4 text-muted-foreground",
|
|
14193
|
+
viewBox: "0 0 24 24",
|
|
14194
|
+
fill: "none",
|
|
14195
|
+
children: [
|
|
14196
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14197
|
+
"circle",
|
|
14198
|
+
{
|
|
14199
|
+
className: "opacity-25",
|
|
14200
|
+
cx: "12",
|
|
14201
|
+
cy: "12",
|
|
14202
|
+
r: "10",
|
|
14203
|
+
stroke: "currentColor",
|
|
14204
|
+
strokeWidth: "4"
|
|
14205
|
+
}
|
|
14206
|
+
),
|
|
14207
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14208
|
+
"path",
|
|
14209
|
+
{
|
|
14210
|
+
className: "opacity-75",
|
|
14211
|
+
fill: "currentColor",
|
|
14212
|
+
d: "M4 12a8 8 0 018-8v8H4z"
|
|
14213
|
+
}
|
|
14214
|
+
)
|
|
14215
|
+
]
|
|
14216
|
+
}
|
|
14217
|
+
) }),
|
|
14218
|
+
status === "sent" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-3 flex items-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14219
|
+
"svg",
|
|
14220
|
+
{
|
|
14221
|
+
className: "h-4 w-4 text-green-500",
|
|
14222
|
+
viewBox: "0 0 20 20",
|
|
14223
|
+
fill: "currentColor",
|
|
14224
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14225
|
+
"path",
|
|
14226
|
+
{
|
|
14227
|
+
fillRule: "evenodd",
|
|
14228
|
+
d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
|
|
14229
|
+
clipRule: "evenodd"
|
|
14230
|
+
}
|
|
14231
|
+
)
|
|
14232
|
+
}
|
|
14233
|
+
) })
|
|
14234
|
+
] }),
|
|
14235
|
+
status === "sent" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-green-600", children: [
|
|
14236
|
+
"OTP sent to +91",
|
|
14237
|
+
rawPhone
|
|
14238
|
+
] }),
|
|
14239
|
+
showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error }),
|
|
14240
|
+
status === "sent" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
14241
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Didn't receive it?" }),
|
|
14242
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14243
|
+
"button",
|
|
14244
|
+
{
|
|
14245
|
+
type: "button",
|
|
14246
|
+
onClick: handleResend,
|
|
14247
|
+
disabled: resendCooldown > 0,
|
|
14248
|
+
className: cn(
|
|
14249
|
+
"underline transition-colors",
|
|
14250
|
+
resendCooldown > 0 ? "opacity-40 cursor-not-allowed" : "text-primary hover:text-primary/80 cursor-pointer"
|
|
14251
|
+
),
|
|
14252
|
+
children: resendCooldown > 0 ? `Resend in ${resendCooldown}s` : "Resend OTP"
|
|
14253
|
+
}
|
|
14254
|
+
)
|
|
14255
|
+
] }),
|
|
14256
|
+
resendError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: resendError })
|
|
14257
|
+
] });
|
|
14258
|
+
};
|
|
14259
|
+
var OtpInputWidget = ({ fieldId }) => {
|
|
14260
|
+
const { fieldDef, error, touched, disabled } = useField(fieldId);
|
|
14261
|
+
const { state } = useForm();
|
|
14262
|
+
const store = useFormStore();
|
|
14263
|
+
const phoneFieldId = fieldDef?.meta?.phoneFieldId ?? "phone";
|
|
14264
|
+
const { value: phoneValue } = useField(phoneFieldId);
|
|
14265
|
+
const phone = phoneValue ?? "";
|
|
14266
|
+
const e164Phone = phone.startsWith("+") ? phone : `+91${phone}`;
|
|
14267
|
+
const [otp, setOtp] = React15.useState("");
|
|
14268
|
+
const [status, setStatus] = React15.useState("idle");
|
|
14269
|
+
const isVerifyingRef = React15.useRef(false);
|
|
14270
|
+
const showError = !!error && (touched || state.submitAttempted) && status !== "verifying" && status !== "verified";
|
|
14271
|
+
if (!fieldDef) return null;
|
|
14272
|
+
const placeholder = typeof fieldDef.placeholder === "string" && fieldDef.placeholder.trim() ? fieldDef.placeholder : "Enter 4-digit OTP";
|
|
14273
|
+
const handleChange = (e) => {
|
|
14274
|
+
const digits = e.target.value.replace(/\D/g, "").slice(0, 4);
|
|
14275
|
+
if (status === "failed") {
|
|
14276
|
+
setStatus("idle");
|
|
14277
|
+
store.setExternalError(fieldId, null);
|
|
14278
|
+
}
|
|
14279
|
+
setOtp(digits);
|
|
14280
|
+
store.setValue(fieldId, digits);
|
|
14281
|
+
if (digits.length === 4 && !isVerifyingRef.current) {
|
|
14282
|
+
verifyOtp(digits);
|
|
14283
|
+
}
|
|
14284
|
+
};
|
|
14285
|
+
const verifyOtp = async (code) => {
|
|
14286
|
+
if (isVerifyingRef.current) return;
|
|
14287
|
+
isVerifyingRef.current = true;
|
|
14288
|
+
setStatus("verifying");
|
|
14289
|
+
store.setExternalError(fieldId, "Verifying...");
|
|
14290
|
+
try {
|
|
14291
|
+
const authCfg = store.getConfig().auth;
|
|
14292
|
+
const base = authCfg?.baseUrl ?? ((typeof process !== "undefined" && process.env?.NEXT_PUBLIC_API_URL) ?? "");
|
|
14293
|
+
const verifyPath = authCfg?.verifyPath ?? "/api/auth/otp/verify/";
|
|
14294
|
+
const res = await fetch(`${base}${verifyPath}`, {
|
|
14295
|
+
method: "POST",
|
|
14296
|
+
headers: { "Content-Type": "application/json" },
|
|
14297
|
+
body: JSON.stringify({ mobile_number: e164Phone, otp_code: code })
|
|
14298
|
+
});
|
|
14299
|
+
const data = await res.json().catch(() => ({}));
|
|
14300
|
+
if (res.ok) {
|
|
14301
|
+
setStatus("verified");
|
|
14302
|
+
store.setExternalError(fieldId, null);
|
|
14303
|
+
store.setValue(fieldId, {
|
|
14304
|
+
verified: true,
|
|
14305
|
+
phone: e164Phone,
|
|
14306
|
+
code,
|
|
14307
|
+
...data?.verification_token ? { token: data.verification_token } : {}
|
|
14308
|
+
});
|
|
14309
|
+
} else {
|
|
14310
|
+
setStatus("failed");
|
|
14311
|
+
const msg = data.error ?? "Invalid OTP. Please try again.";
|
|
14312
|
+
store.setExternalError(fieldId, msg);
|
|
14313
|
+
setOtp("");
|
|
14314
|
+
store.setValue(fieldId, "");
|
|
14315
|
+
}
|
|
14316
|
+
} catch {
|
|
14317
|
+
setStatus("failed");
|
|
14318
|
+
store.setExternalError(fieldId, "Network error. Please try again.");
|
|
14319
|
+
setOtp("");
|
|
14320
|
+
store.setValue(fieldId, "");
|
|
14321
|
+
} finally {
|
|
14322
|
+
isVerifyingRef.current = false;
|
|
14323
|
+
}
|
|
14324
|
+
};
|
|
14325
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
14326
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
|
|
14327
|
+
fieldDef.label,
|
|
14328
|
+
" ",
|
|
14329
|
+
fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
|
|
14330
|
+
] }),
|
|
14331
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
14332
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14333
|
+
Input,
|
|
14334
|
+
{
|
|
14335
|
+
id: fieldId,
|
|
14336
|
+
type: "tel",
|
|
14337
|
+
inputMode: "numeric",
|
|
14338
|
+
value: otp,
|
|
14339
|
+
onChange: handleChange,
|
|
14340
|
+
onBlur: () => store.setTouched(fieldId),
|
|
14341
|
+
disabled: disabled || status === "verifying" || status === "verified",
|
|
14342
|
+
placeholder,
|
|
14343
|
+
maxLength: 4,
|
|
14344
|
+
className: cn(
|
|
14345
|
+
"tracking-widest text-center text-lg",
|
|
14346
|
+
showError && "border-red-500",
|
|
14347
|
+
status === "verified" && "border-green-500 pr-9",
|
|
14348
|
+
status === "verifying" && "pr-9"
|
|
14349
|
+
)
|
|
14350
|
+
}
|
|
14351
|
+
),
|
|
14352
|
+
status === "verifying" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-3 flex items-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14353
|
+
"svg",
|
|
14354
|
+
{
|
|
14355
|
+
className: "animate-spin h-4 w-4 text-muted-foreground",
|
|
14356
|
+
viewBox: "0 0 24 24",
|
|
14357
|
+
fill: "none",
|
|
14358
|
+
children: [
|
|
14359
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14360
|
+
"circle",
|
|
14361
|
+
{
|
|
14362
|
+
className: "opacity-25",
|
|
14363
|
+
cx: "12",
|
|
14364
|
+
cy: "12",
|
|
14365
|
+
r: "10",
|
|
14366
|
+
stroke: "currentColor",
|
|
14367
|
+
strokeWidth: "4"
|
|
14368
|
+
}
|
|
14369
|
+
),
|
|
14370
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14371
|
+
"path",
|
|
14372
|
+
{
|
|
14373
|
+
className: "opacity-75",
|
|
14374
|
+
fill: "currentColor",
|
|
14375
|
+
d: "M4 12a8 8 0 018-8v8H4z"
|
|
14376
|
+
}
|
|
14377
|
+
)
|
|
14378
|
+
]
|
|
14379
|
+
}
|
|
14380
|
+
) }),
|
|
14381
|
+
status === "verified" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-y-0 right-3 flex items-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14382
|
+
"svg",
|
|
14383
|
+
{
|
|
14384
|
+
className: "h-4 w-4 text-green-500",
|
|
14385
|
+
viewBox: "0 0 20 20",
|
|
14386
|
+
fill: "currentColor",
|
|
14387
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14388
|
+
"path",
|
|
14389
|
+
{
|
|
14390
|
+
fillRule: "evenodd",
|
|
14391
|
+
d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
|
|
14392
|
+
clipRule: "evenodd"
|
|
14393
|
+
}
|
|
14394
|
+
)
|
|
14395
|
+
}
|
|
14396
|
+
) })
|
|
14397
|
+
] }),
|
|
14398
|
+
status === "verified" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-green-600", children: "Mobile number verified successfully" }),
|
|
14399
|
+
showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
|
|
14400
|
+
] });
|
|
14401
|
+
};
|
|
13813
14402
|
var FieldRenderer = ({ fieldId }) => {
|
|
13814
14403
|
const { fieldDef, visible } = useField(fieldId);
|
|
13815
14404
|
if (!visible || !fieldDef) {
|
|
@@ -13849,6 +14438,8 @@ function renderWidget(fieldId, fieldDef) {
|
|
|
13849
14438
|
return /* @__PURE__ */ jsxRuntime.jsx(ImageUploadWidget, { fieldId });
|
|
13850
14439
|
case "media_upload":
|
|
13851
14440
|
return /* @__PURE__ */ jsxRuntime.jsx(MediaUploadWidget, { fieldId });
|
|
14441
|
+
case "file_upload":
|
|
14442
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FormFileUploadWidget, { fieldId });
|
|
13852
14443
|
case "signature":
|
|
13853
14444
|
return /* @__PURE__ */ jsxRuntime.jsx(SignatureUploadWidget, { fieldId });
|
|
13854
14445
|
case "editable_table":
|
|
@@ -13901,6 +14492,10 @@ function renderWidget(fieldId, fieldDef) {
|
|
|
13901
14492
|
return /* @__PURE__ */ jsxRuntime.jsx(OBGExaminationWidget, { fieldId });
|
|
13902
14493
|
case "obg_pathway":
|
|
13903
14494
|
return /* @__PURE__ */ jsxRuntime.jsx(OBGPathwayWidget, { fieldId });
|
|
14495
|
+
case "phone_input":
|
|
14496
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PhoneInputWidget, { fieldId });
|
|
14497
|
+
case "otp_input":
|
|
14498
|
+
return /* @__PURE__ */ jsxRuntime.jsx(OtpInputWidget, { fieldId });
|
|
13904
14499
|
case "toggle": {
|
|
13905
14500
|
const { value, setValue, setTouched, disabled } = useField(fieldId);
|
|
13906
14501
|
const store = useFormStore();
|
|
@@ -14021,16 +14616,6 @@ var DynamicForm = () => {
|
|
|
14021
14616
|
})
|
|
14022
14617
|
] });
|
|
14023
14618
|
};
|
|
14024
|
-
function PlainSection({
|
|
14025
|
-
title,
|
|
14026
|
-
showHeading,
|
|
14027
|
-
children
|
|
14028
|
-
}) {
|
|
14029
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 space-y-4", children: [
|
|
14030
|
-
showHeading ? /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-base font-medium text-foreground", children: title }) : null,
|
|
14031
|
-
children
|
|
14032
|
-
] });
|
|
14033
|
-
}
|
|
14034
14619
|
function renderSectionChildren(children) {
|
|
14035
14620
|
return children.map((child, index) => {
|
|
14036
14621
|
if (typeof child === "string") {
|
|
@@ -14050,6 +14635,16 @@ function renderSectionChildren(children) {
|
|
|
14050
14635
|
return /* @__PURE__ */ jsxRuntime.jsx(React15__namespace.default.Fragment, {}, index);
|
|
14051
14636
|
});
|
|
14052
14637
|
}
|
|
14638
|
+
function PlainSection({
|
|
14639
|
+
title,
|
|
14640
|
+
showHeading,
|
|
14641
|
+
children
|
|
14642
|
+
}) {
|
|
14643
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 space-y-4", children: [
|
|
14644
|
+
showHeading ? /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-base font-medium text-foreground", children: title }) : null,
|
|
14645
|
+
children
|
|
14646
|
+
] });
|
|
14647
|
+
}
|
|
14053
14648
|
var DynamicFormV2 = ({
|
|
14054
14649
|
showTitle = true,
|
|
14055
14650
|
sectionMode = "plain",
|
|
@@ -14090,6 +14685,247 @@ var DynamicFormV2 = ({
|
|
|
14090
14685
|
})
|
|
14091
14686
|
] });
|
|
14092
14687
|
};
|
|
14688
|
+
function flattenStepFieldIds(step) {
|
|
14689
|
+
const ids = [];
|
|
14690
|
+
for (const child of step.children) {
|
|
14691
|
+
if (typeof child === "string") {
|
|
14692
|
+
ids.push(child);
|
|
14693
|
+
} else if (child.type === "column_layout") {
|
|
14694
|
+
ids.push(...child.children);
|
|
14695
|
+
}
|
|
14696
|
+
}
|
|
14697
|
+
return ids;
|
|
14698
|
+
}
|
|
14699
|
+
function isFieldComplete(id, state, fieldMap) {
|
|
14700
|
+
if (state.visibility[id] === false) return true;
|
|
14701
|
+
if (state.errors[id]) return false;
|
|
14702
|
+
const def = fieldMap[id];
|
|
14703
|
+
if (def?.required) {
|
|
14704
|
+
const v = state.values[id];
|
|
14705
|
+
if (v === null || v === void 0 || v === "") return false;
|
|
14706
|
+
if (Array.isArray(v) && v.length === 0) return false;
|
|
14707
|
+
}
|
|
14708
|
+
return true;
|
|
14709
|
+
}
|
|
14710
|
+
function isStepComplete(step, state, fieldMap) {
|
|
14711
|
+
return flattenStepFieldIds(step).every(
|
|
14712
|
+
(id) => isFieldComplete(id, state, fieldMap)
|
|
14713
|
+
);
|
|
14714
|
+
}
|
|
14715
|
+
var Stepper2 = ({ steps, statuses, variant }) => {
|
|
14716
|
+
if (variant === "dots") {
|
|
14717
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 mb-6", children: steps.map((step, i) => {
|
|
14718
|
+
const status = statuses[i];
|
|
14719
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(React15__namespace.default.Fragment, { children: [
|
|
14720
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14721
|
+
"div",
|
|
14722
|
+
{
|
|
14723
|
+
className: cn(
|
|
14724
|
+
"h-2.5 w-2.5 rounded-full transition-colors",
|
|
14725
|
+
status === "complete" && "bg-primary",
|
|
14726
|
+
status === "active" && "bg-primary ring-2 ring-primary/30",
|
|
14727
|
+
status === "locked" && "bg-muted"
|
|
14728
|
+
),
|
|
14729
|
+
title: step.title
|
|
14730
|
+
}
|
|
14731
|
+
),
|
|
14732
|
+
i < steps.length - 1 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
14733
|
+
"div",
|
|
14734
|
+
{
|
|
14735
|
+
className: cn(
|
|
14736
|
+
"flex-1 h-px transition-colors",
|
|
14737
|
+
status === "complete" ? "bg-primary" : "bg-border"
|
|
14738
|
+
)
|
|
14739
|
+
}
|
|
14740
|
+
)
|
|
14741
|
+
] }, step.id);
|
|
14742
|
+
}) });
|
|
14743
|
+
}
|
|
14744
|
+
if (variant === "labels") {
|
|
14745
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-0 mb-6", children: steps.map((step, i) => {
|
|
14746
|
+
const status = statuses[i];
|
|
14747
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(React15__namespace.default.Fragment, { children: [
|
|
14748
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [
|
|
14749
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14750
|
+
"div",
|
|
14751
|
+
{
|
|
14752
|
+
className: cn(
|
|
14753
|
+
"h-2 w-2 rounded-full",
|
|
14754
|
+
status === "complete" && "bg-primary",
|
|
14755
|
+
status === "active" && "bg-primary",
|
|
14756
|
+
status === "locked" && "bg-muted"
|
|
14757
|
+
)
|
|
14758
|
+
}
|
|
14759
|
+
),
|
|
14760
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14761
|
+
"span",
|
|
14762
|
+
{
|
|
14763
|
+
className: cn(
|
|
14764
|
+
"text-xs whitespace-nowrap",
|
|
14765
|
+
status === "locked" ? "text-muted-foreground" : "text-foreground"
|
|
14766
|
+
),
|
|
14767
|
+
children: step.title
|
|
14768
|
+
}
|
|
14769
|
+
)
|
|
14770
|
+
] }),
|
|
14771
|
+
i < steps.length - 1 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
14772
|
+
"div",
|
|
14773
|
+
{
|
|
14774
|
+
className: cn(
|
|
14775
|
+
"flex-1 h-px mb-4 transition-colors",
|
|
14776
|
+
status === "complete" ? "bg-primary" : "bg-border"
|
|
14777
|
+
)
|
|
14778
|
+
}
|
|
14779
|
+
)
|
|
14780
|
+
] }, step.id);
|
|
14781
|
+
}) });
|
|
14782
|
+
}
|
|
14783
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-0 mb-6", children: steps.map((step, i) => {
|
|
14784
|
+
const status = statuses[i];
|
|
14785
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(React15__namespace.default.Fragment, { children: [
|
|
14786
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
14787
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14788
|
+
"div",
|
|
14789
|
+
{
|
|
14790
|
+
className: cn(
|
|
14791
|
+
"flex h-7 w-7 items-center justify-center rounded-full text-xs font-semibold border-2 transition-colors",
|
|
14792
|
+
status === "complete" && "border-primary bg-primary text-primary-foreground",
|
|
14793
|
+
status === "active" && "border-primary bg-background text-primary",
|
|
14794
|
+
status === "locked" && "border-border bg-background text-muted-foreground"
|
|
14795
|
+
),
|
|
14796
|
+
children: status === "complete" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5" }) : i + 1
|
|
14797
|
+
}
|
|
14798
|
+
),
|
|
14799
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14800
|
+
"span",
|
|
14801
|
+
{
|
|
14802
|
+
className: cn(
|
|
14803
|
+
"text-sm font-medium hidden sm:block",
|
|
14804
|
+
status === "locked" ? "text-muted-foreground" : "text-foreground"
|
|
14805
|
+
),
|
|
14806
|
+
children: step.title
|
|
14807
|
+
}
|
|
14808
|
+
)
|
|
14809
|
+
] }),
|
|
14810
|
+
i < steps.length - 1 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
14811
|
+
"div",
|
|
14812
|
+
{
|
|
14813
|
+
className: cn(
|
|
14814
|
+
"flex-1 h-px mx-3 transition-colors",
|
|
14815
|
+
status === "complete" ? "bg-primary" : "bg-border"
|
|
14816
|
+
)
|
|
14817
|
+
}
|
|
14818
|
+
)
|
|
14819
|
+
] }, step.id);
|
|
14820
|
+
}) });
|
|
14821
|
+
};
|
|
14822
|
+
var ActiveStep = ({
|
|
14823
|
+
title,
|
|
14824
|
+
children
|
|
14825
|
+
}) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 space-y-4", children: [
|
|
14826
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-base font-medium text-foreground", children: title }),
|
|
14827
|
+
children
|
|
14828
|
+
] });
|
|
14829
|
+
var Wizard = ({ node }) => {
|
|
14830
|
+
const store = useFormStore();
|
|
14831
|
+
const [formState, setFormState] = React15.useState(() => ({
|
|
14832
|
+
...store.getState()
|
|
14833
|
+
}));
|
|
14834
|
+
React15.useEffect(() => {
|
|
14835
|
+
return store.subscribe((state) => {
|
|
14836
|
+
console.log("[MultiStepForm] store update \u2192", {
|
|
14837
|
+
values: state.values,
|
|
14838
|
+
errors: state.errors,
|
|
14839
|
+
visibility: state.visibility
|
|
14840
|
+
});
|
|
14841
|
+
setFormState({ ...state });
|
|
14842
|
+
});
|
|
14843
|
+
}, [store]);
|
|
14844
|
+
const schema = store.getSchema();
|
|
14845
|
+
const fieldMap = {};
|
|
14846
|
+
schema.fields.forEach((f) => {
|
|
14847
|
+
fieldMap[f.id] = f;
|
|
14848
|
+
});
|
|
14849
|
+
const { steps, meta } = node;
|
|
14850
|
+
const showStepper = meta?.showStepper !== false;
|
|
14851
|
+
const variant = meta?.stepperVariant ?? "numbered";
|
|
14852
|
+
const collapseCompleted = meta?.collapseCompleted !== false;
|
|
14853
|
+
const stepComplete = steps.map((s) => {
|
|
14854
|
+
const fieldIds = flattenStepFieldIds(s);
|
|
14855
|
+
const complete = isStepComplete(s, formState, fieldMap);
|
|
14856
|
+
console.log(`[MultiStepForm] step "${s.id}" fieldIds=${JSON.stringify(fieldIds)} complete=${complete}`, {
|
|
14857
|
+
errors: fieldIds.map((id) => ({ id, error: formState.errors[id] })),
|
|
14858
|
+
values: fieldIds.map((id) => ({ id, value: formState.values[id] }))
|
|
14859
|
+
});
|
|
14860
|
+
return complete;
|
|
14861
|
+
});
|
|
14862
|
+
const activeIndex = stepComplete.findIndex((c) => !c) === -1 ? steps.length : stepComplete.findIndex((c) => !c);
|
|
14863
|
+
console.log(`[MultiStepForm] activeIndex=${activeIndex} stepComplete=${JSON.stringify(stepComplete)}`);
|
|
14864
|
+
const statuses = steps.map((_, i) => {
|
|
14865
|
+
if (i < activeIndex) return "complete";
|
|
14866
|
+
if (i === activeIndex) return "active";
|
|
14867
|
+
return "locked";
|
|
14868
|
+
});
|
|
14869
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
14870
|
+
node.title && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold mb-4", children: node.title }),
|
|
14871
|
+
showStepper && /* @__PURE__ */ jsxRuntime.jsx(Stepper2, { steps, statuses, variant }),
|
|
14872
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: steps.map((step, i) => {
|
|
14873
|
+
const status = statuses[i];
|
|
14874
|
+
if (status === "locked") return null;
|
|
14875
|
+
const children = renderSectionChildren(step.children);
|
|
14876
|
+
if (status === "complete") {
|
|
14877
|
+
if (collapseCompleted) {
|
|
14878
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: step.title, children }, step.id);
|
|
14879
|
+
}
|
|
14880
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14881
|
+
"div",
|
|
14882
|
+
{
|
|
14883
|
+
className: "mb-4 opacity-60 pointer-events-none select-none space-y-4",
|
|
14884
|
+
children: [
|
|
14885
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-base font-medium text-foreground", children: step.title }),
|
|
14886
|
+
children
|
|
14887
|
+
]
|
|
14888
|
+
},
|
|
14889
|
+
step.id
|
|
14890
|
+
);
|
|
14891
|
+
}
|
|
14892
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ActiveStep, { title: step.title, children }, step.id);
|
|
14893
|
+
}) })
|
|
14894
|
+
] });
|
|
14895
|
+
};
|
|
14896
|
+
var MultiStepForm = ({
|
|
14897
|
+
showTitle = true,
|
|
14898
|
+
className
|
|
14899
|
+
}) => {
|
|
14900
|
+
const store = useFormStore();
|
|
14901
|
+
const schema = store.getSchema();
|
|
14902
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-6", className), children: [
|
|
14903
|
+
showTitle && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold", children: schema.title }) }),
|
|
14904
|
+
schema.layout.map((node) => {
|
|
14905
|
+
if (node.type === "multi_step") {
|
|
14906
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Wizard, { node }, node.id);
|
|
14907
|
+
}
|
|
14908
|
+
if (node.type === "section") {
|
|
14909
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 space-y-4", children: [
|
|
14910
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-base font-medium text-foreground", children: node.title }),
|
|
14911
|
+
renderSectionChildren(node.children)
|
|
14912
|
+
] }, node.id);
|
|
14913
|
+
}
|
|
14914
|
+
if (node.type === "column_layout") {
|
|
14915
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14916
|
+
ColumnLayout,
|
|
14917
|
+
{
|
|
14918
|
+
columns: node.columns,
|
|
14919
|
+
label: node.label,
|
|
14920
|
+
children: node.children.map((fieldId) => /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId))
|
|
14921
|
+
},
|
|
14922
|
+
node.id
|
|
14923
|
+
);
|
|
14924
|
+
}
|
|
14925
|
+
return null;
|
|
14926
|
+
})
|
|
14927
|
+
] });
|
|
14928
|
+
};
|
|
14093
14929
|
var SUGGESTION_KEYS = /* @__PURE__ */ new Set(["medications", "investigations", "procedures"]);
|
|
14094
14930
|
function shallowEqualKeys(a, b) {
|
|
14095
14931
|
const keysA = Object.keys(a);
|
|
@@ -14702,6 +15538,8 @@ var ReadOnlyFieldRenderer = ({
|
|
|
14702
15538
|
return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyImageUpload, { fieldDef, value });
|
|
14703
15539
|
case "media_upload":
|
|
14704
15540
|
return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyMediaUpload, { fieldDef, value });
|
|
15541
|
+
case "file_upload":
|
|
15542
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyMediaUpload, { fieldDef, value });
|
|
14705
15543
|
case "signature":
|
|
14706
15544
|
return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlySignature, { fieldDef, value });
|
|
14707
15545
|
case "editable_table":
|
|
@@ -15052,7 +15890,8 @@ var FormControls = ({
|
|
|
15052
15890
|
actionsFullWidth = false,
|
|
15053
15891
|
buttonClassName,
|
|
15054
15892
|
onSaveDraft,
|
|
15055
|
-
onSubmit
|
|
15893
|
+
onSubmit,
|
|
15894
|
+
submitLabel
|
|
15056
15895
|
}) => {
|
|
15057
15896
|
const {
|
|
15058
15897
|
state,
|
|
@@ -15141,7 +15980,7 @@ var FormControls = ({
|
|
|
15141
15980
|
disabled: !state.isValid || hasUploadsInFlight,
|
|
15142
15981
|
size: "sm",
|
|
15143
15982
|
className: buttonCn,
|
|
15144
|
-
children: hasUploadsInFlight ? "Upload in progress\u2026" : availableTransitions.length > 1 ? `Submit to ${t.to}` : "Submit"
|
|
15983
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : availableTransitions.length > 1 ? `Submit to ${t.to}` : submitLabel ?? "Submit"
|
|
15145
15984
|
},
|
|
15146
15985
|
t.to
|
|
15147
15986
|
)) : onSubmit ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -15154,7 +15993,7 @@ var FormControls = ({
|
|
|
15154
15993
|
disabled: !state.isValid || hasUploadsInFlight,
|
|
15155
15994
|
size: "sm",
|
|
15156
15995
|
className: buttonCn,
|
|
15157
|
-
children: hasUploadsInFlight ? "Upload in progress\u2026" : "Submit"
|
|
15996
|
+
children: hasUploadsInFlight ? "Upload in progress\u2026" : submitLabel ?? "Submit"
|
|
15158
15997
|
}
|
|
15159
15998
|
) : null })
|
|
15160
15999
|
]
|
|
@@ -15203,11 +16042,13 @@ exports.DynamicFormV2 = DynamicFormV2;
|
|
|
15203
16042
|
exports.EMAIL_REGEX = EMAIL_REGEX;
|
|
15204
16043
|
exports.FieldRenderer = FieldRenderer;
|
|
15205
16044
|
exports.FormControls = FormControls;
|
|
16045
|
+
exports.FormFileUploadWidget = FormFileUploadWidget;
|
|
15206
16046
|
exports.FormProvider = FormProvider;
|
|
15207
16047
|
exports.FormStore = FormStore;
|
|
15208
16048
|
exports.ImageUploadWidget = ImageUploadWidget;
|
|
15209
16049
|
exports.MAR_MEDICATION_ORDERS_META_KEY = MAR_MEDICATION_ORDERS_META_KEY;
|
|
15210
16050
|
exports.MediaUploadWidget = MediaUploadWidget;
|
|
16051
|
+
exports.MultiStepForm = MultiStepForm;
|
|
15211
16052
|
exports.ReadOnlyForm = ReadOnlyForm;
|
|
15212
16053
|
exports.ReadOnlyImageUpload = ReadOnlyImageUpload;
|
|
15213
16054
|
exports.ReadOnlyMediaUpload = ReadOnlyMediaUpload;
|
|
@@ -15217,6 +16058,7 @@ exports.RichTextWidget = RichTextWidget;
|
|
|
15217
16058
|
exports.SignatureUploadWidget = SignatureUploadWidget;
|
|
15218
16059
|
exports.SmartForm = SmartForm;
|
|
15219
16060
|
exports.SmartTextareaWidget = SmartTextareaWidget;
|
|
16061
|
+
exports.Upload = Upload3;
|
|
15220
16062
|
exports.createUploadHandler = createUploadHandler;
|
|
15221
16063
|
exports.evaluateRules = evaluateRules;
|
|
15222
16064
|
exports.fieldMetaRequestsMarMedicationOrdersButton = fieldMetaRequestsMarMedicationOrdersButton;
|