@poly-x/react 0.1.0 → 0.2.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/README.md +33 -2
- package/dist/index.cjs +646 -81
- package/dist/index.d.cts +107 -2
- package/dist/index.d.mts +107 -2
- package/dist/index.mjs +646 -83
- package/dist/styles.css +151 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
let _poly_x_core = require("@poly-x/core");
|
|
4
4
|
let react = require("react");
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
let react_dom = require("react-dom");
|
|
6
7
|
//#region src/auth-channel.ts
|
|
7
8
|
var BroadcastAuthChannel = class {
|
|
8
9
|
supported;
|
|
@@ -499,6 +500,14 @@ function useSessionSource() {
|
|
|
499
500
|
} catch {
|
|
500
501
|
return null;
|
|
501
502
|
}
|
|
503
|
+
},
|
|
504
|
+
refresh: async () => {
|
|
505
|
+
try {
|
|
506
|
+
await engine.getAccessToken();
|
|
507
|
+
return true;
|
|
508
|
+
} catch {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
502
511
|
}
|
|
503
512
|
},
|
|
504
513
|
canSignIn: Boolean(controller)
|
|
@@ -519,12 +528,14 @@ function useAuth() {
|
|
|
519
528
|
}, [ctx]);
|
|
520
529
|
const signOut = (0, react.useCallback)((options) => source.signOut(options), [source]);
|
|
521
530
|
const getToken = (0, react.useCallback)(() => source.getToken(), [source]);
|
|
531
|
+
const refresh = (0, react.useCallback)(() => source.refresh(), [source]);
|
|
522
532
|
return {
|
|
523
533
|
isLoaded: state.status !== "resolving",
|
|
524
534
|
isSignedIn: state.status === "authenticated" || state.status === "refreshing",
|
|
525
535
|
signIn,
|
|
526
536
|
signOut,
|
|
527
|
-
getToken
|
|
537
|
+
getToken,
|
|
538
|
+
refresh
|
|
528
539
|
};
|
|
529
540
|
}
|
|
530
541
|
/** Run this on your callback route to complete a sign-in (see `<AuthCallback>`). */
|
|
@@ -630,7 +641,7 @@ function captureDeviceContext() {
|
|
|
630
641
|
}
|
|
631
642
|
//#endregion
|
|
632
643
|
//#region src/components/sign-in.tsx
|
|
633
|
-
const DEFAULT_LABELS$
|
|
644
|
+
const DEFAULT_LABELS$4 = {
|
|
634
645
|
heading: "Sign in",
|
|
635
646
|
subheading: "Enter your credentials to continue.",
|
|
636
647
|
tagline: "",
|
|
@@ -656,7 +667,8 @@ const DEFAULT_LABELS$3 = {
|
|
|
656
667
|
invalidCredentials: "Invalid email or password.",
|
|
657
668
|
noAccess: "You don't have access to this application.",
|
|
658
669
|
passwordChangeRequired: "You must reset your password before you can sign in.",
|
|
659
|
-
genericError: "Something went wrong. Please try again."
|
|
670
|
+
genericError: "Something went wrong. Please try again.",
|
|
671
|
+
verificationRequired: "Additional verification is required to sign in."
|
|
660
672
|
};
|
|
661
673
|
/** Mirrors poly-auth's own floor (Joi: min 6). */
|
|
662
674
|
const MIN_PASSWORD_LENGTH = 6;
|
|
@@ -745,7 +757,7 @@ function SignIn(props) {
|
|
|
745
757
|
const action = props.action ?? "/api/polyx/authenticate";
|
|
746
758
|
const variant = props.variant ?? "card";
|
|
747
759
|
const labels = {
|
|
748
|
-
...DEFAULT_LABELS$
|
|
760
|
+
...DEFAULT_LABELS$4,
|
|
749
761
|
...props.labels
|
|
750
762
|
};
|
|
751
763
|
const [email, setEmail] = (0, react.useState)("");
|
|
@@ -826,6 +838,9 @@ function SignIn(props) {
|
|
|
826
838
|
case "no_access":
|
|
827
839
|
setError(labels.noAccess);
|
|
828
840
|
return;
|
|
841
|
+
case "verification_required":
|
|
842
|
+
setError(labels.verificationRequired ?? labels.genericError);
|
|
843
|
+
return;
|
|
829
844
|
default:
|
|
830
845
|
setError(labels.genericError);
|
|
831
846
|
return;
|
|
@@ -1138,7 +1153,7 @@ function RecoveryStatus(props) {
|
|
|
1138
1153
|
}
|
|
1139
1154
|
//#endregion
|
|
1140
1155
|
//#region src/components/forgot-password.tsx
|
|
1141
|
-
const DEFAULT_LABELS$
|
|
1156
|
+
const DEFAULT_LABELS$3 = {
|
|
1142
1157
|
heading: "Reset your password",
|
|
1143
1158
|
subheading: "Enter your email and we'll send you a reset link.",
|
|
1144
1159
|
emailLabel: "Email",
|
|
@@ -1173,7 +1188,7 @@ function ForgotPassword(props) {
|
|
|
1173
1188
|
const action = props.action ?? "/api/polyx/forgot-password";
|
|
1174
1189
|
const signInPath = props.signInPath ?? "/sign-in";
|
|
1175
1190
|
const labels = {
|
|
1176
|
-
...DEFAULT_LABELS$
|
|
1191
|
+
...DEFAULT_LABELS$3,
|
|
1177
1192
|
...props.labels
|
|
1178
1193
|
};
|
|
1179
1194
|
const [email, setEmail] = (0, react.useState)("");
|
|
@@ -1301,7 +1316,7 @@ function ForgotPassword(props) {
|
|
|
1301
1316
|
}
|
|
1302
1317
|
//#endregion
|
|
1303
1318
|
//#region src/components/reset-password.tsx
|
|
1304
|
-
const DEFAULT_LABELS$
|
|
1319
|
+
const DEFAULT_LABELS$2 = {
|
|
1305
1320
|
heading: "Choose a new password",
|
|
1306
1321
|
subheading: "Enter and confirm your new password.",
|
|
1307
1322
|
newPasswordLabel: "New password",
|
|
@@ -1337,7 +1352,7 @@ function ResetPassword(props) {
|
|
|
1337
1352
|
const signInPath = props.signInPath ?? "/sign-in";
|
|
1338
1353
|
const forgotPasswordPath = props.forgotPasswordPath ?? "/forgot-password";
|
|
1339
1354
|
const labels = {
|
|
1340
|
-
...DEFAULT_LABELS$
|
|
1355
|
+
...DEFAULT_LABELS$2,
|
|
1341
1356
|
...props.labels
|
|
1342
1357
|
};
|
|
1343
1358
|
const [token] = (0, react.useState)(() => props.token ?? (typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("token") ?? "" : ""));
|
|
@@ -1620,6 +1635,511 @@ function UserAvatar({ src, name, size = 32, className }) {
|
|
|
1620
1635
|
});
|
|
1621
1636
|
}
|
|
1622
1637
|
//#endregion
|
|
1638
|
+
//#region src/components/user-profile.tsx
|
|
1639
|
+
const PROFILE_ENDPOINT = "/api/polyx/profile";
|
|
1640
|
+
const DEFAULT_LABELS$1 = {
|
|
1641
|
+
trigger: "Manage profile",
|
|
1642
|
+
title: "Your profile",
|
|
1643
|
+
description: "Update your personal details.",
|
|
1644
|
+
firstName: "First name",
|
|
1645
|
+
lastName: "Last name",
|
|
1646
|
+
phoneNumber: "Phone number",
|
|
1647
|
+
cnic: "National ID",
|
|
1648
|
+
gender: "Gender",
|
|
1649
|
+
photo: "Photo",
|
|
1650
|
+
changePhoto: "Change photo",
|
|
1651
|
+
optional: "optional",
|
|
1652
|
+
save: "Save changes",
|
|
1653
|
+
saving: "Saving…",
|
|
1654
|
+
cancel: "Cancel",
|
|
1655
|
+
close: "Close",
|
|
1656
|
+
loading: "Loading your profile…",
|
|
1657
|
+
loadFailed: "We couldn't load your profile. Close this and try again.",
|
|
1658
|
+
saveFailed: "We couldn't save your changes. Your edits are still here — try again.",
|
|
1659
|
+
saved: "Profile updated.",
|
|
1660
|
+
signedOut: "Your session ended. Sign in again to make changes.",
|
|
1661
|
+
genderUnset: "Prefer not to say",
|
|
1662
|
+
genderMale: "Male",
|
|
1663
|
+
genderFemale: "Female",
|
|
1664
|
+
genderOther: "Other"
|
|
1665
|
+
};
|
|
1666
|
+
/** Matches the console's options, so the same stored values round-trip. */
|
|
1667
|
+
const GENDERS = [
|
|
1668
|
+
"male",
|
|
1669
|
+
"female",
|
|
1670
|
+
"other"
|
|
1671
|
+
];
|
|
1672
|
+
const EMPTY = {
|
|
1673
|
+
firstName: "",
|
|
1674
|
+
lastName: "",
|
|
1675
|
+
phoneNumber: "",
|
|
1676
|
+
cnic: "",
|
|
1677
|
+
gender: ""
|
|
1678
|
+
};
|
|
1679
|
+
function readProfile(user) {
|
|
1680
|
+
const draft = { ...EMPTY };
|
|
1681
|
+
for (const field of _poly_x_core.PROFILE_FIELDS) {
|
|
1682
|
+
const value = user[field];
|
|
1683
|
+
if (typeof value === "string") draft[field] = value;
|
|
1684
|
+
}
|
|
1685
|
+
return draft;
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* The signed-in person's own profile, as a dialog (v0.4 / FR-PROF).
|
|
1689
|
+
*
|
|
1690
|
+
* Every application that wanted this had to build the same four things: a form, a validation
|
|
1691
|
+
* ruleset guessed at from error messages, a file upload, and — because the browser holds no
|
|
1692
|
+
* credential under BFF custody — a server route to carry the call. The last of those is
|
|
1693
|
+
* security-adjacent work that had no business being duplicated per app.
|
|
1694
|
+
*
|
|
1695
|
+
* The credential never comes near this component: it posts to the SDK's own route, which
|
|
1696
|
+
* attaches the session server-side and takes the identity from the sealed session rather than
|
|
1697
|
+
* from anything sent here. This surface cannot be aimed at another person's profile even if a
|
|
1698
|
+
* caller tries (FR-CUSTODY-4 / D24).
|
|
1699
|
+
*
|
|
1700
|
+
* ```tsx
|
|
1701
|
+
* <UserProfile onSaved={() => router.refresh()} />
|
|
1702
|
+
* ```
|
|
1703
|
+
*/
|
|
1704
|
+
function UserProfile(props) {
|
|
1705
|
+
const labels = {
|
|
1706
|
+
...DEFAULT_LABELS$1,
|
|
1707
|
+
...props.labels
|
|
1708
|
+
};
|
|
1709
|
+
const { isLoaded, isSignedIn, refresh } = useAuth();
|
|
1710
|
+
const controlled = props.open !== void 0;
|
|
1711
|
+
const [internalOpen, setInternalOpen] = (0, react.useState)(props.defaultOpen ?? false);
|
|
1712
|
+
const open = controlled ? props.open === true : internalOpen;
|
|
1713
|
+
const [status, setStatus] = (0, react.useState)("loading");
|
|
1714
|
+
const [stored, setStored] = (0, react.useState)(EMPTY);
|
|
1715
|
+
const [draft, setDraft] = (0, react.useState)(EMPTY);
|
|
1716
|
+
const [errors, setErrors] = (0, react.useState)({});
|
|
1717
|
+
const [photo, setPhoto] = (0, react.useState)(null);
|
|
1718
|
+
const [photoError, setPhotoError] = (0, react.useState)(null);
|
|
1719
|
+
const [storedPhoto, setStoredPhoto] = (0, react.useState)(void 0);
|
|
1720
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
1721
|
+
const [feedback, setFeedback] = (0, react.useState)(null);
|
|
1722
|
+
const [portalHost, setPortalHost] = (0, react.useState)(null);
|
|
1723
|
+
(0, react.useEffect)(() => {
|
|
1724
|
+
const host = document.createElement("div");
|
|
1725
|
+
host.setAttribute("data-polyx-userprofile-portal", "");
|
|
1726
|
+
document.body.appendChild(host);
|
|
1727
|
+
setPortalHost(host);
|
|
1728
|
+
return () => host.remove();
|
|
1729
|
+
}, []);
|
|
1730
|
+
const triggerRef = (0, react.useRef)(null);
|
|
1731
|
+
const dialogRef = (0, react.useRef)(null);
|
|
1732
|
+
const fileRef = (0, react.useRef)(null);
|
|
1733
|
+
const titleId = (0, react.useId)();
|
|
1734
|
+
const fieldId = (0, react.useId)();
|
|
1735
|
+
const onOpenChange = props.onOpenChange;
|
|
1736
|
+
const setOpen = (0, react.useCallback)((next) => {
|
|
1737
|
+
if (!controlled) setInternalOpen(next);
|
|
1738
|
+
onOpenChange?.(next);
|
|
1739
|
+
}, [controlled, onOpenChange]);
|
|
1740
|
+
const polyx = (0, react.useContext)(PolyXContext);
|
|
1741
|
+
const [branding, setBranding] = (0, react.useState)({});
|
|
1742
|
+
(0, react.useEffect)(() => {
|
|
1743
|
+
if (!polyx || !open) return;
|
|
1744
|
+
let active = true;
|
|
1745
|
+
polyx.authClient.fetchBranding({}).then((tokens) => {
|
|
1746
|
+
if (active) setBranding(tokens);
|
|
1747
|
+
});
|
|
1748
|
+
return () => {
|
|
1749
|
+
active = false;
|
|
1750
|
+
};
|
|
1751
|
+
}, [polyx, open]);
|
|
1752
|
+
const brandingStyle = {};
|
|
1753
|
+
if (branding.primaryColor) brandingStyle["--polyx-brand"] = branding.primaryColor;
|
|
1754
|
+
/** Load on open. Never leaves blank fields looking like a stored (empty) profile — EC-PROF-1. */
|
|
1755
|
+
(0, react.useEffect)(() => {
|
|
1756
|
+
if (!open) return;
|
|
1757
|
+
let active = true;
|
|
1758
|
+
setStatus("loading");
|
|
1759
|
+
setFeedback(null);
|
|
1760
|
+
(async () => {
|
|
1761
|
+
try {
|
|
1762
|
+
const response = await fetch(PROFILE_ENDPOINT, { headers: { Accept: "application/json" } });
|
|
1763
|
+
const body = await response.json().catch(() => ({}));
|
|
1764
|
+
if (!active) return;
|
|
1765
|
+
if (!response.ok || !body.user) {
|
|
1766
|
+
setStatus("load_failed");
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
const loaded = readProfile(body.user);
|
|
1770
|
+
setStored(loaded);
|
|
1771
|
+
setDraft(loaded);
|
|
1772
|
+
setStoredPhoto(typeof body.user.profilePicture === "string" ? body.user.profilePicture : void 0);
|
|
1773
|
+
setErrors({});
|
|
1774
|
+
setStatus("ready");
|
|
1775
|
+
} catch {
|
|
1776
|
+
if (active) setStatus("load_failed");
|
|
1777
|
+
}
|
|
1778
|
+
})();
|
|
1779
|
+
return () => {
|
|
1780
|
+
active = false;
|
|
1781
|
+
};
|
|
1782
|
+
}, [open]);
|
|
1783
|
+
(0, react.useEffect)(() => {
|
|
1784
|
+
const preview = photo?.preview;
|
|
1785
|
+
return () => {
|
|
1786
|
+
if (preview) URL.revokeObjectURL(preview);
|
|
1787
|
+
};
|
|
1788
|
+
}, [photo]);
|
|
1789
|
+
/**
|
|
1790
|
+
* Hide the rest of the page from assistive technology while the dialog is open.
|
|
1791
|
+
*
|
|
1792
|
+
* `aria-modal="true"` alone is not enough — screen-reader support for it is inconsistent, so a
|
|
1793
|
+
* virtual cursor can still read and act on everything behind the dialog. The keyboard focus trap
|
|
1794
|
+
* does not cover this either: it constrains Tab, not the virtual cursor. Without both, the dialog
|
|
1795
|
+
* announces itself as modal and then does not behave modally.
|
|
1796
|
+
*
|
|
1797
|
+
* Every direct child of `<body>` except the dialog's own portal is marked, and only attributes
|
|
1798
|
+
* this effect added are removed again — a host that set its own `aria-hidden` keeps it.
|
|
1799
|
+
*/
|
|
1800
|
+
(0, react.useEffect)(() => {
|
|
1801
|
+
if (!open || !portalHost) return;
|
|
1802
|
+
const marked = [];
|
|
1803
|
+
for (const node of Array.from(document.body.children)) {
|
|
1804
|
+
if (node === portalHost || !(node instanceof HTMLElement)) continue;
|
|
1805
|
+
if (node.hasAttribute("aria-hidden")) continue;
|
|
1806
|
+
node.setAttribute("aria-hidden", "true");
|
|
1807
|
+
node.setAttribute("inert", "");
|
|
1808
|
+
marked.push(node);
|
|
1809
|
+
}
|
|
1810
|
+
return () => {
|
|
1811
|
+
for (const node of marked) {
|
|
1812
|
+
node.removeAttribute("aria-hidden");
|
|
1813
|
+
node.removeAttribute("inert");
|
|
1814
|
+
}
|
|
1815
|
+
};
|
|
1816
|
+
}, [open, portalHost]);
|
|
1817
|
+
/**
|
|
1818
|
+
* Move focus into the dialog once there is something to focus (FR-A11Y-1).
|
|
1819
|
+
*
|
|
1820
|
+
* Deliberately keyed on `status`, not just `open`: the dialog opens in its loading state with
|
|
1821
|
+
* no focusable content, so placing focus at open time silently leaves it on the page behind —
|
|
1822
|
+
* which is exactly nowhere for anyone navigating by keyboard or screen reader.
|
|
1823
|
+
*/
|
|
1824
|
+
(0, react.useEffect)(() => {
|
|
1825
|
+
if (!open || status === "loading") return;
|
|
1826
|
+
dialogRef.current?.querySelector("input, select, button")?.focus();
|
|
1827
|
+
}, [open, status]);
|
|
1828
|
+
/** Escape closes; focus is trapped while open (FR-A11Y-2/3). */
|
|
1829
|
+
(0, react.useEffect)(() => {
|
|
1830
|
+
if (!open) return;
|
|
1831
|
+
const node = dialogRef.current;
|
|
1832
|
+
const previous = document.activeElement;
|
|
1833
|
+
const onKeyDown = (event) => {
|
|
1834
|
+
if (event.key === "Escape") {
|
|
1835
|
+
setOpen(false);
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
if (event.key !== "Tab" || !node) return;
|
|
1839
|
+
const focusable = [...node.querySelectorAll("input, select, button, a[href]")].filter((element) => !element.hasAttribute("disabled"));
|
|
1840
|
+
if (focusable.length === 0) return;
|
|
1841
|
+
const first = focusable[0];
|
|
1842
|
+
const last = focusable[focusable.length - 1];
|
|
1843
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
1844
|
+
event.preventDefault();
|
|
1845
|
+
last.focus();
|
|
1846
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
1847
|
+
event.preventDefault();
|
|
1848
|
+
first.focus();
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1851
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1852
|
+
return () => {
|
|
1853
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
1854
|
+
(triggerRef.current ?? previous)?.focus();
|
|
1855
|
+
};
|
|
1856
|
+
}, [open, setOpen]);
|
|
1857
|
+
function set(field, value) {
|
|
1858
|
+
setDraft((current) => ({
|
|
1859
|
+
...current,
|
|
1860
|
+
[field]: value
|
|
1861
|
+
}));
|
|
1862
|
+
setErrors((current) => ({
|
|
1863
|
+
...current,
|
|
1864
|
+
[field]: void 0
|
|
1865
|
+
}));
|
|
1866
|
+
setFeedback(null);
|
|
1867
|
+
}
|
|
1868
|
+
/** Validate on leaving a field, so a mistake is caught next to where it was made (FR-VALID-8). */
|
|
1869
|
+
function blur(field) {
|
|
1870
|
+
const found = (0, _poly_x_core.validateProfileDraft)(draft)[field];
|
|
1871
|
+
setErrors((current) => ({
|
|
1872
|
+
...current,
|
|
1873
|
+
[field]: found
|
|
1874
|
+
}));
|
|
1875
|
+
}
|
|
1876
|
+
function choosePhoto(file) {
|
|
1877
|
+
if (!file) return;
|
|
1878
|
+
const invalid = (0, _poly_x_core.validateProfileImage)(file);
|
|
1879
|
+
if (invalid) {
|
|
1880
|
+
setPhotoError(invalid);
|
|
1881
|
+
setPhoto(null);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
setPhotoError(null);
|
|
1885
|
+
setPhoto({
|
|
1886
|
+
file,
|
|
1887
|
+
preview: URL.createObjectURL(file)
|
|
1888
|
+
});
|
|
1889
|
+
setFeedback(null);
|
|
1890
|
+
}
|
|
1891
|
+
async function save() {
|
|
1892
|
+
if (saving) return;
|
|
1893
|
+
const found = (0, _poly_x_core.validateProfileDraft)(draft);
|
|
1894
|
+
if (Object.keys(found).length > 0) {
|
|
1895
|
+
setErrors(found);
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
const changed = (0, _poly_x_core.buildProfileUpdate)(draft, stored);
|
|
1899
|
+
if (Object.keys(changed).length === 0 && !photo) {
|
|
1900
|
+
setOpen(false);
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
const body = new FormData();
|
|
1904
|
+
for (const [field, value] of Object.entries(changed)) body.append(field, value);
|
|
1905
|
+
if (photo) body.append("image", photo.file);
|
|
1906
|
+
setSaving(true);
|
|
1907
|
+
setFeedback(null);
|
|
1908
|
+
try {
|
|
1909
|
+
const response = await fetch(PROFILE_ENDPOINT, {
|
|
1910
|
+
method: "POST",
|
|
1911
|
+
body
|
|
1912
|
+
});
|
|
1913
|
+
const payload = await response.json().catch(() => ({}));
|
|
1914
|
+
if (response.status === 401) {
|
|
1915
|
+
setFeedback({
|
|
1916
|
+
kind: "error",
|
|
1917
|
+
message: labels.signedOut
|
|
1918
|
+
});
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
if (!response.ok) {
|
|
1922
|
+
const field = (0, _poly_x_core.conflictField)(payload.message);
|
|
1923
|
+
if (field) {
|
|
1924
|
+
setErrors((current) => ({
|
|
1925
|
+
...current,
|
|
1926
|
+
[field]: payload.message
|
|
1927
|
+
}));
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
setFeedback({
|
|
1931
|
+
kind: "error",
|
|
1932
|
+
message: payload.message ?? labels.saveFailed
|
|
1933
|
+
});
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
const saved = payload.user ?? {};
|
|
1937
|
+
setStored(readProfile(saved));
|
|
1938
|
+
if (typeof saved.profilePicture === "string") setStoredPhoto(saved.profilePicture);
|
|
1939
|
+
setPhoto(null);
|
|
1940
|
+
setFeedback({
|
|
1941
|
+
kind: "success",
|
|
1942
|
+
message: labels.saved
|
|
1943
|
+
});
|
|
1944
|
+
await refresh().catch(() => false);
|
|
1945
|
+
props.onSaved?.(saved);
|
|
1946
|
+
setOpen(false);
|
|
1947
|
+
} catch {
|
|
1948
|
+
setFeedback({
|
|
1949
|
+
kind: "error",
|
|
1950
|
+
message: labels.saveFailed
|
|
1951
|
+
});
|
|
1952
|
+
} finally {
|
|
1953
|
+
setSaving(false);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
if (!isLoaded || !isSignedIn) return null;
|
|
1957
|
+
const rootClass = [
|
|
1958
|
+
"polyx-signin",
|
|
1959
|
+
"polyx-userprofile",
|
|
1960
|
+
props.className
|
|
1961
|
+
].filter(Boolean).join(" ");
|
|
1962
|
+
const displayName = [draft.firstName, draft.lastName].filter(Boolean).join(" ");
|
|
1963
|
+
function field(name, label, extra = {}) {
|
|
1964
|
+
const id = `${fieldId}-${name}`;
|
|
1965
|
+
const error = errors[name];
|
|
1966
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1967
|
+
className: "polyx-signin__field",
|
|
1968
|
+
children: [
|
|
1969
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1970
|
+
className: "polyx-signin__label",
|
|
1971
|
+
htmlFor: id,
|
|
1972
|
+
children: [label, extra.optional ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1973
|
+
className: "polyx-userprofile__optional",
|
|
1974
|
+
children: [
|
|
1975
|
+
" (",
|
|
1976
|
+
labels.optional,
|
|
1977
|
+
")"
|
|
1978
|
+
]
|
|
1979
|
+
}) : null]
|
|
1980
|
+
}),
|
|
1981
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1982
|
+
id,
|
|
1983
|
+
className: "polyx-signin__input",
|
|
1984
|
+
type: extra.type ?? "text",
|
|
1985
|
+
value: draft[name] ?? "",
|
|
1986
|
+
placeholder: extra.placeholder,
|
|
1987
|
+
disabled: saving,
|
|
1988
|
+
"aria-required": extra.optional ? void 0 : true,
|
|
1989
|
+
"aria-invalid": error ? true : void 0,
|
|
1990
|
+
"aria-describedby": error ? `${id}-error` : void 0,
|
|
1991
|
+
onChange: (event) => set(name, event.target.value),
|
|
1992
|
+
onBlur: () => blur(name)
|
|
1993
|
+
}),
|
|
1994
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1995
|
+
id: `${id}-error`,
|
|
1996
|
+
className: "polyx-userprofile__field-error",
|
|
1997
|
+
children: error
|
|
1998
|
+
}) : null
|
|
1999
|
+
]
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2003
|
+
className: rootClass,
|
|
2004
|
+
style: brandingStyle,
|
|
2005
|
+
children: [props.open === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2006
|
+
ref: triggerRef,
|
|
2007
|
+
type: "button",
|
|
2008
|
+
className: ["polyx-userprofile__trigger", props.triggerClassName].filter(Boolean).join(" "),
|
|
2009
|
+
onClick: () => setOpen(true),
|
|
2010
|
+
children: props.trigger ?? labels.trigger
|
|
2011
|
+
}) : null, open && portalHost ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2012
|
+
className: "polyx-signin polyx-userprofile polyx-userprofile__overlay",
|
|
2013
|
+
style: brandingStyle,
|
|
2014
|
+
onMouseDown: () => setOpen(false),
|
|
2015
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2016
|
+
ref: dialogRef,
|
|
2017
|
+
role: "dialog",
|
|
2018
|
+
"aria-modal": "true",
|
|
2019
|
+
"aria-labelledby": titleId,
|
|
2020
|
+
className: ["polyx-userprofile__dialog", props.dialogClassName].filter(Boolean).join(" "),
|
|
2021
|
+
onMouseDown: (event) => event.stopPropagation(),
|
|
2022
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2023
|
+
className: "polyx-userprofile__header",
|
|
2024
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
2025
|
+
id: titleId,
|
|
2026
|
+
className: "polyx-userprofile__title",
|
|
2027
|
+
children: labels.title
|
|
2028
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2029
|
+
className: "polyx-userprofile__description",
|
|
2030
|
+
children: labels.description
|
|
2031
|
+
})]
|
|
2032
|
+
}), status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2033
|
+
className: "polyx-userprofile__status",
|
|
2034
|
+
role: "status",
|
|
2035
|
+
children: labels.loading
|
|
2036
|
+
}) : status === "load_failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2037
|
+
className: "polyx-signin__error",
|
|
2038
|
+
role: "alert",
|
|
2039
|
+
children: labels.loadFailed
|
|
2040
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
2041
|
+
className: "polyx-signin__form",
|
|
2042
|
+
onSubmit: (event) => {
|
|
2043
|
+
event.preventDefault();
|
|
2044
|
+
save();
|
|
2045
|
+
},
|
|
2046
|
+
children: [
|
|
2047
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2048
|
+
className: "polyx-userprofile__photo",
|
|
2049
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserAvatar, {
|
|
2050
|
+
src: photo?.preview ?? storedPhoto,
|
|
2051
|
+
name: displayName,
|
|
2052
|
+
size: 56
|
|
2053
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2054
|
+
className: "polyx-userprofile__photo-actions",
|
|
2055
|
+
children: [
|
|
2056
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2057
|
+
type: "button",
|
|
2058
|
+
className: "polyx-userprofile__photo-button",
|
|
2059
|
+
disabled: saving,
|
|
2060
|
+
onClick: () => fileRef.current?.click(),
|
|
2061
|
+
children: labels.changePhoto
|
|
2062
|
+
}),
|
|
2063
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2064
|
+
ref: fileRef,
|
|
2065
|
+
type: "file",
|
|
2066
|
+
accept: "image/*",
|
|
2067
|
+
className: "polyx-userprofile__file",
|
|
2068
|
+
"aria-label": labels.photo,
|
|
2069
|
+
onChange: (event) => choosePhoto(event.target.files?.[0])
|
|
2070
|
+
}),
|
|
2071
|
+
photoError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2072
|
+
className: "polyx-userprofile__field-error",
|
|
2073
|
+
children: photoError
|
|
2074
|
+
}) : null
|
|
2075
|
+
]
|
|
2076
|
+
})]
|
|
2077
|
+
}),
|
|
2078
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2079
|
+
className: "polyx-userprofile__row",
|
|
2080
|
+
children: [field("firstName", labels.firstName), field("lastName", labels.lastName)]
|
|
2081
|
+
}),
|
|
2082
|
+
field("phoneNumber", labels.phoneNumber, {
|
|
2083
|
+
optional: true,
|
|
2084
|
+
type: "tel",
|
|
2085
|
+
placeholder: "+14155552671"
|
|
2086
|
+
}),
|
|
2087
|
+
field("cnic", labels.cnic, { optional: true }),
|
|
2088
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2089
|
+
className: "polyx-signin__field",
|
|
2090
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2091
|
+
className: "polyx-signin__label",
|
|
2092
|
+
htmlFor: `${fieldId}-gender`,
|
|
2093
|
+
children: [labels.gender, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2094
|
+
className: "polyx-userprofile__optional",
|
|
2095
|
+
children: [
|
|
2096
|
+
" (",
|
|
2097
|
+
labels.optional,
|
|
2098
|
+
")"
|
|
2099
|
+
]
|
|
2100
|
+
})]
|
|
2101
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2102
|
+
id: `${fieldId}-gender`,
|
|
2103
|
+
className: "polyx-signin__input",
|
|
2104
|
+
value: draft.gender ?? "",
|
|
2105
|
+
disabled: saving,
|
|
2106
|
+
onChange: (event) => set("gender", event.target.value),
|
|
2107
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2108
|
+
value: "",
|
|
2109
|
+
children: labels.genderUnset
|
|
2110
|
+
}), GENDERS.map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2111
|
+
value,
|
|
2112
|
+
children: value === "male" ? labels.genderMale : value === "female" ? labels.genderFemale : labels.genderOther
|
|
2113
|
+
}, value))]
|
|
2114
|
+
})]
|
|
2115
|
+
}),
|
|
2116
|
+
feedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2117
|
+
className: feedback.kind === "error" ? "polyx-signin__error" : "polyx-userprofile__success",
|
|
2118
|
+
role: feedback.kind === "error" ? "alert" : "status",
|
|
2119
|
+
children: feedback.message
|
|
2120
|
+
}) : null,
|
|
2121
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2122
|
+
className: "polyx-userprofile__actions",
|
|
2123
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2124
|
+
type: "button",
|
|
2125
|
+
className: "polyx-userprofile__cancel",
|
|
2126
|
+
disabled: saving,
|
|
2127
|
+
onClick: () => setOpen(false),
|
|
2128
|
+
children: labels.cancel
|
|
2129
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2130
|
+
type: "submit",
|
|
2131
|
+
className: "polyx-signin__submit",
|
|
2132
|
+
disabled: saving,
|
|
2133
|
+
children: saving ? labels.saving : labels.save
|
|
2134
|
+
})]
|
|
2135
|
+
})
|
|
2136
|
+
]
|
|
2137
|
+
})]
|
|
2138
|
+
})
|
|
2139
|
+
}), portalHost) : null]
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
//#endregion
|
|
1623
2143
|
//#region src/components/user-button.tsx
|
|
1624
2144
|
const DEFAULT_LABELS = {
|
|
1625
2145
|
trigger: "Account menu",
|
|
@@ -1726,6 +2246,7 @@ function UserButtonRoot(props) {
|
|
|
1726
2246
|
const { isLoaded, isSignedIn, signOut } = useAuth();
|
|
1727
2247
|
const user = useUser();
|
|
1728
2248
|
const [open, setOpen] = (0, react.useState)(props.defaultOpen ?? false);
|
|
2249
|
+
const [profileOpen, setProfileOpen] = (0, react.useState)(false);
|
|
1729
2250
|
const [focused, setFocused] = (0, react.useState)(0);
|
|
1730
2251
|
const [side, setSide] = (0, react.useState)(props.side && props.side !== "auto" ? props.side : "bottom");
|
|
1731
2252
|
const triggerRef = (0, react.useRef)(null);
|
|
@@ -1802,6 +2323,12 @@ function UserButtonRoot(props) {
|
|
|
1802
2323
|
labelIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GearIcon, {}),
|
|
1803
2324
|
href: props.manageAccountUrl
|
|
1804
2325
|
});
|
|
2326
|
+
else if (props.showUserProfile) builtIns.set("manageAccount", {
|
|
2327
|
+
key: "manageAccount",
|
|
2328
|
+
label: labels.manageAccount,
|
|
2329
|
+
labelIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GearIcon, {}),
|
|
2330
|
+
onClick: () => setProfileOpen(true)
|
|
2331
|
+
});
|
|
1805
2332
|
builtIns.set("signOut", {
|
|
1806
2333
|
key: "signOut",
|
|
1807
2334
|
label: labels.signOut,
|
|
@@ -1841,81 +2368,89 @@ function UserButtonRoot(props) {
|
|
|
1841
2368
|
props.className
|
|
1842
2369
|
].filter(Boolean).join(" "),
|
|
1843
2370
|
"data-polyx-userbutton": open ? "open" : "closed",
|
|
1844
|
-
children: [
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
name,
|
|
1859
|
-
size: props.avatarSize
|
|
1860
|
-
}), props.showName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1861
|
-
className: "polyx-userbutton__trigger-name",
|
|
1862
|
-
children: name
|
|
1863
|
-
}) : null] })
|
|
1864
|
-
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1865
|
-
ref: menuRef,
|
|
1866
|
-
id: menuId,
|
|
1867
|
-
role: "menu",
|
|
1868
|
-
className: "polyx-userbutton__menu",
|
|
1869
|
-
"data-polyx-side": side,
|
|
1870
|
-
"data-polyx-align": align,
|
|
1871
|
-
onKeyDown: onMenuKeyDown,
|
|
1872
|
-
children: [customHeader ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1873
|
-
className: "polyx-userbutton__identity",
|
|
1874
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserAvatar, {
|
|
2371
|
+
children: [
|
|
2372
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2373
|
+
ref: triggerRef,
|
|
2374
|
+
type: "button",
|
|
2375
|
+
className: ["polyx-userbutton__trigger", props.triggerClassName].filter(Boolean).join(" "),
|
|
2376
|
+
"aria-haspopup": "menu",
|
|
2377
|
+
"aria-expanded": open,
|
|
2378
|
+
"aria-controls": open ? menuId : void 0,
|
|
2379
|
+
"aria-label": labels.trigger,
|
|
2380
|
+
onClick: () => {
|
|
2381
|
+
setFocused(0);
|
|
2382
|
+
setOpen((current) => !current);
|
|
2383
|
+
},
|
|
2384
|
+
children: props.trigger ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserAvatar, {
|
|
1875
2385
|
src: user.avatarUrl,
|
|
1876
2386
|
name,
|
|
1877
|
-
size:
|
|
1878
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1879
|
-
className: "polyx-
|
|
1880
|
-
children:
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
2387
|
+
size: props.avatarSize
|
|
2388
|
+
}), props.showName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2389
|
+
className: "polyx-userbutton__trigger-name",
|
|
2390
|
+
children: name
|
|
2391
|
+
}) : null] })
|
|
2392
|
+
}),
|
|
2393
|
+
open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2394
|
+
ref: menuRef,
|
|
2395
|
+
id: menuId,
|
|
2396
|
+
role: "menu",
|
|
2397
|
+
className: "polyx-userbutton__menu",
|
|
2398
|
+
"data-polyx-side": side,
|
|
2399
|
+
"data-polyx-align": align,
|
|
2400
|
+
onKeyDown: onMenuKeyDown,
|
|
2401
|
+
children: [customHeader ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2402
|
+
className: "polyx-userbutton__identity",
|
|
2403
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserAvatar, {
|
|
2404
|
+
src: user.avatarUrl,
|
|
2405
|
+
name,
|
|
2406
|
+
size: 36
|
|
2407
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2408
|
+
className: "polyx-userbutton__identity-text",
|
|
2409
|
+
children: [user.displayName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2410
|
+
className: "polyx-userbutton__name",
|
|
2411
|
+
children: user.displayName
|
|
2412
|
+
}) : null, user.email ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2413
|
+
className: "polyx-userbutton__email",
|
|
2414
|
+
children: user.email
|
|
2415
|
+
}) : null]
|
|
2416
|
+
})]
|
|
2417
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2418
|
+
className: "polyx-userbutton__items",
|
|
2419
|
+
children: items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: item.custom ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2420
|
+
className: "polyx-userbutton__custom",
|
|
2421
|
+
children: item.custom
|
|
2422
|
+
}) : item.href ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
2423
|
+
ref: (node) => {
|
|
2424
|
+
itemRefs.current[index] = node;
|
|
2425
|
+
},
|
|
2426
|
+
role: "menuitem",
|
|
2427
|
+
tabIndex: index === focused ? 0 : -1,
|
|
2428
|
+
className: "polyx-userbutton__item",
|
|
2429
|
+
href: item.href,
|
|
2430
|
+
onClick: () => close(false),
|
|
2431
|
+
children: [item.labelIcon, item.label]
|
|
2432
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2433
|
+
ref: (node) => {
|
|
2434
|
+
itemRefs.current[index] = node;
|
|
2435
|
+
},
|
|
2436
|
+
role: "menuitem",
|
|
2437
|
+
tabIndex: index === focused ? 0 : -1,
|
|
2438
|
+
type: "button",
|
|
2439
|
+
className: "polyx-userbutton__item",
|
|
2440
|
+
onClick: () => {
|
|
2441
|
+
close(false);
|
|
2442
|
+
item.onClick?.();
|
|
2443
|
+
},
|
|
2444
|
+
children: [item.labelIcon, item.label]
|
|
2445
|
+
}) }, item.key))
|
|
1887
2446
|
})]
|
|
1888
|
-
})
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
itemRefs.current[index] = node;
|
|
1896
|
-
},
|
|
1897
|
-
role: "menuitem",
|
|
1898
|
-
tabIndex: index === focused ? 0 : -1,
|
|
1899
|
-
className: "polyx-userbutton__item",
|
|
1900
|
-
href: item.href,
|
|
1901
|
-
onClick: () => close(false),
|
|
1902
|
-
children: [item.labelIcon, item.label]
|
|
1903
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1904
|
-
ref: (node) => {
|
|
1905
|
-
itemRefs.current[index] = node;
|
|
1906
|
-
},
|
|
1907
|
-
role: "menuitem",
|
|
1908
|
-
tabIndex: index === focused ? 0 : -1,
|
|
1909
|
-
type: "button",
|
|
1910
|
-
className: "polyx-userbutton__item",
|
|
1911
|
-
onClick: () => {
|
|
1912
|
-
close(false);
|
|
1913
|
-
item.onClick?.();
|
|
1914
|
-
},
|
|
1915
|
-
children: [item.labelIcon, item.label]
|
|
1916
|
-
}) }, item.key))
|
|
1917
|
-
})]
|
|
1918
|
-
}) : null]
|
|
2447
|
+
}) : null,
|
|
2448
|
+
props.showUserProfile && !props.manageAccountUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserProfile, {
|
|
2449
|
+
...props.userProfileProps,
|
|
2450
|
+
open: profileOpen,
|
|
2451
|
+
onOpenChange: setProfileOpen
|
|
2452
|
+
}) : null
|
|
2453
|
+
]
|
|
1919
2454
|
});
|
|
1920
2455
|
}
|
|
1921
2456
|
function GearIcon() {
|
|
@@ -1980,6 +2515,33 @@ const UserButton = Object.assign(UserButtonRoot, {
|
|
|
1980
2515
|
Header
|
|
1981
2516
|
});
|
|
1982
2517
|
//#endregion
|
|
2518
|
+
//#region src/auth-event.ts
|
|
2519
|
+
/**
|
|
2520
|
+
* Read a one-shot auth event published by the BFF (F017 / FR-CLIENT-5).
|
|
2521
|
+
*
|
|
2522
|
+
* Sign-in and sign-out are full-page navigations, so an app cannot simply react in the
|
|
2523
|
+
* handler that triggered them — the page is gone. The BFF leaves a short-lived, readable
|
|
2524
|
+
* cookie; the destination page consumes it once.
|
|
2525
|
+
*/
|
|
2526
|
+
/** Must match `AUTH_EVENT_COOKIE` in `@poly-x/next`. */
|
|
2527
|
+
const AUTH_EVENT_COOKIE = "polyx_auth_event";
|
|
2528
|
+
function readCookie(name) {
|
|
2529
|
+
if (typeof document === "undefined") return void 0;
|
|
2530
|
+
return document.cookie.split(";").map((part) => part.trim()).find((part) => part.startsWith(`${name}=`))?.slice(name.length + 1);
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Read the pending auth event and clear it, so it fires exactly once.
|
|
2534
|
+
*
|
|
2535
|
+
* Returns `null` when there is nothing pending — which is the normal case on almost every
|
|
2536
|
+
* page load. Safe to call during render or in an effect; it is a no-op on the server.
|
|
2537
|
+
*/
|
|
2538
|
+
function consumeAuthEvent() {
|
|
2539
|
+
const value = readCookie(AUTH_EVENT_COOKIE);
|
|
2540
|
+
if (value !== "signed-in" && value !== "signed-out") return null;
|
|
2541
|
+
document.cookie = `${AUTH_EVENT_COOKIE}=; Path=/; Max-Age=0; SameSite=Lax`;
|
|
2542
|
+
return value;
|
|
2543
|
+
}
|
|
2544
|
+
//#endregion
|
|
1983
2545
|
//#region src/live-authz.ts
|
|
1984
2546
|
const DEFAULT_POLL_MS = 6e4;
|
|
1985
2547
|
const DEFAULT_FOCUS_THROTTLE_MS = 5e3;
|
|
@@ -2118,7 +2680,8 @@ function useAuthzChanged(callback) {
|
|
|
2118
2680
|
* F006; this entry is the state foundation (provider, hooks, browser seams).
|
|
2119
2681
|
*/
|
|
2120
2682
|
const PACKAGE_NAME = "@poly-x/react";
|
|
2121
|
-
|
|
2683
|
+
/** The published version of this package, injected at build time. */
|
|
2684
|
+
const version = "0.2.0";
|
|
2122
2685
|
//#endregion
|
|
2123
2686
|
exports.AuthCallback = AuthCallback;
|
|
2124
2687
|
exports.BroadcastAuthChannel = BroadcastAuthChannel;
|
|
@@ -2134,9 +2697,11 @@ exports.SessionStoragePkceStore = SessionStoragePkceStore;
|
|
|
2134
2697
|
exports.SignIn = SignIn;
|
|
2135
2698
|
exports.UserAvatar = UserAvatar;
|
|
2136
2699
|
exports.UserButton = UserButton;
|
|
2700
|
+
exports.UserProfile = UserProfile;
|
|
2137
2701
|
exports.WebLocksLock = WebLocksLock;
|
|
2138
2702
|
exports.WindowBrowserBridge = WindowBrowserBridge;
|
|
2139
2703
|
exports.capturePreciseLocation = capturePreciseLocation;
|
|
2704
|
+
exports.consumeAuthEvent = consumeAuthEvent;
|
|
2140
2705
|
exports.createLoginController = createLoginController;
|
|
2141
2706
|
exports.initialsFrom = initialsFrom;
|
|
2142
2707
|
exports.resolveSide = resolveSide;
|