@nocios/crudify-ui 1.3.4 → 2.0.0

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.mjs CHANGED
@@ -2027,19 +2027,631 @@ var UserProfileDisplay = ({
2027
2027
  };
2028
2028
  var UserProfileDisplay_default = UserProfileDisplay;
2029
2029
 
2030
+ // src/components/PublicPolicies/Policies.tsx
2031
+ import { useRef as useRef4 } from "react";
2032
+ import { useTranslation as useTranslation4 } from "react-i18next";
2033
+ import {
2034
+ Box as Box10,
2035
+ Typography as Typography10,
2036
+ Button as Button7,
2037
+ Stack as Stack3,
2038
+ Alert as Alert7,
2039
+ Divider as Divider3
2040
+ } from "@mui/material";
2041
+ import { Add } from "@mui/icons-material";
2042
+
2043
+ // src/components/PublicPolicies/PolicyItem/PolicyItem.tsx
2044
+ import { forwardRef } from "react";
2045
+ import { useTranslation as useTranslation3 } from "react-i18next";
2046
+ import {
2047
+ Box as Box9,
2048
+ FormControl,
2049
+ InputLabel,
2050
+ Select,
2051
+ MenuItem,
2052
+ IconButton as IconButton2,
2053
+ Typography as Typography9,
2054
+ FormHelperText as FormHelperText2,
2055
+ Stack as Stack2,
2056
+ Paper,
2057
+ Divider as Divider2,
2058
+ Button as Button6
2059
+ } from "@mui/material";
2060
+ import { Delete, SelectAll as SelectAll2, ClearAll as ClearAll2 } from "@mui/icons-material";
2061
+
2062
+ // src/components/PublicPolicies/FieldSelector/FieldSelector.tsx
2063
+ import { useState as useState8, useEffect as useEffect8, useRef as useRef3 } from "react";
2064
+ import { useTranslation as useTranslation2 } from "react-i18next";
2065
+ import {
2066
+ Box as Box8,
2067
+ Typography as Typography8,
2068
+ Button as Button5,
2069
+ Stack,
2070
+ FormHelperText,
2071
+ ToggleButton,
2072
+ ToggleButtonGroup
2073
+ } from "@mui/material";
2074
+ import {
2075
+ CheckCircle,
2076
+ Cancel,
2077
+ SelectAll,
2078
+ ClearAll
2079
+ } from "@mui/icons-material";
2080
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
2081
+ var FieldSelector = ({
2082
+ value,
2083
+ onChange,
2084
+ availableFields,
2085
+ error,
2086
+ disabled = false
2087
+ }) => {
2088
+ const { t } = useTranslation2();
2089
+ const [mode, setMode] = useState8("custom");
2090
+ const isUpdatingRef = useRef3(false);
2091
+ useEffect8(() => {
2092
+ const current = value || { allow: [], owner_allow: [], deny: [] };
2093
+ const all = new Set(availableFields);
2094
+ const allow = (current.allow || []).filter((f) => all.has(f));
2095
+ const owner = (current.owner_allow || []).filter((f) => all.has(f));
2096
+ const deny = (current.deny || []).filter((f) => all.has(f));
2097
+ availableFields.forEach((f) => {
2098
+ if (!allow.includes(f) && !owner.includes(f) && !deny.includes(f)) deny.push(f);
2099
+ });
2100
+ const normalized = { allow, owner_allow: owner, deny };
2101
+ if (JSON.stringify(normalized) !== JSON.stringify(current)) {
2102
+ onChange(normalized);
2103
+ }
2104
+ if (allow.length === availableFields.length) setMode("all");
2105
+ else if (deny.length === availableFields.length) setMode("none");
2106
+ else setMode("custom");
2107
+ }, [availableFields, value]);
2108
+ const setAllAllow = () => {
2109
+ isUpdatingRef.current = true;
2110
+ onChange({ allow: [...availableFields], owner_allow: [], deny: [] });
2111
+ setMode("all");
2112
+ setTimeout(() => {
2113
+ isUpdatingRef.current = false;
2114
+ }, 0);
2115
+ };
2116
+ const setAllDeny = () => {
2117
+ isUpdatingRef.current = true;
2118
+ onChange({ allow: [], owner_allow: [], deny: [...availableFields] });
2119
+ setMode("none");
2120
+ setTimeout(() => {
2121
+ isUpdatingRef.current = false;
2122
+ }, 0);
2123
+ };
2124
+ const getFieldState = (fieldName) => {
2125
+ if (value?.allow?.includes(fieldName)) return "allow";
2126
+ if (value?.owner_allow?.includes(fieldName)) return "owner_allow";
2127
+ return "deny";
2128
+ };
2129
+ const setFieldState = (fieldName, state) => {
2130
+ isUpdatingRef.current = true;
2131
+ const allow = new Set(value?.allow || []);
2132
+ const owner = new Set(value?.owner_allow || []);
2133
+ const deny = new Set(value?.deny || []);
2134
+ allow.delete(fieldName);
2135
+ owner.delete(fieldName);
2136
+ deny.delete(fieldName);
2137
+ if (state === "allow") allow.add(fieldName);
2138
+ if (state === "owner_allow") owner.add(fieldName);
2139
+ if (state === "deny") deny.add(fieldName);
2140
+ onChange({ allow: Array.from(allow), owner_allow: Array.from(owner), deny: Array.from(deny) });
2141
+ setMode("custom");
2142
+ setTimeout(() => {
2143
+ isUpdatingRef.current = false;
2144
+ }, 0);
2145
+ };
2146
+ if (availableFields.length === 0) {
2147
+ return /* @__PURE__ */ jsxs8(Box8, { children: [
2148
+ /* @__PURE__ */ jsx11(Typography8, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: t("modules.form.publicPolicies.fields.conditions.label") }),
2149
+ /* @__PURE__ */ jsx11(Typography8, { variant: "body2", color: "text.secondary", sx: { fontStyle: "italic" }, children: t("modules.form.publicPolicies.fields.conditions.noFieldsAvailable") }),
2150
+ error && /* @__PURE__ */ jsx11(FormHelperText, { error: true, sx: { mt: 1 }, children: error })
2151
+ ] });
2152
+ }
2153
+ return /* @__PURE__ */ jsxs8(Box8, { children: [
2154
+ /* @__PURE__ */ jsx11(Typography8, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: t("modules.form.publicPolicies.fields.conditions.label") }),
2155
+ /* @__PURE__ */ jsxs8(Stack, { direction: "row", spacing: 1, sx: { mb: 3 }, children: [
2156
+ /* @__PURE__ */ jsx11(
2157
+ Button5,
2158
+ {
2159
+ variant: mode === "all" ? "contained" : "outlined",
2160
+ startIcon: /* @__PURE__ */ jsx11(SelectAll, {}),
2161
+ onClick: setAllAllow,
2162
+ disabled,
2163
+ size: "small",
2164
+ sx: {
2165
+ minWidth: 120,
2166
+ ...mode === "all" && {
2167
+ backgroundColor: "#16a34a",
2168
+ "&:hover": { backgroundColor: "#15803d" }
2169
+ }
2170
+ },
2171
+ children: t("modules.form.publicPolicies.fields.conditions.allFields")
2172
+ }
2173
+ ),
2174
+ /* @__PURE__ */ jsx11(
2175
+ Button5,
2176
+ {
2177
+ variant: mode === "none" ? "contained" : "outlined",
2178
+ startIcon: /* @__PURE__ */ jsx11(ClearAll, {}),
2179
+ onClick: setAllDeny,
2180
+ disabled,
2181
+ size: "small",
2182
+ sx: {
2183
+ minWidth: 120,
2184
+ ...mode === "none" && {
2185
+ backgroundColor: "#cf222e",
2186
+ "&:hover": { backgroundColor: "#bc1f2c" }
2187
+ }
2188
+ },
2189
+ children: t("modules.form.publicPolicies.fields.conditions.noFields")
2190
+ }
2191
+ )
2192
+ ] }),
2193
+ /* @__PURE__ */ jsxs8(Box8, { sx: { p: 2, border: "1px solid #d1d9e0", borderRadius: 1, backgroundColor: "#f6f8fa" }, children: [
2194
+ /* @__PURE__ */ jsx11(Typography8, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: t("modules.form.publicPolicies.fields.conditions.help") }),
2195
+ /* @__PURE__ */ jsx11(Stack, { spacing: 1, children: availableFields.map((fieldName) => {
2196
+ const fieldState = getFieldState(fieldName);
2197
+ return /* @__PURE__ */ jsxs8(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [
2198
+ /* @__PURE__ */ jsx11(Typography8, { variant: "body2", sx: { minWidth: 100, fontFamily: "monospace" }, children: fieldName }),
2199
+ /* @__PURE__ */ jsxs8(ToggleButtonGroup, { value: fieldState, exclusive: true, size: "small", children: [
2200
+ /* @__PURE__ */ jsxs8(
2201
+ ToggleButton,
2202
+ {
2203
+ value: "allow",
2204
+ onClick: () => setFieldState(fieldName, "allow"),
2205
+ disabled,
2206
+ sx: {
2207
+ px: 2,
2208
+ color: fieldState === "allow" ? "#ffffff" : "#6b7280",
2209
+ backgroundColor: fieldState === "allow" ? "#16a34a" : "#f3f4f6",
2210
+ borderColor: fieldState === "allow" ? "#16a34a" : "#d1d5db",
2211
+ "&:hover": {
2212
+ backgroundColor: fieldState === "allow" ? "#15803d" : "#e5e7eb",
2213
+ borderColor: fieldState === "allow" ? "#15803d" : "#9ca3af"
2214
+ },
2215
+ "&.Mui-selected": {
2216
+ backgroundColor: "#16a34a",
2217
+ color: "#ffffff",
2218
+ "&:hover": {
2219
+ backgroundColor: "#15803d"
2220
+ }
2221
+ }
2222
+ },
2223
+ children: [
2224
+ /* @__PURE__ */ jsx11(CheckCircle, { sx: { fontSize: 16, mr: 0.5 } }),
2225
+ t("modules.form.publicPolicies.fields.conditions.states.allow")
2226
+ ]
2227
+ }
2228
+ ),
2229
+ /* @__PURE__ */ jsx11(
2230
+ ToggleButton,
2231
+ {
2232
+ value: "owner_allow",
2233
+ onClick: () => setFieldState(fieldName, "owner_allow"),
2234
+ disabled,
2235
+ sx: {
2236
+ px: 2,
2237
+ color: fieldState === "owner_allow" ? "#ffffff" : "#6b7280",
2238
+ backgroundColor: fieldState === "owner_allow" ? "#0ea5e9" : "#f3f4f6",
2239
+ borderColor: fieldState === "owner_allow" ? "#0ea5e9" : "#d1d5db",
2240
+ "&:hover": {
2241
+ backgroundColor: fieldState === "owner_allow" ? "#0284c7" : "#e5e7eb",
2242
+ borderColor: fieldState === "owner_allow" ? "#0284c7" : "#9ca3af"
2243
+ },
2244
+ "&.Mui-selected": {
2245
+ backgroundColor: "#0ea5e9",
2246
+ color: "#ffffff",
2247
+ "&:hover": {
2248
+ backgroundColor: "#0284c7"
2249
+ }
2250
+ }
2251
+ },
2252
+ children: t("modules.form.publicPolicies.fields.conditions.states.ownerAllow")
2253
+ }
2254
+ ),
2255
+ /* @__PURE__ */ jsxs8(
2256
+ ToggleButton,
2257
+ {
2258
+ value: "deny",
2259
+ onClick: () => setFieldState(fieldName, "deny"),
2260
+ disabled,
2261
+ sx: {
2262
+ px: 2,
2263
+ color: fieldState === "deny" ? "#ffffff" : "#6b7280",
2264
+ backgroundColor: fieldState === "deny" ? "#dc2626" : "#f3f4f6",
2265
+ borderColor: fieldState === "deny" ? "#dc2626" : "#d1d5db",
2266
+ "&:hover": {
2267
+ backgroundColor: fieldState === "deny" ? "#b91c1c" : "#e5e7eb",
2268
+ borderColor: fieldState === "deny" ? "#b91c1c" : "#9ca3af"
2269
+ },
2270
+ "&.Mui-selected": {
2271
+ backgroundColor: "#dc2626",
2272
+ color: "#ffffff",
2273
+ "&:hover": {
2274
+ backgroundColor: "#b91c1c"
2275
+ }
2276
+ }
2277
+ },
2278
+ children: [
2279
+ /* @__PURE__ */ jsx11(Cancel, { sx: { fontSize: 16, mr: 0.5 } }),
2280
+ t("modules.form.publicPolicies.fields.conditions.states.deny")
2281
+ ]
2282
+ }
2283
+ )
2284
+ ] })
2285
+ ] }, fieldName);
2286
+ }) })
2287
+ ] }),
2288
+ error && /* @__PURE__ */ jsx11(FormHelperText, { error: true, sx: { mt: 1 }, children: error })
2289
+ ] });
2290
+ };
2291
+ var FieldSelector_default = FieldSelector;
2292
+
2293
+ // src/components/PublicPolicies/constants.ts
2294
+ var POLICY_ACTIONS = ["create", "read", "update", "delete"];
2295
+ var PREFERRED_POLICY_ORDER = [
2296
+ "create",
2297
+ "read",
2298
+ "update",
2299
+ "delete"
2300
+ ];
2301
+
2302
+ // src/components/PublicPolicies/PolicyItem/PolicyItem.tsx
2303
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
2304
+ var PolicyItem = forwardRef(({
2305
+ policy,
2306
+ onChange,
2307
+ onRemove,
2308
+ availableFields,
2309
+ isSubmitting = false,
2310
+ usedActions,
2311
+ error
2312
+ }, ref) => {
2313
+ const { t } = useTranslation3();
2314
+ const takenActions = new Set(Array.from(usedActions || []));
2315
+ takenActions.delete(policy.action);
2316
+ const actionOptions = POLICY_ACTIONS.map((a) => ({
2317
+ value: a,
2318
+ label: t(`modules.form.publicPolicies.fields.action.options.${a}`)
2319
+ }));
2320
+ return /* @__PURE__ */ jsxs9(
2321
+ Paper,
2322
+ {
2323
+ ref,
2324
+ sx: {
2325
+ p: 3,
2326
+ border: "1px solid #d1d9e0",
2327
+ borderRadius: 2,
2328
+ position: "relative",
2329
+ backgroundColor: "#ffffff"
2330
+ },
2331
+ children: [
2332
+ /* @__PURE__ */ jsxs9(Box9, { sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", mb: 3 }, children: [
2333
+ /* @__PURE__ */ jsx12(
2334
+ Typography9,
2335
+ {
2336
+ variant: "subtitle1",
2337
+ sx: {
2338
+ fontWeight: 600,
2339
+ color: "#111418",
2340
+ fontSize: "1rem"
2341
+ },
2342
+ children: t("modules.form.publicPolicies.policyTitle")
2343
+ }
2344
+ ),
2345
+ /* @__PURE__ */ jsx12(
2346
+ IconButton2,
2347
+ {
2348
+ onClick: onRemove,
2349
+ size: "small",
2350
+ disabled: isSubmitting,
2351
+ "aria-label": t("modules.form.publicPolicies.removePolicy"),
2352
+ sx: {
2353
+ color: "#656d76",
2354
+ "&:hover": {
2355
+ color: "#cf222e",
2356
+ backgroundColor: "rgba(207, 34, 46, 0.1)"
2357
+ }
2358
+ },
2359
+ children: /* @__PURE__ */ jsx12(Delete, {})
2360
+ }
2361
+ )
2362
+ ] }),
2363
+ /* @__PURE__ */ jsxs9(Stack2, { spacing: 3, children: [
2364
+ /* @__PURE__ */ jsx12(Stack2, { direction: { xs: "column", md: "row" }, spacing: 2, children: /* @__PURE__ */ jsx12(Box9, { sx: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxs9(FormControl, { fullWidth: true, children: [
2365
+ /* @__PURE__ */ jsx12(InputLabel, { children: t("modules.form.publicPolicies.fields.action.label") }),
2366
+ /* @__PURE__ */ jsx12(
2367
+ Select,
2368
+ {
2369
+ value: policy.action,
2370
+ label: t("modules.form.publicPolicies.fields.action.label"),
2371
+ disabled: isSubmitting,
2372
+ onChange: (e) => {
2373
+ const newAction = e.target.value;
2374
+ const next = { ...policy, action: newAction };
2375
+ if (newAction === "delete") {
2376
+ next.permission = "deny";
2377
+ delete next.fields;
2378
+ } else {
2379
+ next.fields = { allow: [], owner_allow: [], deny: availableFields };
2380
+ delete next.permission;
2381
+ }
2382
+ onChange(next);
2383
+ },
2384
+ sx: {
2385
+ backgroundColor: "#ffffff",
2386
+ "&:hover .MuiOutlinedInput-notchedOutline": {
2387
+ borderColor: "#8c959f"
2388
+ },
2389
+ "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
2390
+ borderColor: "#0969da",
2391
+ borderWidth: 2
2392
+ }
2393
+ },
2394
+ children: actionOptions.map((option) => {
2395
+ const disabledOption = takenActions.has(option.value);
2396
+ return /* @__PURE__ */ jsx12(MenuItem, { value: option.value, disabled: disabledOption, children: option.label }, option.value);
2397
+ })
2398
+ }
2399
+ ),
2400
+ error && /* @__PURE__ */ jsx12(FormHelperText2, { error: true, children: error })
2401
+ ] }) }) }),
2402
+ policy.action === "delete" ? /* @__PURE__ */ jsxs9(Box9, { children: [
2403
+ /* @__PURE__ */ jsx12(Typography9, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: t("modules.form.publicPolicies.fields.conditions.label") }),
2404
+ /* @__PURE__ */ jsxs9(Stack2, { direction: "row", spacing: 1, sx: { mb: 3 }, children: [
2405
+ /* @__PURE__ */ jsx12(
2406
+ Button6,
2407
+ {
2408
+ variant: policy.permission === "*" ? "contained" : "outlined",
2409
+ startIcon: /* @__PURE__ */ jsx12(SelectAll2, {}),
2410
+ onClick: () => onChange({ ...policy, permission: "*" }),
2411
+ disabled: isSubmitting,
2412
+ size: "small",
2413
+ sx: {
2414
+ minWidth: 140,
2415
+ whiteSpace: "nowrap",
2416
+ ...policy.permission === "*" && {
2417
+ backgroundColor: "#16a34a",
2418
+ "&:hover": { backgroundColor: "#15803d" }
2419
+ }
2420
+ },
2421
+ children: t("modules.form.publicPolicies.fields.conditions.allFields")
2422
+ }
2423
+ ),
2424
+ /* @__PURE__ */ jsx12(
2425
+ Button6,
2426
+ {
2427
+ variant: policy.permission === "owner" ? "contained" : "outlined",
2428
+ onClick: () => onChange({ ...policy, permission: "owner" }),
2429
+ disabled: isSubmitting,
2430
+ size: "small",
2431
+ sx: {
2432
+ minWidth: 140,
2433
+ whiteSpace: "nowrap",
2434
+ ...policy.permission === "owner" && {
2435
+ backgroundColor: "#0ea5e9",
2436
+ "&:hover": { backgroundColor: "#0284c7" }
2437
+ }
2438
+ },
2439
+ children: t("modules.form.publicPolicies.fields.conditions.states.ownerAllow")
2440
+ }
2441
+ ),
2442
+ /* @__PURE__ */ jsx12(
2443
+ Button6,
2444
+ {
2445
+ variant: policy.permission === "deny" ? "contained" : "outlined",
2446
+ startIcon: /* @__PURE__ */ jsx12(ClearAll2, {}),
2447
+ onClick: () => onChange({ ...policy, permission: "deny" }),
2448
+ disabled: isSubmitting,
2449
+ size: "small",
2450
+ sx: {
2451
+ minWidth: 140,
2452
+ whiteSpace: "nowrap",
2453
+ ...policy.permission === "deny" && {
2454
+ backgroundColor: "#cf222e",
2455
+ "&:hover": { backgroundColor: "#bc1f2c" }
2456
+ }
2457
+ },
2458
+ children: t("modules.form.publicPolicies.fields.conditions.noFields")
2459
+ }
2460
+ )
2461
+ ] })
2462
+ ] }) : /* @__PURE__ */ jsx12(
2463
+ FieldSelector_default,
2464
+ {
2465
+ value: policy.fields || { allow: [], owner_allow: [], deny: [] },
2466
+ onChange: (nextFields) => onChange({ ...policy, fields: nextFields }),
2467
+ availableFields,
2468
+ disabled: isSubmitting
2469
+ }
2470
+ ),
2471
+ /* @__PURE__ */ jsx12(Paper, { variant: "outlined", sx: { p: 2, backgroundColor: "#f9fafb" }, children: policy.action === "delete" ? /* @__PURE__ */ jsxs9(Typography9, { variant: "body2", sx: { fontFamily: "monospace", color: "text.secondary" }, children: [
2472
+ /* @__PURE__ */ jsxs9(Box9, { component: "span", sx: {
2473
+ color: policy.permission === "*" ? "#16a34a" : policy.permission === "owner" ? "#0ea5e9" : "#dc2626"
2474
+ }, children: [
2475
+ t("modules.form.publicPolicies.fields.conditions.states.allow"),
2476
+ ":"
2477
+ ] }),
2478
+ " ",
2479
+ policy.permission || "-"
2480
+ ] }) : /* @__PURE__ */ jsxs9(Stack2, { spacing: 0.5, divider: /* @__PURE__ */ jsx12(Divider2, { sx: { borderColor: "#e5e7eb" } }), children: [
2481
+ /* @__PURE__ */ jsxs9(Typography9, { variant: "body2", sx: { fontFamily: "monospace", color: "text.secondary" }, children: [
2482
+ /* @__PURE__ */ jsxs9(Box9, { component: "span", sx: { color: "#16a34a" }, children: [
2483
+ t("modules.form.publicPolicies.fields.conditions.states.allow"),
2484
+ ":"
2485
+ ] }),
2486
+ " ",
2487
+ (policy?.fields?.allow || []).join(", ") || "-"
2488
+ ] }),
2489
+ /* @__PURE__ */ jsxs9(Typography9, { variant: "body2", sx: { fontFamily: "monospace", color: "text.secondary" }, children: [
2490
+ /* @__PURE__ */ jsxs9(Box9, { component: "span", sx: { color: "#0ea5e9" }, children: [
2491
+ t("modules.form.publicPolicies.fields.conditions.states.ownerAllow"),
2492
+ ":"
2493
+ ] }),
2494
+ " ",
2495
+ (policy?.fields?.owner_allow || []).join(", ") || "-"
2496
+ ] }),
2497
+ /* @__PURE__ */ jsxs9(Typography9, { variant: "body2", sx: { fontFamily: "monospace", color: "text.secondary" }, children: [
2498
+ /* @__PURE__ */ jsxs9(Box9, { component: "span", sx: { color: "#dc2626" }, children: [
2499
+ t("modules.form.publicPolicies.fields.conditions.states.deny"),
2500
+ ":"
2501
+ ] }),
2502
+ " ",
2503
+ (policy?.fields?.deny || []).join(", ") || "-"
2504
+ ] })
2505
+ ] }) })
2506
+ ] })
2507
+ ]
2508
+ }
2509
+ );
2510
+ });
2511
+ var PolicyItem_default = PolicyItem;
2512
+
2513
+ // src/components/PublicPolicies/Policies.tsx
2514
+ import { Fragment as Fragment7, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
2515
+ var generateId = () => {
2516
+ const c = globalThis?.crypto;
2517
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
2518
+ return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
2519
+ };
2520
+ var Policies = ({
2521
+ policies,
2522
+ onChange,
2523
+ availableFields,
2524
+ errors,
2525
+ isSubmitting = false
2526
+ }) => {
2527
+ const { t } = useTranslation4();
2528
+ const policyRefs = useRef4({});
2529
+ const takenActions = new Set((policies || []).map((p) => p.action).filter(Boolean));
2530
+ const remainingActions = PREFERRED_POLICY_ORDER.filter((a) => !takenActions.has(a));
2531
+ const canAddPolicy = remainingActions.length > 0;
2532
+ const addPolicy = () => {
2533
+ const defaultAction = remainingActions[0] || "create";
2534
+ const newPolicy = {
2535
+ id: generateId(),
2536
+ action: defaultAction
2537
+ };
2538
+ if (defaultAction === "delete") {
2539
+ newPolicy.permission = "deny";
2540
+ } else {
2541
+ newPolicy.fields = {
2542
+ allow: [],
2543
+ owner_allow: [],
2544
+ deny: availableFields
2545
+ };
2546
+ }
2547
+ const next = [...policies || [], newPolicy];
2548
+ onChange(next);
2549
+ setTimeout(() => {
2550
+ const newIndex = next.length - 1;
2551
+ const el = policyRefs.current[newIndex];
2552
+ if (el) {
2553
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
2554
+ }
2555
+ }, 100);
2556
+ };
2557
+ const removePolicy = (index) => {
2558
+ const next = [...policies];
2559
+ next.splice(index, 1);
2560
+ onChange(next);
2561
+ };
2562
+ const arrayError = (() => {
2563
+ if (!errors) return null;
2564
+ if (typeof errors === "string") return errors;
2565
+ const msg = errors._error;
2566
+ return typeof msg === "string" ? msg : null;
2567
+ })();
2568
+ const usedActions = new Set((policies || []).map((p) => p.action));
2569
+ return /* @__PURE__ */ jsxs10(Fragment7, { children: [
2570
+ /* @__PURE__ */ jsx13(Divider3, { sx: { borderColor: "#e0e4e7" } }),
2571
+ /* @__PURE__ */ jsxs10(Box10, { children: [
2572
+ /* @__PURE__ */ jsx13(Box10, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 3, children: /* @__PURE__ */ jsxs10(Box10, { children: [
2573
+ /* @__PURE__ */ jsx13(
2574
+ Typography10,
2575
+ {
2576
+ variant: "h6",
2577
+ sx: {
2578
+ fontWeight: 600,
2579
+ color: "#111418",
2580
+ mb: 1
2581
+ },
2582
+ children: t("modules.form.publicPolicies.title")
2583
+ }
2584
+ ),
2585
+ /* @__PURE__ */ jsx13(
2586
+ Typography10,
2587
+ {
2588
+ variant: "body2",
2589
+ color: "text.secondary",
2590
+ sx: { fontSize: "0.875rem" },
2591
+ children: t("modules.form.publicPolicies.description")
2592
+ }
2593
+ )
2594
+ ] }) }),
2595
+ arrayError && /* @__PURE__ */ jsx13(Alert7, { severity: "error", sx: { mb: 3 }, children: arrayError }),
2596
+ /* @__PURE__ */ jsxs10(Stack3, { spacing: 3, children: [
2597
+ (policies || []).length === 0 ? /* @__PURE__ */ jsx13(Alert7, { severity: "info", children: t("modules.form.publicPolicies.noPolicies") }) : policies.map((policy, index) => /* @__PURE__ */ jsx13(
2598
+ PolicyItem_default,
2599
+ {
2600
+ ref: (el) => {
2601
+ policyRefs.current[index] = el;
2602
+ },
2603
+ policy,
2604
+ onChange: (nextPolicy) => {
2605
+ const next = [...policies];
2606
+ next[index] = nextPolicy;
2607
+ onChange(next);
2608
+ },
2609
+ onRemove: () => removePolicy(index),
2610
+ availableFields,
2611
+ isSubmitting,
2612
+ usedActions,
2613
+ error: typeof errors === "object" && errors && policy.id in errors ? errors[policy.id] : void 0
2614
+ },
2615
+ policy.id
2616
+ )),
2617
+ canAddPolicy && /* @__PURE__ */ jsx13(Box10, { children: /* @__PURE__ */ jsx13(
2618
+ Button7,
2619
+ {
2620
+ type: "button",
2621
+ variant: "outlined",
2622
+ startIcon: /* @__PURE__ */ jsx13(Add, {}),
2623
+ onClick: addPolicy,
2624
+ disabled: isSubmitting,
2625
+ sx: {
2626
+ borderColor: "#d0d7de",
2627
+ color: "#656d76",
2628
+ "&:hover": {
2629
+ borderColor: "#8c959f",
2630
+ backgroundColor: "transparent"
2631
+ }
2632
+ },
2633
+ children: t("modules.form.publicPolicies.addPolicy")
2634
+ }
2635
+ ) })
2636
+ ] })
2637
+ ] })
2638
+ ] });
2639
+ };
2640
+ var Policies_default = Policies;
2641
+
2030
2642
  // src/hooks/useCrudifyUser.ts
2031
- import { useState as useState8, useEffect as useEffect8, useCallback as useCallback2, useRef as useRef3 } from "react";
2643
+ import { useState as useState9, useEffect as useEffect9, useCallback as useCallback2, useRef as useRef5 } from "react";
2032
2644
  import crudify3 from "@nocios/crudify-browser";
2033
2645
  var useCrudifyUser = (options = {}) => {
2034
2646
  const { autoFetch = true, retryOnError = false, maxRetries = 3 } = options;
2035
2647
  const { isAuthenticated, isInitialized, user: jwtUser, token } = useCrudifyDataContext();
2036
- const [userData, setUserData] = useState8(null);
2037
- const [loading, setLoading] = useState8(false);
2038
- const [error, setError] = useState8(null);
2039
- const abortControllerRef = useRef3(null);
2040
- const mountedRef = useRef3(true);
2041
- const requestIdRef = useRef3(0);
2042
- const retryCountRef = useRef3(0);
2648
+ const [userData, setUserData] = useState9(null);
2649
+ const [loading, setLoading] = useState9(false);
2650
+ const [error, setError] = useState9(null);
2651
+ const abortControllerRef = useRef5(null);
2652
+ const mountedRef = useRef5(true);
2653
+ const requestIdRef = useRef5(0);
2654
+ const retryCountRef = useRef5(0);
2043
2655
  const getUserEmailFromJWT = useCallback2(() => {
2044
2656
  if (!jwtUser) return null;
2045
2657
  return jwtUser.email || jwtUser["cognito:username"] || null;
@@ -2182,14 +2794,14 @@ var useCrudifyUser = (options = {}) => {
2182
2794
  }
2183
2795
  }
2184
2796
  }, [isInitialized, getUserEmailFromJWT, retryOnError, maxRetries]);
2185
- useEffect8(() => {
2797
+ useEffect9(() => {
2186
2798
  if (autoFetch && isAuthenticated && isInitialized) {
2187
2799
  refreshProfile();
2188
2800
  } else if (!isAuthenticated) {
2189
2801
  clearProfile();
2190
2802
  }
2191
2803
  }, [autoFetch, isAuthenticated, isInitialized, refreshProfile, clearProfile]);
2192
- useEffect8(() => {
2804
+ useEffect9(() => {
2193
2805
  mountedRef.current = true;
2194
2806
  return () => {
2195
2807
  mountedRef.current = false;
@@ -2436,1949 +3048,1337 @@ var useCrudifyInstance = () => {
2436
3048
  console.log(" - TokenManager token:", tmToken ? `${tmToken.substring(0, 20)}...` : "null");
2437
3049
  console.log(" - crudify.getToken():", crudifyToken ? `${crudifyToken.substring(0, 20)}...` : "null");
2438
3050
  console.log(" - Tokens match:", tmToken === crudifyToken);
2439
- await ensureTokenSync();
2440
- console.log("\u{1F4E1} useCrudifyInstance - getStructure: Calling crudify.getStructure");
2441
- const response = await crudify5.getStructure(options);
2442
- console.log("\u{1F4E1} useCrudifyInstance - getStructure: Response received", response);
2443
- return response;
2444
- } catch (error) {
2445
- console.error("\u{1F4E1} useCrudifyInstance - getStructure: Error", error);
2446
- throw error;
2447
- }
2448
- }, [ensureReady, ensureTokenSync]);
2449
- const getStructurePublic = useCallback4(async (options) => {
2450
- console.log("\u{1F4E1} useCrudifyInstance - getStructurePublic: Starting");
2451
- await ensureReady("getStructurePublic");
2452
- try {
2453
- console.log("\u{1F4E1} useCrudifyInstance - getStructurePublic: Calling crudify.getStructurePublic");
2454
- const response = await crudify5.getStructurePublic(options);
2455
- console.log("\u{1F4E1} useCrudifyInstance - getStructurePublic: Response received", response);
2456
- return response;
2457
- } catch (error) {
2458
- console.error("\u{1F4E1} useCrudifyInstance - getStructurePublic: Error", error);
2459
- throw error;
2460
- }
2461
- }, [ensureReady]);
2462
- const readItems = useCallback4(async (moduleKey, filter, options) => {
2463
- await ensureReady("readItems");
2464
- await ensureTokenSync();
2465
- if (moduleKey === "__test_connection__") {
2466
- console.error("\u{1F6A8} FOUND TEST CONNECTION CALL in useCrudifyInstance! Stack trace:");
2467
- console.error(new Error().stack);
2468
- }
2469
- try {
2470
- const response = await crudify5.readItems(moduleKey, filter || {}, options);
2471
- return response;
2472
- } catch (error) {
2473
- console.error("\u{1F4CA} useCrudifyInstance - readItems error:", error);
2474
- throw error;
2475
- }
2476
- }, [ensureReady, ensureTokenSync]);
2477
- const readItem = useCallback4(async (moduleKey, filter, options) => {
2478
- await ensureReady("readItem");
2479
- try {
2480
- const response = await crudify5.readItem(moduleKey, filter, options);
2481
- return response;
2482
- } catch (error) {
2483
- console.error("\u{1F4CA} useCrudifyInstance - readItem error:", error);
2484
- throw error;
2485
- }
2486
- }, [ensureReady]);
2487
- const createItem = useCallback4(async (moduleKey, data, options) => {
2488
- await ensureReady("createItem");
2489
- try {
2490
- const response = await crudify5.createItem(moduleKey, data, options);
2491
- return response;
2492
- } catch (error) {
2493
- console.error("\u{1F4CA} useCrudifyInstance - createItem error:", error);
2494
- throw error;
2495
- }
2496
- }, [ensureReady]);
2497
- const updateItem = useCallback4(async (moduleKey, data, options) => {
2498
- await ensureReady("updateItem");
2499
- try {
2500
- const response = await crudify5.updateItem(moduleKey, data, options);
2501
- return response;
2502
- } catch (error) {
2503
- console.error("\u{1F4CA} useCrudifyInstance - updateItem error:", error);
2504
- throw error;
2505
- }
2506
- }, [ensureReady]);
2507
- const deleteItem = useCallback4(async (moduleKey, id, options) => {
2508
- await ensureReady("deleteItem");
2509
- try {
2510
- const response = await crudify5.deleteItem(moduleKey, id, options);
2511
- return response;
2512
- } catch (error) {
2513
- console.error("\u{1F4CA} useCrudifyInstance - deleteItem error:", error);
2514
- throw error;
2515
- }
2516
- }, [ensureReady]);
2517
- const transaction = useCallback4(async (operations, options) => {
2518
- await ensureReady("transaction");
2519
- try {
2520
- const response = await crudify5.transaction(operations, options);
2521
- return response;
2522
- } catch (error) {
2523
- console.error("\u{1F4CA} useCrudifyInstance - transaction error:", error);
2524
- throw error;
2525
- }
2526
- }, [ensureReady]);
2527
- const login = useCallback4(async (email, password) => {
2528
- await ensureReady("login");
2529
- try {
2530
- const response = await crudify5.login(email, password);
2531
- return response;
2532
- } catch (error) {
2533
- console.error("\u{1F4CA} useCrudifyInstance - login error:", error);
2534
- throw error;
2535
- }
2536
- }, [ensureReady]);
2537
- return {
2538
- // Core operations
2539
- getStructure,
2540
- getStructurePublic,
2541
- readItems,
2542
- readItem,
2543
- createItem,
2544
- updateItem,
2545
- deleteItem,
2546
- transaction,
2547
- login,
2548
- // State information
2549
- isReady,
2550
- isInitialized,
2551
- isInitializing,
2552
- initializationError,
2553
- // Utility methods
2554
- waitForReady
2555
- };
2556
- };
2557
- var getCrudifyInstanceAsync = async () => {
2558
- console.log("\u{1F504} getCrudifyInstanceAsync - Starting");
2559
- const { crudifyInitializer: crudifyInitializer2 } = await import("./CrudifyDataProvider-5GXGNQKI.mjs");
2560
- console.log("\u{1F504} getCrudifyInstanceAsync - Checking if ready");
2561
- console.log(" - crudifyInitializer.isReady():", crudifyInitializer2.isReady());
2562
- console.log(" - crudifyInitializer.getStatus():", crudifyInitializer2.getStatus());
2563
- if (!crudifyInitializer2.isReady()) {
2564
- console.log("\u{1F504} getCrudifyInstanceAsync - Waiting for crudify initialization...");
2565
- await crudifyInitializer2.waitForInitialization();
2566
- if (!crudifyInitializer2.isReady()) {
2567
- const error = crudifyInitializer2.getError();
2568
- throw new Error(`Crudify initialization failed: ${error || "Unknown error after waiting"}`);
2569
- }
2570
- console.log("\u2705 getCrudifyInstanceAsync - Crudify is ready after waiting");
2571
- } else {
2572
- console.log("\u2705 getCrudifyInstanceAsync - Already ready, no wait needed");
2573
- }
2574
- console.log("\u2705 getCrudifyInstanceAsync - Returning crudify instance");
2575
- return crudify5;
2576
- };
2577
- var getCrudifyInstanceSync = async () => {
2578
- const { crudifyInitializer: crudifyInitializer2 } = await import("./CrudifyDataProvider-5GXGNQKI.mjs");
2579
- if (!crudifyInitializer2.isReady()) {
2580
- throw new Error("Crudify not ready. Use getCrudifyInstanceAsync() or call this from within a React component using useCrudifyInstance()");
2581
- }
2582
- return crudify5;
2583
- };
2584
-
2585
- // src/core/SessionManager.ts
2586
- import crudify6 from "@nocios/crudify-browser";
2587
-
2588
- // src/utils/tokenStorage.ts
2589
- import CryptoJS from "crypto-js";
2590
- var _TokenStorage = class _TokenStorage {
2591
- /**
2592
- * Configurar tipo de almacenamiento
2593
- */
2594
- static setStorageType(type) {
2595
- _TokenStorage.storageType = type;
2596
- }
2597
- /**
2598
- * Verificar si el storage está disponible
2599
- */
2600
- static isStorageAvailable(type) {
2601
- try {
2602
- const storage = window[type];
2603
- const testKey = "__storage_test__";
2604
- storage.setItem(testKey, "test");
2605
- storage.removeItem(testKey);
2606
- return true;
2607
- } catch {
2608
- return false;
2609
- }
2610
- }
2611
- /**
2612
- * Obtener instancia de storage
2613
- */
2614
- static getStorage() {
2615
- if (_TokenStorage.storageType === "none") return null;
2616
- if (!_TokenStorage.isStorageAvailable(_TokenStorage.storageType)) {
2617
- console.warn(`Crudify: ${_TokenStorage.storageType} not available, tokens won't persist`);
2618
- return null;
2619
- }
2620
- return window[_TokenStorage.storageType];
2621
- }
2622
- /**
2623
- * Encriptar datos sensibles
2624
- */
2625
- static encrypt(data) {
2626
- try {
2627
- return CryptoJS.AES.encrypt(data, _TokenStorage.ENCRYPTION_KEY).toString();
2628
- } catch (error) {
2629
- console.error("Crudify: Encryption failed", error);
2630
- return data;
2631
- }
2632
- }
2633
- /**
2634
- * Desencriptar datos
2635
- */
2636
- static decrypt(encryptedData) {
2637
- try {
2638
- const bytes = CryptoJS.AES.decrypt(encryptedData, _TokenStorage.ENCRYPTION_KEY);
2639
- const decrypted = bytes.toString(CryptoJS.enc.Utf8);
2640
- return decrypted || encryptedData;
2641
- } catch (error) {
2642
- console.error("Crudify: Decryption failed", error);
2643
- return encryptedData;
2644
- }
2645
- }
2646
- /**
2647
- * Guardar tokens de forma segura
2648
- */
2649
- static saveTokens(tokens) {
2650
- const storage = _TokenStorage.getStorage();
2651
- if (!storage) return;
2652
- try {
2653
- const tokenData = {
2654
- accessToken: tokens.accessToken,
2655
- refreshToken: tokens.refreshToken,
2656
- expiresAt: tokens.expiresAt,
2657
- refreshExpiresAt: tokens.refreshExpiresAt,
2658
- savedAt: Date.now()
2659
- };
2660
- const encrypted = _TokenStorage.encrypt(JSON.stringify(tokenData));
2661
- storage.setItem(_TokenStorage.TOKEN_KEY, encrypted);
2662
- console.debug("Crudify: Tokens saved successfully");
2663
- } catch (error) {
2664
- console.error("Crudify: Failed to save tokens", error);
2665
- }
2666
- }
2667
- /**
2668
- * Obtener tokens guardados
2669
- */
2670
- static getTokens() {
2671
- const storage = _TokenStorage.getStorage();
2672
- if (!storage) return null;
2673
- try {
2674
- const encrypted = storage.getItem(_TokenStorage.TOKEN_KEY);
2675
- if (!encrypted) return null;
2676
- const decrypted = _TokenStorage.decrypt(encrypted);
2677
- const tokenData = JSON.parse(decrypted);
2678
- if (!tokenData.accessToken || !tokenData.refreshToken || !tokenData.expiresAt || !tokenData.refreshExpiresAt) {
2679
- console.warn("Crudify: Incomplete token data found, clearing storage");
2680
- _TokenStorage.clearTokens();
2681
- return null;
2682
- }
2683
- if (Date.now() >= tokenData.refreshExpiresAt) {
2684
- console.info("Crudify: Refresh token expired, clearing storage");
2685
- _TokenStorage.clearTokens();
2686
- return null;
2687
- }
2688
- return {
2689
- accessToken: tokenData.accessToken,
2690
- refreshToken: tokenData.refreshToken,
2691
- expiresAt: tokenData.expiresAt,
2692
- refreshExpiresAt: tokenData.refreshExpiresAt
2693
- };
3051
+ await ensureTokenSync();
3052
+ console.log("\u{1F4E1} useCrudifyInstance - getStructure: Calling crudify.getStructure");
3053
+ const response = await crudify5.getStructure(options);
3054
+ console.log("\u{1F4E1} useCrudifyInstance - getStructure: Response received", response);
3055
+ return response;
2694
3056
  } catch (error) {
2695
- console.error("Crudify: Failed to retrieve tokens", error);
2696
- _TokenStorage.clearTokens();
2697
- return null;
3057
+ console.error("\u{1F4E1} useCrudifyInstance - getStructure: Error", error);
3058
+ throw error;
2698
3059
  }
2699
- }
2700
- /**
2701
- * Limpiar tokens almacenados
2702
- */
2703
- static clearTokens() {
2704
- const storage = _TokenStorage.getStorage();
2705
- if (!storage) return;
3060
+ }, [ensureReady, ensureTokenSync]);
3061
+ const getStructurePublic = useCallback4(async (options) => {
3062
+ console.log("\u{1F4E1} useCrudifyInstance - getStructurePublic: Starting");
3063
+ await ensureReady("getStructurePublic");
2706
3064
  try {
2707
- storage.removeItem(_TokenStorage.TOKEN_KEY);
2708
- console.debug("Crudify: Tokens cleared from storage");
3065
+ console.log("\u{1F4E1} useCrudifyInstance - getStructurePublic: Calling crudify.getStructurePublic");
3066
+ const response = await crudify5.getStructurePublic(options);
3067
+ console.log("\u{1F4E1} useCrudifyInstance - getStructurePublic: Response received", response);
3068
+ return response;
2709
3069
  } catch (error) {
2710
- console.error("Crudify: Failed to clear tokens", error);
3070
+ console.error("\u{1F4E1} useCrudifyInstance - getStructurePublic: Error", error);
3071
+ throw error;
2711
3072
  }
2712
- }
2713
- /**
2714
- * Verificar si hay tokens válidos guardados
2715
- */
2716
- static hasValidTokens() {
2717
- const tokens = _TokenStorage.getTokens();
2718
- return tokens !== null;
2719
- }
2720
- /**
2721
- * Obtener información de expiración
2722
- */
2723
- static getExpirationInfo() {
2724
- const tokens = _TokenStorage.getTokens();
2725
- if (!tokens) return null;
2726
- const now = Date.now();
2727
- return {
2728
- accessExpired: now >= tokens.expiresAt,
2729
- refreshExpired: now >= tokens.refreshExpiresAt,
2730
- accessExpiresIn: Math.max(0, tokens.expiresAt - now),
2731
- refreshExpiresIn: Math.max(0, tokens.refreshExpiresAt - now)
2732
- };
2733
- }
2734
- /**
2735
- * Actualizar solo el access token (después de refresh)
2736
- */
2737
- static updateAccessToken(newAccessToken, newExpiresAt) {
2738
- const existingTokens = _TokenStorage.getTokens();
2739
- if (!existingTokens) {
2740
- console.warn("Crudify: Cannot update access token, no existing tokens found");
2741
- return;
3073
+ }, [ensureReady]);
3074
+ const readItems = useCallback4(async (moduleKey, filter, options) => {
3075
+ await ensureReady("readItems");
3076
+ await ensureTokenSync();
3077
+ if (moduleKey === "__test_connection__") {
3078
+ console.error("\u{1F6A8} FOUND TEST CONNECTION CALL in useCrudifyInstance! Stack trace:");
3079
+ console.error(new Error().stack);
2742
3080
  }
2743
- _TokenStorage.saveTokens({
2744
- ...existingTokens,
2745
- accessToken: newAccessToken,
2746
- expiresAt: newExpiresAt
2747
- });
2748
- }
2749
- };
2750
- _TokenStorage.TOKEN_KEY = "crudify_tokens";
2751
- _TokenStorage.ENCRYPTION_KEY = "crudify_secure_key_v1";
2752
- _TokenStorage.storageType = "localStorage";
2753
- var TokenStorage = _TokenStorage;
2754
-
2755
- // src/core/SessionManager.ts
2756
- var SessionManager = class _SessionManager {
2757
- constructor() {
2758
- this.config = {};
2759
- this.initialized = false;
2760
- }
2761
- static getInstance() {
2762
- if (!_SessionManager.instance) {
2763
- _SessionManager.instance = new _SessionManager();
3081
+ try {
3082
+ const response = await crudify5.readItems(moduleKey, filter || {}, options);
3083
+ return response;
3084
+ } catch (error) {
3085
+ console.error("\u{1F4CA} useCrudifyInstance - readItems error:", error);
3086
+ throw error;
2764
3087
  }
2765
- return _SessionManager.instance;
2766
- }
2767
- /**
2768
- * Inicializar el SessionManager
2769
- */
2770
- async initialize(config = {}) {
2771
- if (this.initialized) {
2772
- console.warn("SessionManager: Already initialized");
2773
- return;
3088
+ }, [ensureReady, ensureTokenSync]);
3089
+ const readItem = useCallback4(async (moduleKey, filter, options) => {
3090
+ await ensureReady("readItem");
3091
+ try {
3092
+ const response = await crudify5.readItem(moduleKey, filter, options);
3093
+ return response;
3094
+ } catch (error) {
3095
+ console.error("\u{1F4CA} useCrudifyInstance - readItem error:", error);
3096
+ throw error;
2774
3097
  }
2775
- this.config = {
2776
- storageType: "localStorage",
2777
- autoRestore: true,
2778
- enableLogging: false,
2779
- ...config
2780
- };
2781
- TokenStorage.setStorageType(this.config.storageType || "localStorage");
2782
- if (this.config.enableLogging) {
3098
+ }, [ensureReady]);
3099
+ const createItem = useCallback4(async (moduleKey, data, options) => {
3100
+ await ensureReady("createItem");
3101
+ try {
3102
+ const response = await crudify5.createItem(moduleKey, data, options);
3103
+ return response;
3104
+ } catch (error) {
3105
+ console.error("\u{1F4CA} useCrudifyInstance - createItem error:", error);
3106
+ throw error;
2783
3107
  }
2784
- if (this.config.autoRestore) {
2785
- await this.restoreSession();
3108
+ }, [ensureReady]);
3109
+ const updateItem = useCallback4(async (moduleKey, data, options) => {
3110
+ await ensureReady("updateItem");
3111
+ try {
3112
+ const response = await crudify5.updateItem(moduleKey, data, options);
3113
+ return response;
3114
+ } catch (error) {
3115
+ console.error("\u{1F4CA} useCrudifyInstance - updateItem error:", error);
3116
+ throw error;
2786
3117
  }
2787
- this.initialized = true;
2788
- this.log("SessionManager initialized successfully");
2789
- }
2790
- /**
2791
- * Login con persistencia automática
2792
- */
2793
- async login(email, password) {
3118
+ }, [ensureReady]);
3119
+ const deleteItem = useCallback4(async (moduleKey, id, options) => {
3120
+ await ensureReady("deleteItem");
2794
3121
  try {
2795
- this.log("Attempting login...");
2796
- const response = await crudify6.login(email, password);
2797
- if (!response.success) {
2798
- this.log("Login failed:", response.errors);
2799
- return {
2800
- success: false,
2801
- error: this.formatError(response.errors)
2802
- };
2803
- }
2804
- const tokens = {
2805
- accessToken: response.data.token,
2806
- refreshToken: response.data.refreshToken,
2807
- expiresAt: response.data.expiresAt,
2808
- refreshExpiresAt: response.data.refreshExpiresAt
2809
- };
2810
- TokenStorage.saveTokens(tokens);
2811
- this.log("Login successful, tokens saved");
2812
- this.config.onLoginSuccess?.(tokens);
2813
- return {
2814
- success: true,
2815
- tokens
2816
- };
3122
+ const response = await crudify5.deleteItem(moduleKey, id, options);
3123
+ return response;
2817
3124
  } catch (error) {
2818
- this.log("Login error:", error);
2819
- return {
2820
- success: false,
2821
- error: error instanceof Error ? error.message : "Unknown error"
2822
- };
3125
+ console.error("\u{1F4CA} useCrudifyInstance - deleteItem error:", error);
3126
+ throw error;
2823
3127
  }
2824
- }
2825
- /**
2826
- * Logout con limpieza de tokens
2827
- */
2828
- async logout() {
3128
+ }, [ensureReady]);
3129
+ const transaction = useCallback4(async (operations, options) => {
3130
+ await ensureReady("transaction");
2829
3131
  try {
2830
- this.log("Logging out...");
2831
- await crudify6.logout();
2832
- TokenStorage.clearTokens();
2833
- this.log("Logout successful");
2834
- this.config.onLogout?.();
3132
+ const response = await crudify5.transaction(operations, options);
3133
+ return response;
2835
3134
  } catch (error) {
2836
- this.log("Logout error:", error);
2837
- TokenStorage.clearTokens();
3135
+ console.error("\u{1F4CA} useCrudifyInstance - transaction error:", error);
3136
+ throw error;
2838
3137
  }
2839
- }
2840
- /**
2841
- * Restaurar sesión desde storage
2842
- */
2843
- async restoreSession() {
3138
+ }, [ensureReady]);
3139
+ const login = useCallback4(async (email, password) => {
3140
+ await ensureReady("login");
2844
3141
  try {
2845
- this.log("Attempting to restore session...");
2846
- const savedTokens = TokenStorage.getTokens();
2847
- if (!savedTokens) {
2848
- this.log("No valid tokens found in storage");
2849
- return false;
2850
- }
2851
- crudify6.setTokens({
2852
- accessToken: savedTokens.accessToken,
2853
- refreshToken: savedTokens.refreshToken,
2854
- expiresAt: savedTokens.expiresAt,
2855
- refreshExpiresAt: savedTokens.refreshExpiresAt
2856
- });
2857
- this.log("Session restored successfully");
2858
- this.config.onSessionRestored?.(savedTokens);
2859
- return true;
3142
+ const response = await crudify5.login(email, password);
3143
+ return response;
2860
3144
  } catch (error) {
2861
- this.log("Session restore error:", error);
2862
- TokenStorage.clearTokens();
2863
- return false;
3145
+ console.error("\u{1F4CA} useCrudifyInstance - login error:", error);
3146
+ throw error;
3147
+ }
3148
+ }, [ensureReady]);
3149
+ return {
3150
+ // Core operations
3151
+ getStructure,
3152
+ getStructurePublic,
3153
+ readItems,
3154
+ readItem,
3155
+ createItem,
3156
+ updateItem,
3157
+ deleteItem,
3158
+ transaction,
3159
+ login,
3160
+ // State information
3161
+ isReady,
3162
+ isInitialized,
3163
+ isInitializing,
3164
+ initializationError,
3165
+ // Utility methods
3166
+ waitForReady
3167
+ };
3168
+ };
3169
+ var getCrudifyInstanceAsync = async () => {
3170
+ console.log("\u{1F504} getCrudifyInstanceAsync - Starting");
3171
+ const { crudifyInitializer: crudifyInitializer2 } = await import("./CrudifyDataProvider-5GXGNQKI.mjs");
3172
+ console.log("\u{1F504} getCrudifyInstanceAsync - Checking if ready");
3173
+ console.log(" - crudifyInitializer.isReady():", crudifyInitializer2.isReady());
3174
+ console.log(" - crudifyInitializer.getStatus():", crudifyInitializer2.getStatus());
3175
+ if (!crudifyInitializer2.isReady()) {
3176
+ console.log("\u{1F504} getCrudifyInstanceAsync - Waiting for crudify initialization...");
3177
+ await crudifyInitializer2.waitForInitialization();
3178
+ if (!crudifyInitializer2.isReady()) {
3179
+ const error = crudifyInitializer2.getError();
3180
+ throw new Error(`Crudify initialization failed: ${error || "Unknown error after waiting"}`);
2864
3181
  }
3182
+ console.log("\u2705 getCrudifyInstanceAsync - Crudify is ready after waiting");
3183
+ } else {
3184
+ console.log("\u2705 getCrudifyInstanceAsync - Already ready, no wait needed");
2865
3185
  }
2866
- /**
2867
- * Verificar si el usuario está autenticado
2868
- */
2869
- isAuthenticated() {
2870
- return crudify6.isLogin() || TokenStorage.hasValidTokens();
3186
+ console.log("\u2705 getCrudifyInstanceAsync - Returning crudify instance");
3187
+ return crudify5;
3188
+ };
3189
+ var getCrudifyInstanceSync = async () => {
3190
+ const { crudifyInitializer: crudifyInitializer2 } = await import("./CrudifyDataProvider-5GXGNQKI.mjs");
3191
+ if (!crudifyInitializer2.isReady()) {
3192
+ throw new Error("Crudify not ready. Use getCrudifyInstanceAsync() or call this from within a React component using useCrudifyInstance()");
2871
3193
  }
3194
+ return crudify5;
3195
+ };
3196
+
3197
+ // src/core/SessionManager.ts
3198
+ import crudify6 from "@nocios/crudify-browser";
3199
+
3200
+ // src/utils/tokenStorage.ts
3201
+ import CryptoJS from "crypto-js";
3202
+ var _TokenStorage = class _TokenStorage {
2872
3203
  /**
2873
- * Obtener información de tokens actuales
3204
+ * Configurar tipo de almacenamiento
2874
3205
  */
2875
- getTokenInfo() {
2876
- const crudifyTokens = crudify6.getTokenData();
2877
- const storageInfo = TokenStorage.getExpirationInfo();
2878
- return {
2879
- isLoggedIn: this.isAuthenticated(),
2880
- crudifyTokens,
2881
- storageInfo,
2882
- hasValidTokens: TokenStorage.hasValidTokens()
2883
- };
3206
+ static setStorageType(type) {
3207
+ _TokenStorage.storageType = type;
2884
3208
  }
2885
3209
  /**
2886
- * Refrescar tokens manualmente
3210
+ * Verificar si el storage está disponible
2887
3211
  */
2888
- async refreshTokens() {
3212
+ static isStorageAvailable(type) {
2889
3213
  try {
2890
- this.log("Manually refreshing tokens...");
2891
- const response = await crudify6.refreshAccessToken();
2892
- if (!response.success) {
2893
- this.log("Token refresh failed:", response.errors);
2894
- TokenStorage.clearTokens();
2895
- this.config.onSessionExpired?.();
2896
- return false;
2897
- }
2898
- const newTokens = {
2899
- accessToken: response.data.token,
2900
- refreshToken: response.data.refreshToken,
2901
- expiresAt: response.data.expiresAt,
2902
- refreshExpiresAt: response.data.refreshExpiresAt
2903
- };
2904
- TokenStorage.saveTokens(newTokens);
2905
- this.log("Tokens refreshed and saved successfully");
3214
+ const storage = window[type];
3215
+ const testKey = "__storage_test__";
3216
+ storage.setItem(testKey, "test");
3217
+ storage.removeItem(testKey);
2906
3218
  return true;
2907
- } catch (error) {
2908
- this.log("Token refresh error:", error);
2909
- TokenStorage.clearTokens();
2910
- this.config.onSessionExpired?.();
3219
+ } catch {
2911
3220
  return false;
2912
3221
  }
2913
3222
  }
2914
3223
  /**
2915
- * Configurar interceptor de respuesta para manejo automático de errores
3224
+ * Obtener instancia de storage
2916
3225
  */
2917
- setupResponseInterceptor() {
2918
- crudify6.setResponseInterceptor(async (response) => {
2919
- if (response.errors) {
2920
- const hasAuthError = response.errors.some(
2921
- (error) => error.message?.includes("Unauthorized") || error.message?.includes("Token") || error.extensions?.code === "UNAUTHENTICATED"
2922
- );
2923
- if (hasAuthError && TokenStorage.hasValidTokens()) {
2924
- this.log("Auth error detected, attempting token refresh...");
2925
- const refreshSuccess = await this.refreshTokens();
2926
- if (!refreshSuccess) {
2927
- this.log("Session expired, triggering callback");
2928
- this.config.onSessionExpired?.();
2929
- }
2930
- }
2931
- }
2932
- return response;
2933
- });
2934
- this.log("Response interceptor configured");
3226
+ static getStorage() {
3227
+ if (_TokenStorage.storageType === "none") return null;
3228
+ if (!_TokenStorage.isStorageAvailable(_TokenStorage.storageType)) {
3229
+ console.warn(`Crudify: ${_TokenStorage.storageType} not available, tokens won't persist`);
3230
+ return null;
3231
+ }
3232
+ return window[_TokenStorage.storageType];
2935
3233
  }
2936
3234
  /**
2937
- * Limpiar sesión completamente
3235
+ * Encriptar datos sensibles
2938
3236
  */
2939
- clearSession() {
2940
- TokenStorage.clearTokens();
2941
- crudify6.logout();
2942
- this.log("Session cleared completely");
2943
- }
2944
- // Métodos privados
2945
- log(message, ...args) {
2946
- if (this.config.enableLogging) {
2947
- console.log(`[SessionManager] ${message}`, ...args);
2948
- }
2949
- }
2950
- formatError(errors) {
2951
- if (!errors) return "Unknown error";
2952
- if (typeof errors === "string") return errors;
2953
- if (typeof errors === "object") {
2954
- const errorMessages = Object.values(errors).flat();
2955
- return errorMessages.join(", ");
3237
+ static encrypt(data) {
3238
+ try {
3239
+ return CryptoJS.AES.encrypt(data, _TokenStorage.ENCRYPTION_KEY).toString();
3240
+ } catch (error) {
3241
+ console.error("Crudify: Encryption failed", error);
3242
+ return data;
2956
3243
  }
2957
- return "Authentication failed";
2958
3244
  }
2959
- };
2960
-
2961
- // src/hooks/useSession.ts
2962
- import { useState as useState9, useEffect as useEffect9, useCallback as useCallback5 } from "react";
2963
- function useSession(options = {}) {
2964
- const [state, setState] = useState9({
2965
- isAuthenticated: false,
2966
- isLoading: true,
2967
- isInitialized: false,
2968
- tokens: null,
2969
- error: null
2970
- });
2971
- const sessionManager = SessionManager.getInstance();
2972
- const initialize = useCallback5(async () => {
3245
+ /**
3246
+ * Desencriptar datos
3247
+ */
3248
+ static decrypt(encryptedData) {
2973
3249
  try {
2974
- setState((prev) => ({ ...prev, isLoading: true, error: null }));
2975
- const config = {
2976
- autoRestore: options.autoRestore ?? true,
2977
- enableLogging: options.enableLogging ?? false,
2978
- onSessionExpired: () => {
2979
- setState((prev) => ({
2980
- ...prev,
2981
- isAuthenticated: false,
2982
- tokens: null,
2983
- error: "Session expired"
2984
- }));
2985
- options.onSessionExpired?.();
2986
- },
2987
- onSessionRestored: (tokens) => {
2988
- setState((prev) => ({
2989
- ...prev,
2990
- isAuthenticated: true,
2991
- tokens,
2992
- error: null
2993
- }));
2994
- options.onSessionRestored?.(tokens);
2995
- },
2996
- onLoginSuccess: (tokens) => {
2997
- setState((prev) => ({
2998
- ...prev,
2999
- isAuthenticated: true,
3000
- tokens,
3001
- error: null
3002
- }));
3003
- },
3004
- onLogout: () => {
3005
- setState((prev) => ({
3006
- ...prev,
3007
- isAuthenticated: false,
3008
- tokens: null,
3009
- error: null
3010
- }));
3011
- }
3012
- };
3013
- await sessionManager.initialize(config);
3014
- sessionManager.setupResponseInterceptor();
3015
- const isAuth = sessionManager.isAuthenticated();
3016
- const tokenInfo = sessionManager.getTokenInfo();
3017
- setState((prev) => ({
3018
- ...prev,
3019
- isAuthenticated: isAuth,
3020
- isInitialized: true,
3021
- isLoading: false,
3022
- tokens: tokenInfo.crudifyTokens.accessToken ? {
3023
- accessToken: tokenInfo.crudifyTokens.accessToken,
3024
- refreshToken: tokenInfo.crudifyTokens.refreshToken,
3025
- expiresAt: tokenInfo.crudifyTokens.expiresAt,
3026
- refreshExpiresAt: tokenInfo.crudifyTokens.refreshExpiresAt
3027
- } : null
3028
- }));
3250
+ const bytes = CryptoJS.AES.decrypt(encryptedData, _TokenStorage.ENCRYPTION_KEY);
3251
+ const decrypted = bytes.toString(CryptoJS.enc.Utf8);
3252
+ return decrypted || encryptedData;
3029
3253
  } catch (error) {
3030
- setState((prev) => ({
3031
- ...prev,
3032
- isLoading: false,
3033
- isInitialized: true,
3034
- error: error instanceof Error ? error.message : "Initialization failed"
3035
- }));
3254
+ console.error("Crudify: Decryption failed", error);
3255
+ return encryptedData;
3036
3256
  }
3037
- }, [options.autoRestore, options.enableLogging, options.onSessionExpired, options.onSessionRestored]);
3038
- const login = useCallback5(async (email, password) => {
3039
- setState((prev) => ({ ...prev, isLoading: true, error: null }));
3257
+ }
3258
+ /**
3259
+ * Guardar tokens de forma segura
3260
+ */
3261
+ static saveTokens(tokens) {
3262
+ const storage = _TokenStorage.getStorage();
3263
+ if (!storage) return;
3040
3264
  try {
3041
- const result = await sessionManager.login(email, password);
3042
- if (result.success && result.tokens) {
3043
- setState((prev) => ({
3044
- ...prev,
3045
- isAuthenticated: true,
3046
- tokens: result.tokens,
3047
- isLoading: false,
3048
- error: null
3049
- }));
3050
- } else {
3051
- setState((prev) => ({
3052
- ...prev,
3053
- isAuthenticated: false,
3054
- tokens: null,
3055
- isLoading: false,
3056
- error: result.error || "Login failed"
3057
- }));
3058
- }
3059
- return result;
3060
- } catch (error) {
3061
- const errorMsg = error instanceof Error ? error.message : "Login failed";
3062
- setState((prev) => ({
3063
- ...prev,
3064
- isAuthenticated: false,
3065
- tokens: null,
3066
- isLoading: false,
3067
- error: errorMsg
3068
- }));
3069
- return {
3070
- success: false,
3071
- error: errorMsg
3265
+ const tokenData = {
3266
+ accessToken: tokens.accessToken,
3267
+ refreshToken: tokens.refreshToken,
3268
+ expiresAt: tokens.expiresAt,
3269
+ refreshExpiresAt: tokens.refreshExpiresAt,
3270
+ savedAt: Date.now()
3072
3271
  };
3073
- }
3074
- }, [sessionManager]);
3075
- const logout = useCallback5(async () => {
3076
- setState((prev) => ({ ...prev, isLoading: true }));
3077
- try {
3078
- await sessionManager.logout();
3079
- setState((prev) => ({
3080
- ...prev,
3081
- isAuthenticated: false,
3082
- tokens: null,
3083
- isLoading: false,
3084
- error: null
3085
- }));
3272
+ const encrypted = _TokenStorage.encrypt(JSON.stringify(tokenData));
3273
+ storage.setItem(_TokenStorage.TOKEN_KEY, encrypted);
3274
+ console.debug("Crudify: Tokens saved successfully");
3086
3275
  } catch (error) {
3087
- setState((prev) => ({
3088
- ...prev,
3089
- isAuthenticated: false,
3090
- tokens: null,
3091
- isLoading: false,
3092
- error: error instanceof Error ? error.message : "Logout error"
3093
- }));
3276
+ console.error("Crudify: Failed to save tokens", error);
3094
3277
  }
3095
- }, [sessionManager]);
3096
- const refreshTokens = useCallback5(async () => {
3278
+ }
3279
+ /**
3280
+ * Obtener tokens guardados
3281
+ */
3282
+ static getTokens() {
3283
+ const storage = _TokenStorage.getStorage();
3284
+ if (!storage) return null;
3097
3285
  try {
3098
- const success = await sessionManager.refreshTokens();
3099
- if (success) {
3100
- const tokenInfo = sessionManager.getTokenInfo();
3101
- setState((prev) => ({
3102
- ...prev,
3103
- tokens: tokenInfo.crudifyTokens.accessToken ? {
3104
- accessToken: tokenInfo.crudifyTokens.accessToken,
3105
- refreshToken: tokenInfo.crudifyTokens.refreshToken,
3106
- expiresAt: tokenInfo.crudifyTokens.expiresAt,
3107
- refreshExpiresAt: tokenInfo.crudifyTokens.refreshExpiresAt
3108
- } : null,
3109
- error: null
3110
- }));
3111
- } else {
3112
- setState((prev) => ({
3113
- ...prev,
3114
- isAuthenticated: false,
3115
- tokens: null,
3116
- error: "Token refresh failed"
3117
- }));
3286
+ const encrypted = storage.getItem(_TokenStorage.TOKEN_KEY);
3287
+ if (!encrypted) return null;
3288
+ const decrypted = _TokenStorage.decrypt(encrypted);
3289
+ const tokenData = JSON.parse(decrypted);
3290
+ if (!tokenData.accessToken || !tokenData.refreshToken || !tokenData.expiresAt || !tokenData.refreshExpiresAt) {
3291
+ console.warn("Crudify: Incomplete token data found, clearing storage");
3292
+ _TokenStorage.clearTokens();
3293
+ return null;
3118
3294
  }
3119
- return success;
3295
+ if (Date.now() >= tokenData.refreshExpiresAt) {
3296
+ console.info("Crudify: Refresh token expired, clearing storage");
3297
+ _TokenStorage.clearTokens();
3298
+ return null;
3299
+ }
3300
+ return {
3301
+ accessToken: tokenData.accessToken,
3302
+ refreshToken: tokenData.refreshToken,
3303
+ expiresAt: tokenData.expiresAt,
3304
+ refreshExpiresAt: tokenData.refreshExpiresAt
3305
+ };
3120
3306
  } catch (error) {
3121
- setState((prev) => ({
3122
- ...prev,
3123
- isAuthenticated: false,
3124
- tokens: null,
3125
- error: error instanceof Error ? error.message : "Token refresh failed"
3126
- }));
3127
- return false;
3128
- }
3129
- }, [sessionManager]);
3130
- const clearError = useCallback5(() => {
3131
- setState((prev) => ({ ...prev, error: null }));
3132
- }, []);
3133
- const getTokenInfo = useCallback5(() => {
3134
- return sessionManager.getTokenInfo();
3135
- }, [sessionManager]);
3136
- useEffect9(() => {
3137
- initialize();
3138
- }, [initialize]);
3139
- return {
3140
- // Estado
3141
- ...state,
3142
- // Acciones
3143
- login,
3144
- logout,
3145
- refreshTokens,
3146
- clearError,
3147
- getTokenInfo,
3148
- // Utilidades
3149
- isExpiringSoon: state.tokens ? state.tokens.expiresAt - Date.now() < 5 * 60 * 1e3 : false,
3150
- // 5 minutos
3151
- expiresIn: state.tokens ? Math.max(0, state.tokens.expiresAt - Date.now()) : 0,
3152
- refreshExpiresIn: state.tokens ? Math.max(0, state.tokens.refreshExpiresAt - Date.now()) : 0
3153
- };
3154
- }
3155
-
3156
- // src/providers/SessionProvider.tsx
3157
- import { createContext as createContext4, useContext as useContext4, useMemo as useMemo3 } from "react";
3158
- import { Fragment as Fragment7, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
3159
- var SessionContext = createContext4(void 0);
3160
- function SessionProvider({ children, options = {} }) {
3161
- const sessionHook = useSession(options);
3162
- const sessionData = useMemo3(() => {
3163
- if (!sessionHook.tokens?.accessToken || !sessionHook.isAuthenticated) {
3307
+ console.error("Crudify: Failed to retrieve tokens", error);
3308
+ _TokenStorage.clearTokens();
3164
3309
  return null;
3165
3310
  }
3311
+ }
3312
+ /**
3313
+ * Limpiar tokens almacenados
3314
+ */
3315
+ static clearTokens() {
3316
+ const storage = _TokenStorage.getStorage();
3317
+ if (!storage) return;
3166
3318
  try {
3167
- const decoded = decodeJwtSafely(sessionHook.tokens.accessToken);
3168
- if (decoded && decoded.sub && decoded.email && decoded.subscriber) {
3169
- const result = {
3170
- _id: decoded.sub,
3171
- email: decoded.email,
3172
- subscriberKey: decoded.subscriber
3173
- };
3174
- Object.keys(decoded).forEach((key) => {
3175
- if (!["sub", "email", "subscriber"].includes(key)) {
3176
- result[key] = decoded[key];
3177
- }
3178
- });
3179
- return result;
3180
- }
3319
+ storage.removeItem(_TokenStorage.TOKEN_KEY);
3320
+ console.debug("Crudify: Tokens cleared from storage");
3181
3321
  } catch (error) {
3182
- console.error("Error decoding JWT token for sessionData:", error);
3322
+ console.error("Crudify: Failed to clear tokens", error);
3183
3323
  }
3184
- return null;
3185
- }, [sessionHook.tokens?.accessToken, sessionHook.isAuthenticated]);
3186
- const contextValue = {
3187
- ...sessionHook,
3188
- sessionData
3189
- };
3190
- return /* @__PURE__ */ jsx11(SessionContext.Provider, { value: contextValue, children });
3191
- }
3192
- function useSessionContext() {
3193
- const context = useContext4(SessionContext);
3194
- if (context === void 0) {
3195
- throw new Error("useSessionContext must be used within a SessionProvider");
3196
- }
3197
- return context;
3198
- }
3199
- function ProtectedRoute({
3200
- children,
3201
- fallback = /* @__PURE__ */ jsx11("div", { children: "Please log in to access this content" }),
3202
- redirectTo
3203
- }) {
3204
- const { isAuthenticated, isLoading, isInitialized } = useSessionContext();
3205
- if (!isInitialized || isLoading) {
3206
- return /* @__PURE__ */ jsx11("div", { children: "Loading..." });
3207
3324
  }
3208
- if (!isAuthenticated) {
3209
- if (redirectTo) {
3210
- redirectTo();
3211
- return null;
3212
- }
3213
- return /* @__PURE__ */ jsx11(Fragment7, { children: fallback });
3325
+ /**
3326
+ * Verificar si hay tokens válidos guardados
3327
+ */
3328
+ static hasValidTokens() {
3329
+ const tokens = _TokenStorage.getTokens();
3330
+ return tokens !== null;
3214
3331
  }
3215
- return /* @__PURE__ */ jsx11(Fragment7, { children });
3216
- }
3217
- function SessionDebugInfo() {
3218
- const session = useSessionContext();
3219
- if (!session.isInitialized) {
3220
- return /* @__PURE__ */ jsx11("div", { children: "Session not initialized" });
3332
+ /**
3333
+ * Obtener información de expiración
3334
+ */
3335
+ static getExpirationInfo() {
3336
+ const tokens = _TokenStorage.getTokens();
3337
+ if (!tokens) return null;
3338
+ const now = Date.now();
3339
+ return {
3340
+ accessExpired: now >= tokens.expiresAt,
3341
+ refreshExpired: now >= tokens.refreshExpiresAt,
3342
+ accessExpiresIn: Math.max(0, tokens.expiresAt - now),
3343
+ refreshExpiresIn: Math.max(0, tokens.refreshExpiresAt - now)
3344
+ };
3221
3345
  }
3222
- return /* @__PURE__ */ jsxs8("div", { style: {
3223
- padding: "10px",
3224
- margin: "10px",
3225
- border: "1px solid #ccc",
3226
- borderRadius: "4px",
3227
- fontSize: "12px",
3228
- fontFamily: "monospace"
3229
- }, children: [
3230
- /* @__PURE__ */ jsx11("h4", { children: "Session Debug Info" }),
3231
- /* @__PURE__ */ jsxs8("div", { children: [
3232
- /* @__PURE__ */ jsx11("strong", { children: "Authenticated:" }),
3233
- " ",
3234
- session.isAuthenticated ? "Yes" : "No"
3235
- ] }),
3236
- /* @__PURE__ */ jsxs8("div", { children: [
3237
- /* @__PURE__ */ jsx11("strong", { children: "Loading:" }),
3238
- " ",
3239
- session.isLoading ? "Yes" : "No"
3240
- ] }),
3241
- /* @__PURE__ */ jsxs8("div", { children: [
3242
- /* @__PURE__ */ jsx11("strong", { children: "Error:" }),
3243
- " ",
3244
- session.error || "None"
3245
- ] }),
3246
- session.tokens && /* @__PURE__ */ jsxs8(Fragment7, { children: [
3247
- /* @__PURE__ */ jsxs8("div", { children: [
3248
- /* @__PURE__ */ jsx11("strong", { children: "Access Token:" }),
3249
- " ",
3250
- session.tokens.accessToken.substring(0, 20),
3251
- "..."
3252
- ] }),
3253
- /* @__PURE__ */ jsxs8("div", { children: [
3254
- /* @__PURE__ */ jsx11("strong", { children: "Refresh Token:" }),
3255
- " ",
3256
- session.tokens.refreshToken.substring(0, 20),
3257
- "..."
3258
- ] }),
3259
- /* @__PURE__ */ jsxs8("div", { children: [
3260
- /* @__PURE__ */ jsx11("strong", { children: "Access Expires In:" }),
3261
- " ",
3262
- Math.round(session.expiresIn / 1e3 / 60),
3263
- " minutes"
3264
- ] }),
3265
- /* @__PURE__ */ jsxs8("div", { children: [
3266
- /* @__PURE__ */ jsx11("strong", { children: "Refresh Expires In:" }),
3267
- " ",
3268
- Math.round(session.refreshExpiresIn / 1e3 / 60 / 60),
3269
- " hours"
3270
- ] }),
3271
- /* @__PURE__ */ jsxs8("div", { children: [
3272
- /* @__PURE__ */ jsx11("strong", { children: "Expiring Soon:" }),
3273
- " ",
3274
- session.isExpiringSoon ? "Yes" : "No"
3275
- ] })
3276
- ] })
3277
- ] });
3278
- }
3279
-
3280
- // src/components/LoginComponent.tsx
3281
- import { useState as useState10 } from "react";
3282
- import { Button as Button5, TextField as TextField5, Box as Box8, Alert as Alert7, Typography as Typography8, CircularProgress as CircularProgress7 } from "@mui/material";
3283
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
3284
- function LoginComponent() {
3285
- const [email, setEmail] = useState10("");
3286
- const [password, setPassword] = useState10("");
3287
- const [showForm, setShowForm] = useState10(false);
3288
- const {
3289
- isAuthenticated,
3290
- isLoading,
3291
- error,
3292
- login,
3293
- logout,
3294
- refreshTokens,
3295
- clearError,
3296
- isExpiringSoon,
3297
- expiresIn
3298
- } = useSessionContext();
3299
- const handleLogin = async (e) => {
3300
- e.preventDefault();
3301
- if (!email || !password) {
3346
+ /**
3347
+ * Actualizar solo el access token (después de refresh)
3348
+ */
3349
+ static updateAccessToken(newAccessToken, newExpiresAt) {
3350
+ const existingTokens = _TokenStorage.getTokens();
3351
+ if (!existingTokens) {
3352
+ console.warn("Crudify: Cannot update access token, no existing tokens found");
3353
+ return;
3354
+ }
3355
+ _TokenStorage.saveTokens({
3356
+ ...existingTokens,
3357
+ accessToken: newAccessToken,
3358
+ expiresAt: newExpiresAt
3359
+ });
3360
+ }
3361
+ };
3362
+ _TokenStorage.TOKEN_KEY = "crudify_tokens";
3363
+ _TokenStorage.ENCRYPTION_KEY = "crudify_secure_key_v1";
3364
+ _TokenStorage.storageType = "localStorage";
3365
+ var TokenStorage = _TokenStorage;
3366
+
3367
+ // src/core/SessionManager.ts
3368
+ var SessionManager = class _SessionManager {
3369
+ constructor() {
3370
+ this.config = {};
3371
+ this.initialized = false;
3372
+ }
3373
+ static getInstance() {
3374
+ if (!_SessionManager.instance) {
3375
+ _SessionManager.instance = new _SessionManager();
3376
+ }
3377
+ return _SessionManager.instance;
3378
+ }
3379
+ /**
3380
+ * Inicializar el SessionManager
3381
+ */
3382
+ async initialize(config = {}) {
3383
+ if (this.initialized) {
3384
+ console.warn("SessionManager: Already initialized");
3302
3385
  return;
3303
3386
  }
3304
- const result = await login(email, password);
3305
- if (result.success) {
3306
- setEmail("");
3307
- setPassword("");
3308
- setShowForm(false);
3387
+ this.config = {
3388
+ storageType: "localStorage",
3389
+ autoRestore: true,
3390
+ enableLogging: false,
3391
+ ...config
3392
+ };
3393
+ TokenStorage.setStorageType(this.config.storageType || "localStorage");
3394
+ if (this.config.enableLogging) {
3309
3395
  }
3310
- };
3311
- const handleLogout = async () => {
3312
- await logout();
3313
- };
3314
- const handleRefreshTokens = async () => {
3315
- await refreshTokens();
3316
- };
3317
- if (isAuthenticated) {
3318
- return /* @__PURE__ */ jsxs9(Box8, { sx: { maxWidth: 600, mx: "auto", p: 3 }, children: [
3319
- /* @__PURE__ */ jsx12(Typography8, { variant: "h4", gutterBottom: true, children: "Welcome! \u{1F389}" }),
3320
- /* @__PURE__ */ jsx12(Alert7, { severity: "success", sx: { mb: 3 }, children: "You are successfully logged in with Refresh Token Pattern enabled" }),
3321
- /* @__PURE__ */ jsxs9(Box8, { sx: { mb: 3, p: 2, bgcolor: "background.paper", border: 1, borderColor: "divider", borderRadius: 1 }, children: [
3322
- /* @__PURE__ */ jsx12(Typography8, { variant: "h6", gutterBottom: true, children: "Token Status" }),
3323
- /* @__PURE__ */ jsxs9(Typography8, { variant: "body2", color: "text.secondary", children: [
3324
- "Access Token expires in: ",
3325
- Math.round(expiresIn / 1e3 / 60),
3326
- " minutes"
3327
- ] }),
3328
- isExpiringSoon && /* @__PURE__ */ jsx12(Alert7, { severity: "warning", sx: { mt: 1 }, children: "Token expires soon - automatic refresh will happen" })
3329
- ] }),
3330
- /* @__PURE__ */ jsxs9(Box8, { sx: { display: "flex", gap: 2, flexWrap: "wrap" }, children: [
3331
- /* @__PURE__ */ jsx12(
3332
- Button5,
3333
- {
3334
- variant: "contained",
3335
- onClick: handleRefreshTokens,
3336
- disabled: isLoading,
3337
- startIcon: isLoading ? /* @__PURE__ */ jsx12(CircularProgress7, { size: 16 }) : null,
3338
- children: "Refresh Tokens"
3339
- }
3340
- ),
3341
- /* @__PURE__ */ jsx12(
3342
- Button5,
3343
- {
3344
- variant: "outlined",
3345
- color: "error",
3346
- onClick: handleLogout,
3347
- disabled: isLoading,
3348
- children: "Logout"
3349
- }
3350
- )
3351
- ] }),
3352
- error && /* @__PURE__ */ jsx12(Alert7, { severity: "error", sx: { mt: 2 }, onClose: clearError, children: error })
3353
- ] });
3396
+ if (this.config.autoRestore) {
3397
+ await this.restoreSession();
3398
+ }
3399
+ this.initialized = true;
3400
+ this.log("SessionManager initialized successfully");
3354
3401
  }
3355
- return /* @__PURE__ */ jsxs9(Box8, { sx: { maxWidth: 400, mx: "auto", p: 3 }, children: [
3356
- /* @__PURE__ */ jsx12(Typography8, { variant: "h4", gutterBottom: true, align: "center", children: "Login with Refresh Tokens" }),
3357
- /* @__PURE__ */ jsx12(Alert7, { severity: "info", sx: { mb: 3 }, children: "This demo shows the new Refresh Token Pattern with automatic session management" }),
3358
- !showForm ? /* @__PURE__ */ jsx12(
3359
- Button5,
3360
- {
3361
- fullWidth: true,
3362
- variant: "contained",
3363
- size: "large",
3364
- onClick: () => setShowForm(true),
3365
- sx: { mt: 2 },
3366
- children: "Show Login Form"
3402
+ /**
3403
+ * Login con persistencia automática
3404
+ */
3405
+ async login(email, password) {
3406
+ try {
3407
+ this.log("Attempting login...");
3408
+ const response = await crudify6.login(email, password);
3409
+ if (!response.success) {
3410
+ this.log("Login failed:", response.errors);
3411
+ return {
3412
+ success: false,
3413
+ error: this.formatError(response.errors)
3414
+ };
3367
3415
  }
3368
- ) : /* @__PURE__ */ jsxs9("form", { onSubmit: handleLogin, children: [
3369
- /* @__PURE__ */ jsx12(
3370
- TextField5,
3371
- {
3372
- fullWidth: true,
3373
- label: "Email",
3374
- type: "email",
3375
- value: email,
3376
- onChange: (e) => setEmail(e.target.value),
3377
- margin: "normal",
3378
- required: true,
3379
- autoComplete: "email"
3380
- }
3381
- ),
3382
- /* @__PURE__ */ jsx12(
3383
- TextField5,
3384
- {
3385
- fullWidth: true,
3386
- label: "Password",
3387
- type: "password",
3388
- value: password,
3389
- onChange: (e) => setPassword(e.target.value),
3390
- margin: "normal",
3391
- required: true,
3392
- autoComplete: "current-password"
3393
- }
3394
- ),
3395
- /* @__PURE__ */ jsx12(
3396
- Button5,
3397
- {
3398
- type: "submit",
3399
- fullWidth: true,
3400
- variant: "contained",
3401
- size: "large",
3402
- disabled: isLoading,
3403
- startIcon: isLoading ? /* @__PURE__ */ jsx12(CircularProgress7, { size: 16 }) : null,
3404
- sx: { mt: 3, mb: 2 },
3405
- children: isLoading ? "Logging in..." : "Login"
3406
- }
3407
- )
3408
- ] }),
3409
- error && /* @__PURE__ */ jsx12(Alert7, { severity: "error", sx: { mt: 2 }, onClose: clearError, children: error })
3410
- ] });
3411
- }
3412
- function SessionStatus() {
3413
- const { isAuthenticated, isLoading, isExpiringSoon, expiresIn } = useSessionContext();
3414
- if (isLoading) {
3415
- return /* @__PURE__ */ jsxs9(Box8, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
3416
- /* @__PURE__ */ jsx12(CircularProgress7, { size: 16 }),
3417
- /* @__PURE__ */ jsx12(Typography8, { variant: "caption", children: "Loading session..." })
3418
- ] });
3416
+ const tokens = {
3417
+ accessToken: response.data.token,
3418
+ refreshToken: response.data.refreshToken,
3419
+ expiresAt: response.data.expiresAt,
3420
+ refreshExpiresAt: response.data.refreshExpiresAt
3421
+ };
3422
+ TokenStorage.saveTokens(tokens);
3423
+ this.log("Login successful, tokens saved");
3424
+ this.config.onLoginSuccess?.(tokens);
3425
+ return {
3426
+ success: true,
3427
+ tokens
3428
+ };
3429
+ } catch (error) {
3430
+ this.log("Login error:", error);
3431
+ return {
3432
+ success: false,
3433
+ error: error instanceof Error ? error.message : "Unknown error"
3434
+ };
3435
+ }
3419
3436
  }
3420
- if (!isAuthenticated) {
3421
- return /* @__PURE__ */ jsx12(Typography8, { variant: "caption", color: "text.secondary", children: "Not logged in" });
3437
+ /**
3438
+ * Logout con limpieza de tokens
3439
+ */
3440
+ async logout() {
3441
+ try {
3442
+ this.log("Logging out...");
3443
+ await crudify6.logout();
3444
+ TokenStorage.clearTokens();
3445
+ this.log("Logout successful");
3446
+ this.config.onLogout?.();
3447
+ } catch (error) {
3448
+ this.log("Logout error:", error);
3449
+ TokenStorage.clearTokens();
3450
+ }
3422
3451
  }
3423
- return /* @__PURE__ */ jsxs9(Box8, { children: [
3424
- /* @__PURE__ */ jsx12(Typography8, { variant: "caption", color: "success.main", children: "\u2713 Authenticated" }),
3425
- isExpiringSoon && /* @__PURE__ */ jsxs9(Typography8, { variant: "caption", color: "warning.main", display: "block", children: [
3426
- "\u26A0 Token expires in ",
3427
- Math.round(expiresIn / 1e3 / 60),
3428
- " min"
3429
- ] })
3430
- ] });
3431
- }
3432
-
3433
- // src/hooks/useUserData.ts
3434
- import { useState as useState11, useEffect as useEffect10, useCallback as useCallback6, useRef as useRef4 } from "react";
3435
- import crudify7 from "@nocios/crudify-browser";
3436
- var useUserData = (options = {}) => {
3437
- const { autoFetch = true, retryOnError = false, maxRetries = 3 } = options;
3438
- const { isAuthenticated, isInitialized, sessionData, tokens } = useSessionContext();
3439
- const [userData, setUserData] = useState11(null);
3440
- const [loading, setLoading] = useState11(false);
3441
- const [error, setError] = useState11(null);
3442
- const abortControllerRef = useRef4(null);
3443
- const mountedRef = useRef4(true);
3444
- const requestIdRef = useRef4(0);
3445
- const retryCountRef = useRef4(0);
3446
- const getUserEmail = useCallback6(() => {
3447
- if (!sessionData) return null;
3448
- return sessionData.email || sessionData["cognito:username"] || null;
3449
- }, [sessionData]);
3450
- const clearProfile = useCallback6(() => {
3451
- setUserData(null);
3452
- setError(null);
3453
- setLoading(false);
3454
- retryCountRef.current = 0;
3455
- }, []);
3456
- const refreshProfile = useCallback6(async () => {
3457
- const userEmail = getUserEmail();
3458
- console.log("\u{1F464} useUserData - Refreshing profile for:", userEmail);
3459
- if (!userEmail) {
3460
- if (mountedRef.current) {
3461
- setError("No user email available from session data");
3462
- setLoading(false);
3452
+ /**
3453
+ * Restaurar sesión desde storage
3454
+ */
3455
+ async restoreSession() {
3456
+ try {
3457
+ this.log("Attempting to restore session...");
3458
+ const savedTokens = TokenStorage.getTokens();
3459
+ if (!savedTokens) {
3460
+ this.log("No valid tokens found in storage");
3461
+ return false;
3462
+ }
3463
+ crudify6.setTokens({
3464
+ accessToken: savedTokens.accessToken,
3465
+ refreshToken: savedTokens.refreshToken,
3466
+ expiresAt: savedTokens.expiresAt,
3467
+ refreshExpiresAt: savedTokens.refreshExpiresAt
3468
+ });
3469
+ this.log("Session restored successfully");
3470
+ this.config.onSessionRestored?.(savedTokens);
3471
+ return true;
3472
+ } catch (error) {
3473
+ this.log("Session restore error:", error);
3474
+ TokenStorage.clearTokens();
3475
+ return false;
3476
+ }
3477
+ }
3478
+ /**
3479
+ * Verificar si el usuario está autenticado
3480
+ */
3481
+ isAuthenticated() {
3482
+ return crudify6.isLogin() || TokenStorage.hasValidTokens();
3483
+ }
3484
+ /**
3485
+ * Obtener información de tokens actuales
3486
+ */
3487
+ getTokenInfo() {
3488
+ const crudifyTokens = crudify6.getTokenData();
3489
+ const storageInfo = TokenStorage.getExpirationInfo();
3490
+ return {
3491
+ isLoggedIn: this.isAuthenticated(),
3492
+ crudifyTokens,
3493
+ storageInfo,
3494
+ hasValidTokens: TokenStorage.hasValidTokens()
3495
+ };
3496
+ }
3497
+ /**
3498
+ * Refrescar tokens manualmente
3499
+ */
3500
+ async refreshTokens() {
3501
+ try {
3502
+ this.log("Manually refreshing tokens...");
3503
+ const response = await crudify6.refreshAccessToken();
3504
+ if (!response.success) {
3505
+ this.log("Token refresh failed:", response.errors);
3506
+ TokenStorage.clearTokens();
3507
+ this.config.onSessionExpired?.();
3508
+ return false;
3463
3509
  }
3464
- return;
3510
+ const newTokens = {
3511
+ accessToken: response.data.token,
3512
+ refreshToken: response.data.refreshToken,
3513
+ expiresAt: response.data.expiresAt,
3514
+ refreshExpiresAt: response.data.refreshExpiresAt
3515
+ };
3516
+ TokenStorage.saveTokens(newTokens);
3517
+ this.log("Tokens refreshed and saved successfully");
3518
+ return true;
3519
+ } catch (error) {
3520
+ this.log("Token refresh error:", error);
3521
+ TokenStorage.clearTokens();
3522
+ this.config.onSessionExpired?.();
3523
+ return false;
3465
3524
  }
3466
- if (!isInitialized) {
3467
- if (mountedRef.current) {
3468
- setError("Session not initialized");
3469
- setLoading(false);
3525
+ }
3526
+ /**
3527
+ * Configurar interceptor de respuesta para manejo automático de errores
3528
+ */
3529
+ setupResponseInterceptor() {
3530
+ crudify6.setResponseInterceptor(async (response) => {
3531
+ if (response.errors) {
3532
+ const hasAuthError = response.errors.some(
3533
+ (error) => error.message?.includes("Unauthorized") || error.message?.includes("Token") || error.extensions?.code === "UNAUTHENTICATED"
3534
+ );
3535
+ if (hasAuthError && TokenStorage.hasValidTokens()) {
3536
+ this.log("Auth error detected, attempting token refresh...");
3537
+ const refreshSuccess = await this.refreshTokens();
3538
+ if (!refreshSuccess) {
3539
+ this.log("Session expired, triggering callback");
3540
+ this.config.onSessionExpired?.();
3541
+ }
3542
+ }
3470
3543
  }
3471
- return;
3544
+ return response;
3545
+ });
3546
+ this.log("Response interceptor configured");
3547
+ }
3548
+ /**
3549
+ * Limpiar sesión completamente
3550
+ */
3551
+ clearSession() {
3552
+ TokenStorage.clearTokens();
3553
+ crudify6.logout();
3554
+ this.log("Session cleared completely");
3555
+ }
3556
+ // Métodos privados
3557
+ log(message, ...args) {
3558
+ if (this.config.enableLogging) {
3559
+ console.log(`[SessionManager] ${message}`, ...args);
3472
3560
  }
3473
- if (abortControllerRef.current) {
3474
- abortControllerRef.current.abort();
3561
+ }
3562
+ formatError(errors) {
3563
+ if (!errors) return "Unknown error";
3564
+ if (typeof errors === "string") return errors;
3565
+ if (typeof errors === "object") {
3566
+ const errorMessages = Object.values(errors).flat();
3567
+ return errorMessages.join(", ");
3475
3568
  }
3476
- const abortController = new AbortController();
3477
- abortControllerRef.current = abortController;
3478
- const currentRequestId = ++requestIdRef.current;
3569
+ return "Authentication failed";
3570
+ }
3571
+ };
3572
+
3573
+ // src/hooks/useSession.ts
3574
+ import { useState as useState10, useEffect as useEffect10, useCallback as useCallback5 } from "react";
3575
+ function useSession(options = {}) {
3576
+ const [state, setState] = useState10({
3577
+ isAuthenticated: false,
3578
+ isLoading: true,
3579
+ isInitialized: false,
3580
+ tokens: null,
3581
+ error: null
3582
+ });
3583
+ const sessionManager = SessionManager.getInstance();
3584
+ const initialize = useCallback5(async () => {
3479
3585
  try {
3480
- if (mountedRef.current) {
3481
- setLoading(true);
3482
- setError(null);
3483
- }
3484
- console.log("\u{1F464} useUserData - Fetching profile data from database");
3485
- const response = await crudify7.readItems("users", {
3486
- filter: { email: userEmail },
3487
- pagination: { limit: 1 }
3488
- });
3489
- console.log("\u{1F464} useUserData - Database response:", response);
3490
- console.log("\u{1F464} useUserData - response.data:", response.data);
3491
- console.log("\u{1F464} useUserData - response.data type:", typeof response.data);
3492
- if (currentRequestId === requestIdRef.current && mountedRef.current && !abortController.signal.aborted) {
3493
- let userData2 = null;
3494
- if (response.success) {
3495
- console.log("\u{1F464} useUserData - Processing successful response:", {
3496
- dataType: typeof response.data,
3497
- isArray: Array.isArray(response.data),
3498
- hasResponse: !!response.data?.response,
3499
- hasResponseData: !!response.data?.response?.data,
3500
- responseDataType: typeof response.data?.response?.data
3501
- });
3502
- if (Array.isArray(response.data) && response.data.length > 0) {
3503
- console.log("\u{1F464} useUserData - Found direct array format");
3504
- userData2 = response.data[0];
3505
- } else if (response.data?.response?.data) {
3506
- console.log("\u{1F464} useUserData - Found nested response.data format");
3507
- try {
3508
- const rawData = response.data.response.data;
3509
- console.log("\u{1F464} useUserData - Raw nested data:", rawData);
3510
- console.log("\u{1F464} useUserData - Raw data type:", typeof rawData);
3511
- const parsedData = typeof rawData === "string" ? JSON.parse(rawData) : rawData;
3512
- console.log("\u{1F464} useUserData - Parsed nested data:", parsedData);
3513
- if (parsedData && parsedData.items && Array.isArray(parsedData.items) && parsedData.items.length > 0) {
3514
- userData2 = parsedData.items[0];
3515
- console.log("\u{1F464} useUserData - Extracted user from nested items:", userData2);
3516
- } else {
3517
- console.log("\u{1F464} useUserData - No items found in parsed data or items array is empty");
3518
- }
3519
- } catch (parseError) {
3520
- console.error("\u{1F464} useUserData - Error parsing nested response data:", parseError);
3521
- }
3522
- } else if (response.data && typeof response.data === "object") {
3523
- console.log("\u{1F464} useUserData - Found object format, checking for items");
3524
- if (response.data.items && Array.isArray(response.data.items) && response.data.items.length > 0) {
3525
- console.log("\u{1F464} useUserData - Found items in object format");
3526
- userData2 = response.data.items[0];
3527
- } else {
3528
- console.log("\u{1F464} useUserData - No items found in object format");
3529
- }
3530
- } else if (response.data?.data?.response?.data) {
3531
- console.log("\u{1F464} useUserData - Found double-nested data.data.response.data format");
3532
- try {
3533
- const rawData = response.data.data.response.data;
3534
- console.log("\u{1F464} useUserData - Raw double-nested data:", rawData);
3535
- const parsedData = typeof rawData === "string" ? JSON.parse(rawData) : rawData;
3536
- console.log("\u{1F464} useUserData - Parsed double-nested data:", parsedData);
3537
- if (parsedData && parsedData.items && Array.isArray(parsedData.items) && parsedData.items.length > 0) {
3538
- userData2 = parsedData.items[0];
3539
- console.log("\u{1F464} useUserData - Extracted user from double-nested items:", userData2);
3540
- }
3541
- } catch (parseError) {
3542
- console.error("\u{1F464} useUserData - Error parsing double-nested response data:", parseError);
3543
- }
3544
- }
3545
- }
3546
- if (userData2) {
3547
- console.log("\u{1F464} useUserData - User data found:", userData2);
3548
- setUserData(userData2);
3549
- setError(null);
3550
- retryCountRef.current = 0;
3551
- console.log("\u{1F464} useUserData - Profile loaded successfully:", userData2);
3552
- } else {
3553
- setError("User profile not found in database");
3554
- setUserData(null);
3555
- console.warn("\u{1F464} useUserData - User not found for email:", userEmail);
3556
- }
3557
- }
3558
- } catch (err) {
3559
- if (currentRequestId === requestIdRef.current && mountedRef.current) {
3560
- const error2 = err;
3561
- console.error("\u{1F464} useUserData - Error fetching profile:", error2);
3562
- if (error2.name === "AbortError") {
3563
- return;
3564
- }
3565
- const shouldRetry = retryOnError && retryCountRef.current < maxRetries && (error2.message?.includes("Network Error") || error2.message?.includes("Failed to fetch"));
3566
- if (shouldRetry) {
3567
- retryCountRef.current++;
3568
- console.log(`\u{1F464} useUserData - Retrying profile fetch (${retryCountRef.current}/${maxRetries})`);
3569
- setTimeout(() => {
3570
- if (mountedRef.current) {
3571
- refreshProfile();
3572
- }
3573
- }, 1e3 * retryCountRef.current);
3574
- } else {
3575
- setError("Failed to load user profile from database");
3576
- setUserData(null);
3586
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
3587
+ const config = {
3588
+ autoRestore: options.autoRestore ?? true,
3589
+ enableLogging: options.enableLogging ?? false,
3590
+ onSessionExpired: () => {
3591
+ setState((prev) => ({
3592
+ ...prev,
3593
+ isAuthenticated: false,
3594
+ tokens: null,
3595
+ error: "Session expired"
3596
+ }));
3597
+ options.onSessionExpired?.();
3598
+ },
3599
+ onSessionRestored: (tokens) => {
3600
+ setState((prev) => ({
3601
+ ...prev,
3602
+ isAuthenticated: true,
3603
+ tokens,
3604
+ error: null
3605
+ }));
3606
+ options.onSessionRestored?.(tokens);
3607
+ },
3608
+ onLoginSuccess: (tokens) => {
3609
+ setState((prev) => ({
3610
+ ...prev,
3611
+ isAuthenticated: true,
3612
+ tokens,
3613
+ error: null
3614
+ }));
3615
+ },
3616
+ onLogout: () => {
3617
+ setState((prev) => ({
3618
+ ...prev,
3619
+ isAuthenticated: false,
3620
+ tokens: null,
3621
+ error: null
3622
+ }));
3577
3623
  }
3624
+ };
3625
+ await sessionManager.initialize(config);
3626
+ sessionManager.setupResponseInterceptor();
3627
+ const isAuth = sessionManager.isAuthenticated();
3628
+ const tokenInfo = sessionManager.getTokenInfo();
3629
+ setState((prev) => ({
3630
+ ...prev,
3631
+ isAuthenticated: isAuth,
3632
+ isInitialized: true,
3633
+ isLoading: false,
3634
+ tokens: tokenInfo.crudifyTokens.accessToken ? {
3635
+ accessToken: tokenInfo.crudifyTokens.accessToken,
3636
+ refreshToken: tokenInfo.crudifyTokens.refreshToken,
3637
+ expiresAt: tokenInfo.crudifyTokens.expiresAt,
3638
+ refreshExpiresAt: tokenInfo.crudifyTokens.refreshExpiresAt
3639
+ } : null
3640
+ }));
3641
+ } catch (error) {
3642
+ setState((prev) => ({
3643
+ ...prev,
3644
+ isLoading: false,
3645
+ isInitialized: true,
3646
+ error: error instanceof Error ? error.message : "Initialization failed"
3647
+ }));
3648
+ }
3649
+ }, [options.autoRestore, options.enableLogging, options.onSessionExpired, options.onSessionRestored]);
3650
+ const login = useCallback5(async (email, password) => {
3651
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
3652
+ try {
3653
+ const result = await sessionManager.login(email, password);
3654
+ if (result.success && result.tokens) {
3655
+ setState((prev) => ({
3656
+ ...prev,
3657
+ isAuthenticated: true,
3658
+ tokens: result.tokens,
3659
+ isLoading: false,
3660
+ error: null
3661
+ }));
3662
+ } else {
3663
+ setState((prev) => ({
3664
+ ...prev,
3665
+ isAuthenticated: false,
3666
+ tokens: null,
3667
+ isLoading: false,
3668
+ error: result.error || "Login failed"
3669
+ }));
3578
3670
  }
3579
- } finally {
3580
- if (currentRequestId === requestIdRef.current && mountedRef.current) {
3581
- setLoading(false);
3582
- }
3583
- if (abortControllerRef.current === abortController) {
3584
- abortControllerRef.current = null;
3585
- }
3671
+ return result;
3672
+ } catch (error) {
3673
+ const errorMsg = error instanceof Error ? error.message : "Login failed";
3674
+ setState((prev) => ({
3675
+ ...prev,
3676
+ isAuthenticated: false,
3677
+ tokens: null,
3678
+ isLoading: false,
3679
+ error: errorMsg
3680
+ }));
3681
+ return {
3682
+ success: false,
3683
+ error: errorMsg
3684
+ };
3586
3685
  }
3587
- }, [isInitialized, getUserEmail, retryOnError, maxRetries]);
3588
- useEffect10(() => {
3589
- if (autoFetch && isAuthenticated && isInitialized) {
3590
- refreshProfile();
3591
- } else if (!isAuthenticated) {
3592
- clearProfile();
3686
+ }, [sessionManager]);
3687
+ const logout = useCallback5(async () => {
3688
+ setState((prev) => ({ ...prev, isLoading: true }));
3689
+ try {
3690
+ await sessionManager.logout();
3691
+ setState((prev) => ({
3692
+ ...prev,
3693
+ isAuthenticated: false,
3694
+ tokens: null,
3695
+ isLoading: false,
3696
+ error: null
3697
+ }));
3698
+ } catch (error) {
3699
+ setState((prev) => ({
3700
+ ...prev,
3701
+ isAuthenticated: false,
3702
+ tokens: null,
3703
+ isLoading: false,
3704
+ error: error instanceof Error ? error.message : "Logout error"
3705
+ }));
3593
3706
  }
3594
- }, [autoFetch, isAuthenticated, isInitialized, refreshProfile, clearProfile]);
3595
- useEffect10(() => {
3596
- mountedRef.current = true;
3597
- return () => {
3598
- mountedRef.current = false;
3599
- if (abortControllerRef.current) {
3600
- abortControllerRef.current.abort();
3601
- abortControllerRef.current = null;
3707
+ }, [sessionManager]);
3708
+ const refreshTokens = useCallback5(async () => {
3709
+ try {
3710
+ const success = await sessionManager.refreshTokens();
3711
+ if (success) {
3712
+ const tokenInfo = sessionManager.getTokenInfo();
3713
+ setState((prev) => ({
3714
+ ...prev,
3715
+ tokens: tokenInfo.crudifyTokens.accessToken ? {
3716
+ accessToken: tokenInfo.crudifyTokens.accessToken,
3717
+ refreshToken: tokenInfo.crudifyTokens.refreshToken,
3718
+ expiresAt: tokenInfo.crudifyTokens.expiresAt,
3719
+ refreshExpiresAt: tokenInfo.crudifyTokens.refreshExpiresAt
3720
+ } : null,
3721
+ error: null
3722
+ }));
3723
+ } else {
3724
+ setState((prev) => ({
3725
+ ...prev,
3726
+ isAuthenticated: false,
3727
+ tokens: null,
3728
+ error: "Token refresh failed"
3729
+ }));
3602
3730
  }
3603
- };
3731
+ return success;
3732
+ } catch (error) {
3733
+ setState((prev) => ({
3734
+ ...prev,
3735
+ isAuthenticated: false,
3736
+ tokens: null,
3737
+ error: error instanceof Error ? error.message : "Token refresh failed"
3738
+ }));
3739
+ return false;
3740
+ }
3741
+ }, [sessionManager]);
3742
+ const clearError = useCallback5(() => {
3743
+ setState((prev) => ({ ...prev, error: null }));
3604
3744
  }, []);
3605
- const user = {
3606
- session: sessionData,
3607
- // Usar sessionData del nuevo sistema
3608
- data: userData
3609
- // Mantener userData del database igual que legacy
3610
- };
3745
+ const getTokenInfo = useCallback5(() => {
3746
+ return sessionManager.getTokenInfo();
3747
+ }, [sessionManager]);
3748
+ useEffect10(() => {
3749
+ initialize();
3750
+ }, [initialize]);
3611
3751
  return {
3612
- user,
3613
- loading,
3614
- error,
3615
- refreshProfile,
3616
- clearProfile
3617
- };
3618
- };
3619
-
3620
- // src/hooks/useAuth.ts
3621
- import { useCallback as useCallback7 } from "react";
3622
- var useAuth = () => {
3623
- const {
3624
- isAuthenticated,
3625
- isLoading,
3626
- isInitialized,
3627
- tokens,
3628
- error,
3629
- sessionData,
3752
+ // Estado
3753
+ ...state,
3754
+ // Acciones
3630
3755
  login,
3631
3756
  logout,
3632
3757
  refreshTokens,
3633
3758
  clearError,
3634
3759
  getTokenInfo,
3635
- isExpiringSoon,
3636
- expiresIn,
3637
- refreshExpiresIn
3638
- } = useSessionContext();
3639
- const setToken = useCallback7((token) => {
3640
- if (token) {
3641
- console.warn("useAuth.setToken() is deprecated. Use login() method instead for better security.");
3642
- } else {
3643
- logout();
3760
+ // Utilidades
3761
+ isExpiringSoon: state.tokens ? state.tokens.expiresAt - Date.now() < 5 * 60 * 1e3 : false,
3762
+ // 5 minutos
3763
+ expiresIn: state.tokens ? Math.max(0, state.tokens.expiresAt - Date.now()) : 0,
3764
+ refreshExpiresIn: state.tokens ? Math.max(0, state.tokens.refreshExpiresAt - Date.now()) : 0
3765
+ };
3766
+ }
3767
+
3768
+ // src/providers/SessionProvider.tsx
3769
+ import { createContext as createContext4, useContext as useContext4, useMemo as useMemo3 } from "react";
3770
+ import { Fragment as Fragment8, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
3771
+ var SessionContext = createContext4(void 0);
3772
+ function SessionProvider({ children, options = {} }) {
3773
+ const sessionHook = useSession(options);
3774
+ const sessionData = useMemo3(() => {
3775
+ if (!sessionHook.tokens?.accessToken || !sessionHook.isAuthenticated) {
3776
+ return null;
3644
3777
  }
3645
- }, [logout]);
3646
- const tokenExpiration = tokens?.expiresAt ? new Date(tokens.expiresAt) : null;
3647
- return {
3648
- // Estado básico (compatible con legacy)
3649
- isAuthenticated,
3650
- loading: isLoading,
3651
- // En el nuevo sistema se llama isLoading
3652
- error,
3653
- // Datos del token (compatible con legacy + mejorado)
3654
- token: tokens?.accessToken || null,
3655
- user: sessionData,
3656
- // sessionData del nuevo sistema (más rico que legacy)
3657
- tokenExpiration,
3658
- // Acciones (compatible con legacy + mejorado)
3659
- setToken,
3660
- logout,
3661
- refreshToken: refreshTokens,
3662
- // Funcionalidad real en el nuevo sistema
3663
- // Nuevas funcionalidades del sistema de refresh tokens
3664
- login,
3665
- isExpiringSoon,
3666
- expiresIn,
3667
- refreshExpiresIn,
3668
- getTokenInfo,
3669
- clearError
3778
+ try {
3779
+ const decoded = decodeJwtSafely(sessionHook.tokens.accessToken);
3780
+ if (decoded && decoded.sub && decoded.email && decoded.subscriber) {
3781
+ const result = {
3782
+ _id: decoded.sub,
3783
+ email: decoded.email,
3784
+ subscriberKey: decoded.subscriber
3785
+ };
3786
+ Object.keys(decoded).forEach((key) => {
3787
+ if (!["sub", "email", "subscriber"].includes(key)) {
3788
+ result[key] = decoded[key];
3789
+ }
3790
+ });
3791
+ return result;
3792
+ }
3793
+ } catch (error) {
3794
+ console.error("Error decoding JWT token for sessionData:", error);
3795
+ }
3796
+ return null;
3797
+ }, [sessionHook.tokens?.accessToken, sessionHook.isAuthenticated]);
3798
+ const contextValue = {
3799
+ ...sessionHook,
3800
+ sessionData
3670
3801
  };
3671
- };
3802
+ return /* @__PURE__ */ jsx14(SessionContext.Provider, { value: contextValue, children });
3803
+ }
3804
+ function useSessionContext() {
3805
+ const context = useContext4(SessionContext);
3806
+ if (context === void 0) {
3807
+ throw new Error("useSessionContext must be used within a SessionProvider");
3808
+ }
3809
+ return context;
3810
+ }
3811
+ function ProtectedRoute({
3812
+ children,
3813
+ fallback = /* @__PURE__ */ jsx14("div", { children: "Please log in to access this content" }),
3814
+ redirectTo
3815
+ }) {
3816
+ const { isAuthenticated, isLoading, isInitialized } = useSessionContext();
3817
+ if (!isInitialized || isLoading) {
3818
+ return /* @__PURE__ */ jsx14("div", { children: "Loading..." });
3819
+ }
3820
+ if (!isAuthenticated) {
3821
+ if (redirectTo) {
3822
+ redirectTo();
3823
+ return null;
3824
+ }
3825
+ return /* @__PURE__ */ jsx14(Fragment8, { children: fallback });
3826
+ }
3827
+ return /* @__PURE__ */ jsx14(Fragment8, { children });
3828
+ }
3829
+ function SessionDebugInfo() {
3830
+ const session = useSessionContext();
3831
+ if (!session.isInitialized) {
3832
+ return /* @__PURE__ */ jsx14("div", { children: "Session not initialized" });
3833
+ }
3834
+ return /* @__PURE__ */ jsxs11("div", { style: {
3835
+ padding: "10px",
3836
+ margin: "10px",
3837
+ border: "1px solid #ccc",
3838
+ borderRadius: "4px",
3839
+ fontSize: "12px",
3840
+ fontFamily: "monospace"
3841
+ }, children: [
3842
+ /* @__PURE__ */ jsx14("h4", { children: "Session Debug Info" }),
3843
+ /* @__PURE__ */ jsxs11("div", { children: [
3844
+ /* @__PURE__ */ jsx14("strong", { children: "Authenticated:" }),
3845
+ " ",
3846
+ session.isAuthenticated ? "Yes" : "No"
3847
+ ] }),
3848
+ /* @__PURE__ */ jsxs11("div", { children: [
3849
+ /* @__PURE__ */ jsx14("strong", { children: "Loading:" }),
3850
+ " ",
3851
+ session.isLoading ? "Yes" : "No"
3852
+ ] }),
3853
+ /* @__PURE__ */ jsxs11("div", { children: [
3854
+ /* @__PURE__ */ jsx14("strong", { children: "Error:" }),
3855
+ " ",
3856
+ session.error || "None"
3857
+ ] }),
3858
+ session.tokens && /* @__PURE__ */ jsxs11(Fragment8, { children: [
3859
+ /* @__PURE__ */ jsxs11("div", { children: [
3860
+ /* @__PURE__ */ jsx14("strong", { children: "Access Token:" }),
3861
+ " ",
3862
+ session.tokens.accessToken.substring(0, 20),
3863
+ "..."
3864
+ ] }),
3865
+ /* @__PURE__ */ jsxs11("div", { children: [
3866
+ /* @__PURE__ */ jsx14("strong", { children: "Refresh Token:" }),
3867
+ " ",
3868
+ session.tokens.refreshToken.substring(0, 20),
3869
+ "..."
3870
+ ] }),
3871
+ /* @__PURE__ */ jsxs11("div", { children: [
3872
+ /* @__PURE__ */ jsx14("strong", { children: "Access Expires In:" }),
3873
+ " ",
3874
+ Math.round(session.expiresIn / 1e3 / 60),
3875
+ " minutes"
3876
+ ] }),
3877
+ /* @__PURE__ */ jsxs11("div", { children: [
3878
+ /* @__PURE__ */ jsx14("strong", { children: "Refresh Expires In:" }),
3879
+ " ",
3880
+ Math.round(session.refreshExpiresIn / 1e3 / 60 / 60),
3881
+ " hours"
3882
+ ] }),
3883
+ /* @__PURE__ */ jsxs11("div", { children: [
3884
+ /* @__PURE__ */ jsx14("strong", { children: "Expiring Soon:" }),
3885
+ " ",
3886
+ session.isExpiringSoon ? "Yes" : "No"
3887
+ ] })
3888
+ ] })
3889
+ ] });
3890
+ }
3672
3891
 
3673
- // src/hooks/useData.ts
3674
- import { useCallback as useCallback8 } from "react";
3675
- import crudify8 from "@nocios/crudify-browser";
3676
- var useData = () => {
3892
+ // src/components/LoginComponent.tsx
3893
+ import { useState as useState11 } from "react";
3894
+ import { Button as Button8, TextField as TextField5, Box as Box11, Alert as Alert8, Typography as Typography11, CircularProgress as CircularProgress7 } from "@mui/material";
3895
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
3896
+ function LoginComponent() {
3897
+ const [email, setEmail] = useState11("");
3898
+ const [password, setPassword] = useState11("");
3899
+ const [showForm, setShowForm] = useState11(false);
3677
3900
  const {
3678
- isInitialized,
3901
+ isAuthenticated,
3679
3902
  isLoading,
3680
3903
  error,
3681
- isAuthenticated,
3682
- login: sessionLogin
3904
+ login,
3905
+ logout,
3906
+ refreshTokens,
3907
+ clearError,
3908
+ isExpiringSoon,
3909
+ expiresIn
3683
3910
  } = useSessionContext();
3684
- const isReady = useCallback8(() => {
3685
- return isInitialized && !isLoading && !error;
3686
- }, [isInitialized, isLoading, error]);
3687
- const waitForReady = useCallback8(async () => {
3688
- return new Promise((resolve, reject) => {
3689
- const checkReady = () => {
3690
- if (isReady()) {
3691
- resolve();
3692
- } else if (error) {
3693
- reject(new Error(error));
3694
- } else {
3695
- setTimeout(checkReady, 100);
3696
- }
3697
- };
3698
- checkReady();
3699
- });
3700
- }, [isReady, error]);
3701
- const ensureReady = useCallback8(async () => {
3702
- if (!isReady()) {
3703
- throw new Error("System not ready. Check isInitialized, isLoading, and error states.");
3704
- }
3705
- }, [isReady]);
3706
- const readItems = useCallback8(async (moduleKey, filter, options) => {
3707
- await ensureReady();
3708
- return await crudify8.readItems(moduleKey, filter || {}, options);
3709
- }, [ensureReady]);
3710
- const readItem = useCallback8(async (moduleKey, filter, options) => {
3711
- await ensureReady();
3712
- return await crudify8.readItem(moduleKey, filter, options);
3713
- }, [ensureReady]);
3714
- const createItem = useCallback8(async (moduleKey, data, options) => {
3715
- await ensureReady();
3716
- return await crudify8.createItem(moduleKey, data, options);
3717
- }, [ensureReady]);
3718
- const updateItem = useCallback8(async (moduleKey, data, options) => {
3719
- await ensureReady();
3720
- return await crudify8.updateItem(moduleKey, data, options);
3721
- }, [ensureReady]);
3722
- const deleteItem = useCallback8(async (moduleKey, id, options) => {
3723
- await ensureReady();
3724
- return await crudify8.deleteItem(moduleKey, id, options);
3725
- }, [ensureReady]);
3726
- const transaction = useCallback8(async (operations, options) => {
3727
- await ensureReady();
3728
- return await crudify8.transaction(operations, options);
3729
- }, [ensureReady]);
3730
- const login = useCallback8(async (email, password) => {
3731
- try {
3732
- const result = await sessionLogin(email, password);
3733
- if (result.success) {
3734
- return {
3735
- success: true,
3736
- data: result.tokens
3737
- };
3738
- } else {
3739
- return {
3740
- success: false,
3741
- errors: result.error || "Login failed"
3742
- };
3743
- }
3744
- } catch (error2) {
3745
- return {
3746
- success: false,
3747
- errors: error2 instanceof Error ? error2.message : "Login failed"
3748
- };
3911
+ const handleLogin = async (e) => {
3912
+ e.preventDefault();
3913
+ if (!email || !password) {
3914
+ return;
3749
3915
  }
3750
- }, [sessionLogin]);
3751
- return {
3752
- // CRUD operations
3753
- readItems,
3754
- readItem,
3755
- createItem,
3756
- updateItem,
3757
- deleteItem,
3758
- transaction,
3759
- login,
3760
- // Estado
3761
- isInitialized,
3762
- isInitializing: isLoading,
3763
- // El nuevo sistema usa isLoading
3764
- initializationError: error,
3765
- // Utilidades
3766
- isReady,
3767
- waitForReady
3768
- };
3769
- };
3770
-
3771
- // src/components/PublicPolicies/Policies.tsx
3772
- import { useRef as useRef6 } from "react";
3773
- import { useTranslation as useTranslation4 } from "react-i18next";
3774
- import {
3775
- Box as Box11,
3776
- Typography as Typography11,
3777
- Button as Button8,
3778
- Stack as Stack3,
3779
- Alert as Alert8,
3780
- Divider as Divider3
3781
- } from "@mui/material";
3782
- import { Add } from "@mui/icons-material";
3783
-
3784
- // src/components/PublicPolicies/PolicyItem/PolicyItem.tsx
3785
- import { forwardRef } from "react";
3786
- import { useTranslation as useTranslation3 } from "react-i18next";
3787
- import {
3788
- Box as Box10,
3789
- FormControl,
3790
- InputLabel,
3791
- Select,
3792
- MenuItem,
3793
- IconButton as IconButton2,
3794
- Typography as Typography10,
3795
- FormHelperText as FormHelperText2,
3796
- Stack as Stack2,
3797
- Paper,
3798
- Divider as Divider2,
3799
- Button as Button7
3800
- } from "@mui/material";
3801
- import { Delete, SelectAll as SelectAll2, ClearAll as ClearAll2 } from "@mui/icons-material";
3802
-
3803
- // src/components/PublicPolicies/FieldSelector/FieldSelector.tsx
3804
- import { useState as useState12, useEffect as useEffect11, useRef as useRef5 } from "react";
3805
- import { useTranslation as useTranslation2 } from "react-i18next";
3806
- import {
3807
- Box as Box9,
3808
- Typography as Typography9,
3809
- Button as Button6,
3810
- Stack,
3811
- FormHelperText,
3812
- ToggleButton,
3813
- ToggleButtonGroup
3814
- } from "@mui/material";
3815
- import {
3816
- CheckCircle,
3817
- Cancel,
3818
- SelectAll,
3819
- ClearAll
3820
- } from "@mui/icons-material";
3821
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
3822
- var FieldSelector = ({
3823
- value,
3824
- onChange,
3825
- availableFields,
3826
- error,
3827
- disabled = false
3828
- }) => {
3829
- const { t } = useTranslation2();
3830
- const [mode, setMode] = useState12("custom");
3831
- const isUpdatingRef = useRef5(false);
3832
- useEffect11(() => {
3833
- const current = value || { allow: [], owner_allow: [], deny: [] };
3834
- const all = new Set(availableFields);
3835
- const allow = (current.allow || []).filter((f) => all.has(f));
3836
- const owner = (current.owner_allow || []).filter((f) => all.has(f));
3837
- const deny = (current.deny || []).filter((f) => all.has(f));
3838
- availableFields.forEach((f) => {
3839
- if (!allow.includes(f) && !owner.includes(f) && !deny.includes(f)) deny.push(f);
3840
- });
3841
- const normalized = { allow, owner_allow: owner, deny };
3842
- if (JSON.stringify(normalized) !== JSON.stringify(current)) {
3843
- onChange(normalized);
3916
+ const result = await login(email, password);
3917
+ if (result.success) {
3918
+ setEmail("");
3919
+ setPassword("");
3920
+ setShowForm(false);
3844
3921
  }
3845
- if (allow.length === availableFields.length) setMode("all");
3846
- else if (deny.length === availableFields.length) setMode("none");
3847
- else setMode("custom");
3848
- }, [availableFields, value]);
3849
- const setAllAllow = () => {
3850
- isUpdatingRef.current = true;
3851
- onChange({ allow: [...availableFields], owner_allow: [], deny: [] });
3852
- setMode("all");
3853
- setTimeout(() => {
3854
- isUpdatingRef.current = false;
3855
- }, 0);
3856
- };
3857
- const setAllDeny = () => {
3858
- isUpdatingRef.current = true;
3859
- onChange({ allow: [], owner_allow: [], deny: [...availableFields] });
3860
- setMode("none");
3861
- setTimeout(() => {
3862
- isUpdatingRef.current = false;
3863
- }, 0);
3864
3922
  };
3865
- const getFieldState = (fieldName) => {
3866
- if (value?.allow?.includes(fieldName)) return "allow";
3867
- if (value?.owner_allow?.includes(fieldName)) return "owner_allow";
3868
- return "deny";
3923
+ const handleLogout = async () => {
3924
+ await logout();
3869
3925
  };
3870
- const setFieldState = (fieldName, state) => {
3871
- isUpdatingRef.current = true;
3872
- const allow = new Set(value?.allow || []);
3873
- const owner = new Set(value?.owner_allow || []);
3874
- const deny = new Set(value?.deny || []);
3875
- allow.delete(fieldName);
3876
- owner.delete(fieldName);
3877
- deny.delete(fieldName);
3878
- if (state === "allow") allow.add(fieldName);
3879
- if (state === "owner_allow") owner.add(fieldName);
3880
- if (state === "deny") deny.add(fieldName);
3881
- onChange({ allow: Array.from(allow), owner_allow: Array.from(owner), deny: Array.from(deny) });
3882
- setMode("custom");
3883
- setTimeout(() => {
3884
- isUpdatingRef.current = false;
3885
- }, 0);
3926
+ const handleRefreshTokens = async () => {
3927
+ await refreshTokens();
3886
3928
  };
3887
- if (availableFields.length === 0) {
3888
- return /* @__PURE__ */ jsxs10(Box9, { children: [
3889
- /* @__PURE__ */ jsx13(Typography9, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: t("modules.form.publicPolicies.fields.conditions.label") }),
3890
- /* @__PURE__ */ jsx13(Typography9, { variant: "body2", color: "text.secondary", sx: { fontStyle: "italic" }, children: t("modules.form.publicPolicies.fields.conditions.noFieldsAvailable") }),
3891
- error && /* @__PURE__ */ jsx13(FormHelperText, { error: true, sx: { mt: 1 }, children: error })
3929
+ if (isAuthenticated) {
3930
+ return /* @__PURE__ */ jsxs12(Box11, { sx: { maxWidth: 600, mx: "auto", p: 3 }, children: [
3931
+ /* @__PURE__ */ jsx15(Typography11, { variant: "h4", gutterBottom: true, children: "Welcome! \u{1F389}" }),
3932
+ /* @__PURE__ */ jsx15(Alert8, { severity: "success", sx: { mb: 3 }, children: "You are successfully logged in with Refresh Token Pattern enabled" }),
3933
+ /* @__PURE__ */ jsxs12(Box11, { sx: { mb: 3, p: 2, bgcolor: "background.paper", border: 1, borderColor: "divider", borderRadius: 1 }, children: [
3934
+ /* @__PURE__ */ jsx15(Typography11, { variant: "h6", gutterBottom: true, children: "Token Status" }),
3935
+ /* @__PURE__ */ jsxs12(Typography11, { variant: "body2", color: "text.secondary", children: [
3936
+ "Access Token expires in: ",
3937
+ Math.round(expiresIn / 1e3 / 60),
3938
+ " minutes"
3939
+ ] }),
3940
+ isExpiringSoon && /* @__PURE__ */ jsx15(Alert8, { severity: "warning", sx: { mt: 1 }, children: "Token expires soon - automatic refresh will happen" })
3941
+ ] }),
3942
+ /* @__PURE__ */ jsxs12(Box11, { sx: { display: "flex", gap: 2, flexWrap: "wrap" }, children: [
3943
+ /* @__PURE__ */ jsx15(
3944
+ Button8,
3945
+ {
3946
+ variant: "contained",
3947
+ onClick: handleRefreshTokens,
3948
+ disabled: isLoading,
3949
+ startIcon: isLoading ? /* @__PURE__ */ jsx15(CircularProgress7, { size: 16 }) : null,
3950
+ children: "Refresh Tokens"
3951
+ }
3952
+ ),
3953
+ /* @__PURE__ */ jsx15(
3954
+ Button8,
3955
+ {
3956
+ variant: "outlined",
3957
+ color: "error",
3958
+ onClick: handleLogout,
3959
+ disabled: isLoading,
3960
+ children: "Logout"
3961
+ }
3962
+ )
3963
+ ] }),
3964
+ error && /* @__PURE__ */ jsx15(Alert8, { severity: "error", sx: { mt: 2 }, onClose: clearError, children: error })
3892
3965
  ] });
3893
3966
  }
3894
- return /* @__PURE__ */ jsxs10(Box9, { children: [
3895
- /* @__PURE__ */ jsx13(Typography9, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: t("modules.form.publicPolicies.fields.conditions.label") }),
3896
- /* @__PURE__ */ jsxs10(Stack, { direction: "row", spacing: 1, sx: { mb: 3 }, children: [
3897
- /* @__PURE__ */ jsx13(
3898
- Button6,
3967
+ return /* @__PURE__ */ jsxs12(Box11, { sx: { maxWidth: 400, mx: "auto", p: 3 }, children: [
3968
+ /* @__PURE__ */ jsx15(Typography11, { variant: "h4", gutterBottom: true, align: "center", children: "Login with Refresh Tokens" }),
3969
+ /* @__PURE__ */ jsx15(Alert8, { severity: "info", sx: { mb: 3 }, children: "This demo shows the new Refresh Token Pattern with automatic session management" }),
3970
+ !showForm ? /* @__PURE__ */ jsx15(
3971
+ Button8,
3972
+ {
3973
+ fullWidth: true,
3974
+ variant: "contained",
3975
+ size: "large",
3976
+ onClick: () => setShowForm(true),
3977
+ sx: { mt: 2 },
3978
+ children: "Show Login Form"
3979
+ }
3980
+ ) : /* @__PURE__ */ jsxs12("form", { onSubmit: handleLogin, children: [
3981
+ /* @__PURE__ */ jsx15(
3982
+ TextField5,
3899
3983
  {
3900
- variant: mode === "all" ? "contained" : "outlined",
3901
- startIcon: /* @__PURE__ */ jsx13(SelectAll, {}),
3902
- onClick: setAllAllow,
3903
- disabled,
3904
- size: "small",
3905
- sx: {
3906
- minWidth: 120,
3907
- ...mode === "all" && {
3908
- backgroundColor: "#16a34a",
3909
- "&:hover": { backgroundColor: "#15803d" }
3910
- }
3911
- },
3912
- children: t("modules.form.publicPolicies.fields.conditions.allFields")
3984
+ fullWidth: true,
3985
+ label: "Email",
3986
+ type: "email",
3987
+ value: email,
3988
+ onChange: (e) => setEmail(e.target.value),
3989
+ margin: "normal",
3990
+ required: true,
3991
+ autoComplete: "email"
3992
+ }
3993
+ ),
3994
+ /* @__PURE__ */ jsx15(
3995
+ TextField5,
3996
+ {
3997
+ fullWidth: true,
3998
+ label: "Password",
3999
+ type: "password",
4000
+ value: password,
4001
+ onChange: (e) => setPassword(e.target.value),
4002
+ margin: "normal",
4003
+ required: true,
4004
+ autoComplete: "current-password"
3913
4005
  }
3914
4006
  ),
3915
- /* @__PURE__ */ jsx13(
3916
- Button6,
4007
+ /* @__PURE__ */ jsx15(
4008
+ Button8,
3917
4009
  {
3918
- variant: mode === "none" ? "contained" : "outlined",
3919
- startIcon: /* @__PURE__ */ jsx13(ClearAll, {}),
3920
- onClick: setAllDeny,
3921
- disabled,
3922
- size: "small",
3923
- sx: {
3924
- minWidth: 120,
3925
- ...mode === "none" && {
3926
- backgroundColor: "#cf222e",
3927
- "&:hover": { backgroundColor: "#bc1f2c" }
3928
- }
3929
- },
3930
- children: t("modules.form.publicPolicies.fields.conditions.noFields")
4010
+ type: "submit",
4011
+ fullWidth: true,
4012
+ variant: "contained",
4013
+ size: "large",
4014
+ disabled: isLoading,
4015
+ startIcon: isLoading ? /* @__PURE__ */ jsx15(CircularProgress7, { size: 16 }) : null,
4016
+ sx: { mt: 3, mb: 2 },
4017
+ children: isLoading ? "Logging in..." : "Login"
3931
4018
  }
3932
4019
  )
3933
4020
  ] }),
3934
- /* @__PURE__ */ jsxs10(Box9, { sx: { p: 2, border: "1px solid #d1d9e0", borderRadius: 1, backgroundColor: "#f6f8fa" }, children: [
3935
- /* @__PURE__ */ jsx13(Typography9, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: t("modules.form.publicPolicies.fields.conditions.help") }),
3936
- /* @__PURE__ */ jsx13(Stack, { spacing: 1, children: availableFields.map((fieldName) => {
3937
- const fieldState = getFieldState(fieldName);
3938
- return /* @__PURE__ */ jsxs10(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [
3939
- /* @__PURE__ */ jsx13(Typography9, { variant: "body2", sx: { minWidth: 100, fontFamily: "monospace" }, children: fieldName }),
3940
- /* @__PURE__ */ jsxs10(ToggleButtonGroup, { value: fieldState, exclusive: true, size: "small", children: [
3941
- /* @__PURE__ */ jsxs10(
3942
- ToggleButton,
3943
- {
3944
- value: "allow",
3945
- onClick: () => setFieldState(fieldName, "allow"),
3946
- disabled,
3947
- sx: {
3948
- px: 2,
3949
- color: fieldState === "allow" ? "#ffffff" : "#6b7280",
3950
- backgroundColor: fieldState === "allow" ? "#16a34a" : "#f3f4f6",
3951
- borderColor: fieldState === "allow" ? "#16a34a" : "#d1d5db",
3952
- "&:hover": {
3953
- backgroundColor: fieldState === "allow" ? "#15803d" : "#e5e7eb",
3954
- borderColor: fieldState === "allow" ? "#15803d" : "#9ca3af"
3955
- },
3956
- "&.Mui-selected": {
3957
- backgroundColor: "#16a34a",
3958
- color: "#ffffff",
3959
- "&:hover": {
3960
- backgroundColor: "#15803d"
3961
- }
3962
- }
3963
- },
3964
- children: [
3965
- /* @__PURE__ */ jsx13(CheckCircle, { sx: { fontSize: 16, mr: 0.5 } }),
3966
- t("modules.form.publicPolicies.fields.conditions.states.allow")
3967
- ]
3968
- }
3969
- ),
3970
- /* @__PURE__ */ jsx13(
3971
- ToggleButton,
3972
- {
3973
- value: "owner_allow",
3974
- onClick: () => setFieldState(fieldName, "owner_allow"),
3975
- disabled,
3976
- sx: {
3977
- px: 2,
3978
- color: fieldState === "owner_allow" ? "#ffffff" : "#6b7280",
3979
- backgroundColor: fieldState === "owner_allow" ? "#0ea5e9" : "#f3f4f6",
3980
- borderColor: fieldState === "owner_allow" ? "#0ea5e9" : "#d1d5db",
3981
- "&:hover": {
3982
- backgroundColor: fieldState === "owner_allow" ? "#0284c7" : "#e5e7eb",
3983
- borderColor: fieldState === "owner_allow" ? "#0284c7" : "#9ca3af"
3984
- },
3985
- "&.Mui-selected": {
3986
- backgroundColor: "#0ea5e9",
3987
- color: "#ffffff",
3988
- "&:hover": {
3989
- backgroundColor: "#0284c7"
3990
- }
3991
- }
3992
- },
3993
- children: t("modules.form.publicPolicies.fields.conditions.states.ownerAllow")
3994
- }
3995
- ),
3996
- /* @__PURE__ */ jsxs10(
3997
- ToggleButton,
3998
- {
3999
- value: "deny",
4000
- onClick: () => setFieldState(fieldName, "deny"),
4001
- disabled,
4002
- sx: {
4003
- px: 2,
4004
- color: fieldState === "deny" ? "#ffffff" : "#6b7280",
4005
- backgroundColor: fieldState === "deny" ? "#dc2626" : "#f3f4f6",
4006
- borderColor: fieldState === "deny" ? "#dc2626" : "#d1d5db",
4007
- "&:hover": {
4008
- backgroundColor: fieldState === "deny" ? "#b91c1c" : "#e5e7eb",
4009
- borderColor: fieldState === "deny" ? "#b91c1c" : "#9ca3af"
4010
- },
4011
- "&.Mui-selected": {
4012
- backgroundColor: "#dc2626",
4013
- color: "#ffffff",
4014
- "&:hover": {
4015
- backgroundColor: "#b91c1c"
4016
- }
4017
- }
4018
- },
4019
- children: [
4020
- /* @__PURE__ */ jsx13(Cancel, { sx: { fontSize: 16, mr: 0.5 } }),
4021
- t("modules.form.publicPolicies.fields.conditions.states.deny")
4022
- ]
4023
- }
4024
- )
4025
- ] })
4026
- ] }, fieldName);
4027
- }) })
4028
- ] }),
4029
- error && /* @__PURE__ */ jsx13(FormHelperText, { error: true, sx: { mt: 1 }, children: error })
4021
+ error && /* @__PURE__ */ jsx15(Alert8, { severity: "error", sx: { mt: 2 }, onClose: clearError, children: error })
4030
4022
  ] });
4031
- };
4032
- var FieldSelector_default = FieldSelector;
4033
-
4034
- // src/components/PublicPolicies/constants.ts
4035
- var POLICY_ACTIONS = ["create", "read", "update", "delete"];
4036
- var PREFERRED_POLICY_ORDER = [
4037
- "create",
4038
- "read",
4039
- "update",
4040
- "delete"
4041
- ];
4023
+ }
4024
+ function SessionStatus() {
4025
+ const { isAuthenticated, isLoading, isExpiringSoon, expiresIn } = useSessionContext();
4026
+ if (isLoading) {
4027
+ return /* @__PURE__ */ jsxs12(Box11, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
4028
+ /* @__PURE__ */ jsx15(CircularProgress7, { size: 16 }),
4029
+ /* @__PURE__ */ jsx15(Typography11, { variant: "caption", children: "Loading session..." })
4030
+ ] });
4031
+ }
4032
+ if (!isAuthenticated) {
4033
+ return /* @__PURE__ */ jsx15(Typography11, { variant: "caption", color: "text.secondary", children: "Not logged in" });
4034
+ }
4035
+ return /* @__PURE__ */ jsxs12(Box11, { children: [
4036
+ /* @__PURE__ */ jsx15(Typography11, { variant: "caption", color: "success.main", children: "\u2713 Authenticated" }),
4037
+ isExpiringSoon && /* @__PURE__ */ jsxs12(Typography11, { variant: "caption", color: "warning.main", display: "block", children: [
4038
+ "\u26A0 Token expires in ",
4039
+ Math.round(expiresIn / 1e3 / 60),
4040
+ " min"
4041
+ ] })
4042
+ ] });
4043
+ }
4042
4044
 
4043
- // src/components/PublicPolicies/PolicyItem/PolicyItem.tsx
4044
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
4045
- var PolicyItem = forwardRef(({
4046
- policy,
4047
- onChange,
4048
- onRemove,
4049
- availableFields,
4050
- isSubmitting = false,
4051
- usedActions,
4052
- error
4053
- }, ref) => {
4054
- const { t } = useTranslation3();
4055
- const takenActions = new Set(Array.from(usedActions || []));
4056
- takenActions.delete(policy.action);
4057
- const actionOptions = POLICY_ACTIONS.map((a) => ({
4058
- value: a,
4059
- label: t(`modules.form.publicPolicies.fields.action.options.${a}`)
4060
- }));
4061
- return /* @__PURE__ */ jsxs11(
4062
- Paper,
4063
- {
4064
- ref,
4065
- sx: {
4066
- p: 3,
4067
- border: "1px solid #d1d9e0",
4068
- borderRadius: 2,
4069
- position: "relative",
4070
- backgroundColor: "#ffffff"
4071
- },
4072
- children: [
4073
- /* @__PURE__ */ jsxs11(Box10, { sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", mb: 3 }, children: [
4074
- /* @__PURE__ */ jsx14(
4075
- Typography10,
4076
- {
4077
- variant: "subtitle1",
4078
- sx: {
4079
- fontWeight: 600,
4080
- color: "#111418",
4081
- fontSize: "1rem"
4082
- },
4083
- children: t("modules.form.publicPolicies.policyTitle")
4045
+ // src/hooks/useUserData.ts
4046
+ import { useState as useState12, useEffect as useEffect11, useCallback as useCallback6, useRef as useRef6 } from "react";
4047
+ import crudify7 from "@nocios/crudify-browser";
4048
+ var useUserData = (options = {}) => {
4049
+ const { autoFetch = true, retryOnError = false, maxRetries = 3 } = options;
4050
+ const { isAuthenticated, isInitialized, sessionData, tokens } = useSessionContext();
4051
+ const [userData, setUserData] = useState12(null);
4052
+ const [loading, setLoading] = useState12(false);
4053
+ const [error, setError] = useState12(null);
4054
+ const abortControllerRef = useRef6(null);
4055
+ const mountedRef = useRef6(true);
4056
+ const requestIdRef = useRef6(0);
4057
+ const retryCountRef = useRef6(0);
4058
+ const getUserEmail = useCallback6(() => {
4059
+ if (!sessionData) return null;
4060
+ return sessionData.email || sessionData["cognito:username"] || null;
4061
+ }, [sessionData]);
4062
+ const clearProfile = useCallback6(() => {
4063
+ setUserData(null);
4064
+ setError(null);
4065
+ setLoading(false);
4066
+ retryCountRef.current = 0;
4067
+ }, []);
4068
+ const refreshProfile = useCallback6(async () => {
4069
+ const userEmail = getUserEmail();
4070
+ console.log("\u{1F464} useUserData - Refreshing profile for:", userEmail);
4071
+ if (!userEmail) {
4072
+ if (mountedRef.current) {
4073
+ setError("No user email available from session data");
4074
+ setLoading(false);
4075
+ }
4076
+ return;
4077
+ }
4078
+ if (!isInitialized) {
4079
+ if (mountedRef.current) {
4080
+ setError("Session not initialized");
4081
+ setLoading(false);
4082
+ }
4083
+ return;
4084
+ }
4085
+ if (abortControllerRef.current) {
4086
+ abortControllerRef.current.abort();
4087
+ }
4088
+ const abortController = new AbortController();
4089
+ abortControllerRef.current = abortController;
4090
+ const currentRequestId = ++requestIdRef.current;
4091
+ try {
4092
+ if (mountedRef.current) {
4093
+ setLoading(true);
4094
+ setError(null);
4095
+ }
4096
+ console.log("\u{1F464} useUserData - Fetching profile data from database");
4097
+ const response = await crudify7.readItems("users", {
4098
+ filter: { email: userEmail },
4099
+ pagination: { limit: 1 }
4100
+ });
4101
+ console.log("\u{1F464} useUserData - Database response:", response);
4102
+ console.log("\u{1F464} useUserData - response.data:", response.data);
4103
+ console.log("\u{1F464} useUserData - response.data type:", typeof response.data);
4104
+ if (currentRequestId === requestIdRef.current && mountedRef.current && !abortController.signal.aborted) {
4105
+ let userData2 = null;
4106
+ if (response.success) {
4107
+ console.log("\u{1F464} useUserData - Processing successful response:", {
4108
+ dataType: typeof response.data,
4109
+ isArray: Array.isArray(response.data),
4110
+ hasResponse: !!response.data?.response,
4111
+ hasResponseData: !!response.data?.response?.data,
4112
+ responseDataType: typeof response.data?.response?.data
4113
+ });
4114
+ if (Array.isArray(response.data) && response.data.length > 0) {
4115
+ console.log("\u{1F464} useUserData - Found direct array format");
4116
+ userData2 = response.data[0];
4117
+ } else if (response.data?.response?.data) {
4118
+ console.log("\u{1F464} useUserData - Found nested response.data format");
4119
+ try {
4120
+ const rawData = response.data.response.data;
4121
+ console.log("\u{1F464} useUserData - Raw nested data:", rawData);
4122
+ console.log("\u{1F464} useUserData - Raw data type:", typeof rawData);
4123
+ const parsedData = typeof rawData === "string" ? JSON.parse(rawData) : rawData;
4124
+ console.log("\u{1F464} useUserData - Parsed nested data:", parsedData);
4125
+ if (parsedData && parsedData.items && Array.isArray(parsedData.items) && parsedData.items.length > 0) {
4126
+ userData2 = parsedData.items[0];
4127
+ console.log("\u{1F464} useUserData - Extracted user from nested items:", userData2);
4128
+ } else {
4129
+ console.log("\u{1F464} useUserData - No items found in parsed data or items array is empty");
4130
+ }
4131
+ } catch (parseError) {
4132
+ console.error("\u{1F464} useUserData - Error parsing nested response data:", parseError);
4084
4133
  }
4085
- ),
4086
- /* @__PURE__ */ jsx14(
4087
- IconButton2,
4088
- {
4089
- onClick: onRemove,
4090
- size: "small",
4091
- disabled: isSubmitting,
4092
- "aria-label": t("modules.form.publicPolicies.removePolicy"),
4093
- sx: {
4094
- color: "#656d76",
4095
- "&:hover": {
4096
- color: "#cf222e",
4097
- backgroundColor: "rgba(207, 34, 46, 0.1)"
4098
- }
4099
- },
4100
- children: /* @__PURE__ */ jsx14(Delete, {})
4134
+ } else if (response.data && typeof response.data === "object") {
4135
+ console.log("\u{1F464} useUserData - Found object format, checking for items");
4136
+ if (response.data.items && Array.isArray(response.data.items) && response.data.items.length > 0) {
4137
+ console.log("\u{1F464} useUserData - Found items in object format");
4138
+ userData2 = response.data.items[0];
4139
+ } else {
4140
+ console.log("\u{1F464} useUserData - No items found in object format");
4101
4141
  }
4102
- )
4103
- ] }),
4104
- /* @__PURE__ */ jsxs11(Stack2, { spacing: 3, children: [
4105
- /* @__PURE__ */ jsx14(Stack2, { direction: { xs: "column", md: "row" }, spacing: 2, children: /* @__PURE__ */ jsx14(Box10, { sx: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxs11(FormControl, { fullWidth: true, children: [
4106
- /* @__PURE__ */ jsx14(InputLabel, { children: t("modules.form.publicPolicies.fields.action.label") }),
4107
- /* @__PURE__ */ jsx14(
4108
- Select,
4109
- {
4110
- value: policy.action,
4111
- label: t("modules.form.publicPolicies.fields.action.label"),
4112
- disabled: isSubmitting,
4113
- onChange: (e) => {
4114
- const newAction = e.target.value;
4115
- const next = { ...policy, action: newAction };
4116
- if (newAction === "delete") {
4117
- next.permission = "deny";
4118
- delete next.fields;
4119
- } else {
4120
- next.fields = { allow: [], owner_allow: [], deny: availableFields };
4121
- delete next.permission;
4122
- }
4123
- onChange(next);
4124
- },
4125
- sx: {
4126
- backgroundColor: "#ffffff",
4127
- "&:hover .MuiOutlinedInput-notchedOutline": {
4128
- borderColor: "#8c959f"
4129
- },
4130
- "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
4131
- borderColor: "#0969da",
4132
- borderWidth: 2
4133
- }
4134
- },
4135
- children: actionOptions.map((option) => {
4136
- const disabledOption = takenActions.has(option.value);
4137
- return /* @__PURE__ */ jsx14(MenuItem, { value: option.value, disabled: disabledOption, children: option.label }, option.value);
4138
- })
4142
+ } else if (response.data?.data?.response?.data) {
4143
+ console.log("\u{1F464} useUserData - Found double-nested data.data.response.data format");
4144
+ try {
4145
+ const rawData = response.data.data.response.data;
4146
+ console.log("\u{1F464} useUserData - Raw double-nested data:", rawData);
4147
+ const parsedData = typeof rawData === "string" ? JSON.parse(rawData) : rawData;
4148
+ console.log("\u{1F464} useUserData - Parsed double-nested data:", parsedData);
4149
+ if (parsedData && parsedData.items && Array.isArray(parsedData.items) && parsedData.items.length > 0) {
4150
+ userData2 = parsedData.items[0];
4151
+ console.log("\u{1F464} useUserData - Extracted user from double-nested items:", userData2);
4139
4152
  }
4140
- ),
4141
- error && /* @__PURE__ */ jsx14(FormHelperText2, { error: true, children: error })
4142
- ] }) }) }),
4143
- policy.action === "delete" ? /* @__PURE__ */ jsxs11(Box10, { children: [
4144
- /* @__PURE__ */ jsx14(Typography10, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: t("modules.form.publicPolicies.fields.conditions.label") }),
4145
- /* @__PURE__ */ jsxs11(Stack2, { direction: "row", spacing: 1, sx: { mb: 3 }, children: [
4146
- /* @__PURE__ */ jsx14(
4147
- Button7,
4148
- {
4149
- variant: policy.permission === "*" ? "contained" : "outlined",
4150
- startIcon: /* @__PURE__ */ jsx14(SelectAll2, {}),
4151
- onClick: () => onChange({ ...policy, permission: "*" }),
4152
- disabled: isSubmitting,
4153
- size: "small",
4154
- sx: {
4155
- minWidth: 140,
4156
- whiteSpace: "nowrap",
4157
- ...policy.permission === "*" && {
4158
- backgroundColor: "#16a34a",
4159
- "&:hover": { backgroundColor: "#15803d" }
4160
- }
4161
- },
4162
- children: t("modules.form.publicPolicies.fields.conditions.allFields")
4163
- }
4164
- ),
4165
- /* @__PURE__ */ jsx14(
4166
- Button7,
4167
- {
4168
- variant: policy.permission === "owner" ? "contained" : "outlined",
4169
- onClick: () => onChange({ ...policy, permission: "owner" }),
4170
- disabled: isSubmitting,
4171
- size: "small",
4172
- sx: {
4173
- minWidth: 140,
4174
- whiteSpace: "nowrap",
4175
- ...policy.permission === "owner" && {
4176
- backgroundColor: "#0ea5e9",
4177
- "&:hover": { backgroundColor: "#0284c7" }
4178
- }
4179
- },
4180
- children: t("modules.form.publicPolicies.fields.conditions.states.ownerAllow")
4181
- }
4182
- ),
4183
- /* @__PURE__ */ jsx14(
4184
- Button7,
4185
- {
4186
- variant: policy.permission === "deny" ? "contained" : "outlined",
4187
- startIcon: /* @__PURE__ */ jsx14(ClearAll2, {}),
4188
- onClick: () => onChange({ ...policy, permission: "deny" }),
4189
- disabled: isSubmitting,
4190
- size: "small",
4191
- sx: {
4192
- minWidth: 140,
4193
- whiteSpace: "nowrap",
4194
- ...policy.permission === "deny" && {
4195
- backgroundColor: "#cf222e",
4196
- "&:hover": { backgroundColor: "#bc1f2c" }
4197
- }
4198
- },
4199
- children: t("modules.form.publicPolicies.fields.conditions.noFields")
4200
- }
4201
- )
4202
- ] })
4203
- ] }) : /* @__PURE__ */ jsx14(
4204
- FieldSelector_default,
4205
- {
4206
- value: policy.fields || { allow: [], owner_allow: [], deny: [] },
4207
- onChange: (nextFields) => onChange({ ...policy, fields: nextFields }),
4208
- availableFields,
4209
- disabled: isSubmitting
4153
+ } catch (parseError) {
4154
+ console.error("\u{1F464} useUserData - Error parsing double-nested response data:", parseError);
4210
4155
  }
4211
- ),
4212
- /* @__PURE__ */ jsx14(Paper, { variant: "outlined", sx: { p: 2, backgroundColor: "#f9fafb" }, children: policy.action === "delete" ? /* @__PURE__ */ jsxs11(Typography10, { variant: "body2", sx: { fontFamily: "monospace", color: "text.secondary" }, children: [
4213
- /* @__PURE__ */ jsxs11(Box10, { component: "span", sx: {
4214
- color: policy.permission === "*" ? "#16a34a" : policy.permission === "owner" ? "#0ea5e9" : "#dc2626"
4215
- }, children: [
4216
- t("modules.form.publicPolicies.fields.conditions.states.allow"),
4217
- ":"
4218
- ] }),
4219
- " ",
4220
- policy.permission || "-"
4221
- ] }) : /* @__PURE__ */ jsxs11(Stack2, { spacing: 0.5, divider: /* @__PURE__ */ jsx14(Divider2, { sx: { borderColor: "#e5e7eb" } }), children: [
4222
- /* @__PURE__ */ jsxs11(Typography10, { variant: "body2", sx: { fontFamily: "monospace", color: "text.secondary" }, children: [
4223
- /* @__PURE__ */ jsxs11(Box10, { component: "span", sx: { color: "#16a34a" }, children: [
4224
- t("modules.form.publicPolicies.fields.conditions.states.allow"),
4225
- ":"
4226
- ] }),
4227
- " ",
4228
- (policy?.fields?.allow || []).join(", ") || "-"
4229
- ] }),
4230
- /* @__PURE__ */ jsxs11(Typography10, { variant: "body2", sx: { fontFamily: "monospace", color: "text.secondary" }, children: [
4231
- /* @__PURE__ */ jsxs11(Box10, { component: "span", sx: { color: "#0ea5e9" }, children: [
4232
- t("modules.form.publicPolicies.fields.conditions.states.ownerAllow"),
4233
- ":"
4234
- ] }),
4235
- " ",
4236
- (policy?.fields?.owner_allow || []).join(", ") || "-"
4237
- ] }),
4238
- /* @__PURE__ */ jsxs11(Typography10, { variant: "body2", sx: { fontFamily: "monospace", color: "text.secondary" }, children: [
4239
- /* @__PURE__ */ jsxs11(Box10, { component: "span", sx: { color: "#dc2626" }, children: [
4240
- t("modules.form.publicPolicies.fields.conditions.states.deny"),
4241
- ":"
4242
- ] }),
4243
- " ",
4244
- (policy?.fields?.deny || []).join(", ") || "-"
4245
- ] })
4246
- ] }) })
4247
- ] })
4248
- ]
4156
+ }
4157
+ }
4158
+ if (userData2) {
4159
+ console.log("\u{1F464} useUserData - User data found:", userData2);
4160
+ setUserData(userData2);
4161
+ setError(null);
4162
+ retryCountRef.current = 0;
4163
+ console.log("\u{1F464} useUserData - Profile loaded successfully:", userData2);
4164
+ } else {
4165
+ setError("User profile not found in database");
4166
+ setUserData(null);
4167
+ console.warn("\u{1F464} useUserData - User not found for email:", userEmail);
4168
+ }
4169
+ }
4170
+ } catch (err) {
4171
+ if (currentRequestId === requestIdRef.current && mountedRef.current) {
4172
+ const error2 = err;
4173
+ console.error("\u{1F464} useUserData - Error fetching profile:", error2);
4174
+ if (error2.name === "AbortError") {
4175
+ return;
4176
+ }
4177
+ const shouldRetry = retryOnError && retryCountRef.current < maxRetries && (error2.message?.includes("Network Error") || error2.message?.includes("Failed to fetch"));
4178
+ if (shouldRetry) {
4179
+ retryCountRef.current++;
4180
+ console.log(`\u{1F464} useUserData - Retrying profile fetch (${retryCountRef.current}/${maxRetries})`);
4181
+ setTimeout(() => {
4182
+ if (mountedRef.current) {
4183
+ refreshProfile();
4184
+ }
4185
+ }, 1e3 * retryCountRef.current);
4186
+ } else {
4187
+ setError("Failed to load user profile from database");
4188
+ setUserData(null);
4189
+ }
4190
+ }
4191
+ } finally {
4192
+ if (currentRequestId === requestIdRef.current && mountedRef.current) {
4193
+ setLoading(false);
4194
+ }
4195
+ if (abortControllerRef.current === abortController) {
4196
+ abortControllerRef.current = null;
4197
+ }
4249
4198
  }
4250
- );
4251
- });
4252
- var PolicyItem_default = PolicyItem;
4253
-
4254
- // src/components/PublicPolicies/Policies.tsx
4255
- import { Fragment as Fragment8, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
4256
- var generateId = () => {
4257
- const c = globalThis?.crypto;
4258
- if (c && typeof c.randomUUID === "function") return c.randomUUID();
4259
- return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
4260
- };
4261
- var Policies = ({
4262
- policies,
4263
- onChange,
4264
- availableFields,
4265
- errors,
4266
- isSubmitting = false
4267
- }) => {
4268
- const { t } = useTranslation4();
4269
- const policyRefs = useRef6({});
4270
- const takenActions = new Set((policies || []).map((p) => p.action).filter(Boolean));
4271
- const remainingActions = PREFERRED_POLICY_ORDER.filter((a) => !takenActions.has(a));
4272
- const canAddPolicy = remainingActions.length > 0;
4273
- const addPolicy = () => {
4274
- const defaultAction = remainingActions[0] || "create";
4275
- const newPolicy = {
4276
- id: generateId(),
4277
- action: defaultAction
4199
+ }, [isInitialized, getUserEmail, retryOnError, maxRetries]);
4200
+ useEffect11(() => {
4201
+ if (autoFetch && isAuthenticated && isInitialized) {
4202
+ refreshProfile();
4203
+ } else if (!isAuthenticated) {
4204
+ clearProfile();
4205
+ }
4206
+ }, [autoFetch, isAuthenticated, isInitialized, refreshProfile, clearProfile]);
4207
+ useEffect11(() => {
4208
+ mountedRef.current = true;
4209
+ return () => {
4210
+ mountedRef.current = false;
4211
+ if (abortControllerRef.current) {
4212
+ abortControllerRef.current.abort();
4213
+ abortControllerRef.current = null;
4214
+ }
4278
4215
  };
4279
- if (defaultAction === "delete") {
4280
- newPolicy.permission = "deny";
4216
+ }, []);
4217
+ const user = {
4218
+ session: sessionData,
4219
+ // Usar sessionData del nuevo sistema
4220
+ data: userData
4221
+ // Mantener userData del database igual que legacy
4222
+ };
4223
+ return {
4224
+ user,
4225
+ loading,
4226
+ error,
4227
+ refreshProfile,
4228
+ clearProfile
4229
+ };
4230
+ };
4231
+
4232
+ // src/hooks/useAuth.ts
4233
+ import { useCallback as useCallback7 } from "react";
4234
+ var useAuth = () => {
4235
+ const {
4236
+ isAuthenticated,
4237
+ isLoading,
4238
+ isInitialized,
4239
+ tokens,
4240
+ error,
4241
+ sessionData,
4242
+ login,
4243
+ logout,
4244
+ refreshTokens,
4245
+ clearError,
4246
+ getTokenInfo,
4247
+ isExpiringSoon,
4248
+ expiresIn,
4249
+ refreshExpiresIn
4250
+ } = useSessionContext();
4251
+ const setToken = useCallback7((token) => {
4252
+ if (token) {
4253
+ console.warn("useAuth.setToken() is deprecated. Use login() method instead for better security.");
4281
4254
  } else {
4282
- newPolicy.fields = {
4283
- allow: [],
4284
- owner_allow: [],
4285
- deny: availableFields
4255
+ logout();
4256
+ }
4257
+ }, [logout]);
4258
+ const tokenExpiration = tokens?.expiresAt ? new Date(tokens.expiresAt) : null;
4259
+ return {
4260
+ // Estado básico (compatible con legacy)
4261
+ isAuthenticated,
4262
+ loading: isLoading,
4263
+ // En el nuevo sistema se llama isLoading
4264
+ error,
4265
+ // Datos del token (compatible con legacy + mejorado)
4266
+ token: tokens?.accessToken || null,
4267
+ user: sessionData,
4268
+ // sessionData del nuevo sistema (más rico que legacy)
4269
+ tokenExpiration,
4270
+ // Acciones (compatible con legacy + mejorado)
4271
+ setToken,
4272
+ logout,
4273
+ refreshToken: refreshTokens,
4274
+ // Funcionalidad real en el nuevo sistema
4275
+ // Nuevas funcionalidades del sistema de refresh tokens
4276
+ login,
4277
+ isExpiringSoon,
4278
+ expiresIn,
4279
+ refreshExpiresIn,
4280
+ getTokenInfo,
4281
+ clearError
4282
+ };
4283
+ };
4284
+
4285
+ // src/hooks/useData.ts
4286
+ import { useCallback as useCallback8 } from "react";
4287
+ import crudify8 from "@nocios/crudify-browser";
4288
+ var useData = () => {
4289
+ const {
4290
+ isInitialized,
4291
+ isLoading,
4292
+ error,
4293
+ isAuthenticated,
4294
+ login: sessionLogin
4295
+ } = useSessionContext();
4296
+ const isReady = useCallback8(() => {
4297
+ return isInitialized && !isLoading && !error;
4298
+ }, [isInitialized, isLoading, error]);
4299
+ const waitForReady = useCallback8(async () => {
4300
+ return new Promise((resolve, reject) => {
4301
+ const checkReady = () => {
4302
+ if (isReady()) {
4303
+ resolve();
4304
+ } else if (error) {
4305
+ reject(new Error(error));
4306
+ } else {
4307
+ setTimeout(checkReady, 100);
4308
+ }
4286
4309
  };
4310
+ checkReady();
4311
+ });
4312
+ }, [isReady, error]);
4313
+ const ensureReady = useCallback8(async () => {
4314
+ if (!isReady()) {
4315
+ throw new Error("System not ready. Check isInitialized, isLoading, and error states.");
4287
4316
  }
4288
- const next = [...policies || [], newPolicy];
4289
- onChange(next);
4290
- setTimeout(() => {
4291
- const newIndex = next.length - 1;
4292
- const el = policyRefs.current[newIndex];
4293
- if (el) {
4294
- el.scrollIntoView({ behavior: "smooth", block: "center" });
4317
+ }, [isReady]);
4318
+ const readItems = useCallback8(async (moduleKey, filter, options) => {
4319
+ await ensureReady();
4320
+ return await crudify8.readItems(moduleKey, filter || {}, options);
4321
+ }, [ensureReady]);
4322
+ const readItem = useCallback8(async (moduleKey, filter, options) => {
4323
+ await ensureReady();
4324
+ return await crudify8.readItem(moduleKey, filter, options);
4325
+ }, [ensureReady]);
4326
+ const createItem = useCallback8(async (moduleKey, data, options) => {
4327
+ await ensureReady();
4328
+ return await crudify8.createItem(moduleKey, data, options);
4329
+ }, [ensureReady]);
4330
+ const updateItem = useCallback8(async (moduleKey, data, options) => {
4331
+ await ensureReady();
4332
+ return await crudify8.updateItem(moduleKey, data, options);
4333
+ }, [ensureReady]);
4334
+ const deleteItem = useCallback8(async (moduleKey, id, options) => {
4335
+ await ensureReady();
4336
+ return await crudify8.deleteItem(moduleKey, id, options);
4337
+ }, [ensureReady]);
4338
+ const transaction = useCallback8(async (operations, options) => {
4339
+ await ensureReady();
4340
+ return await crudify8.transaction(operations, options);
4341
+ }, [ensureReady]);
4342
+ const login = useCallback8(async (email, password) => {
4343
+ try {
4344
+ const result = await sessionLogin(email, password);
4345
+ if (result.success) {
4346
+ return {
4347
+ success: true,
4348
+ data: result.tokens
4349
+ };
4350
+ } else {
4351
+ return {
4352
+ success: false,
4353
+ errors: result.error || "Login failed"
4354
+ };
4295
4355
  }
4296
- }, 100);
4297
- };
4298
- const removePolicy = (index) => {
4299
- const next = [...policies];
4300
- next.splice(index, 1);
4301
- onChange(next);
4356
+ } catch (error2) {
4357
+ return {
4358
+ success: false,
4359
+ errors: error2 instanceof Error ? error2.message : "Login failed"
4360
+ };
4361
+ }
4362
+ }, [sessionLogin]);
4363
+ return {
4364
+ // CRUD operations
4365
+ readItems,
4366
+ readItem,
4367
+ createItem,
4368
+ updateItem,
4369
+ deleteItem,
4370
+ transaction,
4371
+ login,
4372
+ // Estado
4373
+ isInitialized,
4374
+ isInitializing: isLoading,
4375
+ // El nuevo sistema usa isLoading
4376
+ initializationError: error,
4377
+ // Utilidades
4378
+ isReady,
4379
+ waitForReady
4302
4380
  };
4303
- const arrayError = (() => {
4304
- if (!errors) return null;
4305
- if (typeof errors === "string") return errors;
4306
- const msg = errors._error;
4307
- return typeof msg === "string" ? msg : null;
4308
- })();
4309
- const usedActions = new Set((policies || []).map((p) => p.action));
4310
- return /* @__PURE__ */ jsxs12(Fragment8, { children: [
4311
- /* @__PURE__ */ jsx15(Divider3, { sx: { borderColor: "#e0e4e7" } }),
4312
- /* @__PURE__ */ jsxs12(Box11, { children: [
4313
- /* @__PURE__ */ jsx15(Box11, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 3, children: /* @__PURE__ */ jsxs12(Box11, { children: [
4314
- /* @__PURE__ */ jsx15(
4315
- Typography11,
4316
- {
4317
- variant: "h6",
4318
- sx: {
4319
- fontWeight: 600,
4320
- color: "#111418",
4321
- mb: 1
4322
- },
4323
- children: t("modules.form.publicPolicies.title")
4324
- }
4325
- ),
4326
- /* @__PURE__ */ jsx15(
4327
- Typography11,
4328
- {
4329
- variant: "body2",
4330
- color: "text.secondary",
4331
- sx: { fontSize: "0.875rem" },
4332
- children: t("modules.form.publicPolicies.description")
4333
- }
4334
- )
4335
- ] }) }),
4336
- arrayError && /* @__PURE__ */ jsx15(Alert8, { severity: "error", sx: { mb: 3 }, children: arrayError }),
4337
- /* @__PURE__ */ jsxs12(Stack3, { spacing: 3, children: [
4338
- (policies || []).length === 0 ? /* @__PURE__ */ jsx15(Alert8, { severity: "info", children: t("modules.form.publicPolicies.noPolicies") }) : policies.map((policy, index) => /* @__PURE__ */ jsx15(
4339
- PolicyItem_default,
4340
- {
4341
- ref: (el) => {
4342
- policyRefs.current[index] = el;
4343
- },
4344
- policy,
4345
- onChange: (nextPolicy) => {
4346
- const next = [...policies];
4347
- next[index] = nextPolicy;
4348
- onChange(next);
4349
- },
4350
- onRemove: () => removePolicy(index),
4351
- availableFields,
4352
- isSubmitting,
4353
- usedActions,
4354
- error: typeof errors === "object" && errors && policy.id in errors ? errors[policy.id] : void 0
4355
- },
4356
- policy.id
4357
- )),
4358
- canAddPolicy && /* @__PURE__ */ jsx15(Box11, { children: /* @__PURE__ */ jsx15(
4359
- Button8,
4360
- {
4361
- type: "button",
4362
- variant: "outlined",
4363
- startIcon: /* @__PURE__ */ jsx15(Add, {}),
4364
- onClick: addPolicy,
4365
- disabled: isSubmitting,
4366
- sx: {
4367
- borderColor: "#d0d7de",
4368
- color: "#656d76",
4369
- "&:hover": {
4370
- borderColor: "#8c959f",
4371
- backgroundColor: "transparent"
4372
- }
4373
- },
4374
- children: t("modules.form.publicPolicies.addPolicy")
4375
- }
4376
- ) })
4377
- ] })
4378
- ] })
4379
- ] });
4380
4381
  };
4381
- var Policies_default = Policies;
4382
4382
  export {
4383
4383
  CrudifyDataProvider,
4384
4384
  CrudifyLogin_default as CrudifyLogin,