@empoweredvote/ev-ui 0.6.1 → 0.6.3
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.js +284 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +256 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2890,6 +2890,94 @@ var evContext = {
|
|
|
2890
2890
|
async clear() {
|
|
2891
2891
|
return send({ type: "ev-context:clear" });
|
|
2892
2892
|
},
|
|
2893
|
+
/**
|
|
2894
|
+
* Read the userId-stamped authed slice for the given user.
|
|
2895
|
+
*
|
|
2896
|
+
* Stored shape:
|
|
2897
|
+
* { compass?, address?, verdicts?, authed?: { userId, compass?, address?, verdicts? } }
|
|
2898
|
+
*
|
|
2899
|
+
* - Returns null if there is no stored value, no `authed` body, or the stored
|
|
2900
|
+
* `authed.userId` does not match the requested `userId` (mismatch = inert).
|
|
2901
|
+
* - Otherwise returns `{ compass, address, verdicts }` with undefined keys
|
|
2902
|
+
* omitted.
|
|
2903
|
+
*
|
|
2904
|
+
* Used by logged-in consumers to do SWR-style hydration: render this slice
|
|
2905
|
+
* synchronously on mount, then replace silently when the API responds.
|
|
2906
|
+
*/
|
|
2907
|
+
async getAuthedSlice(userId) {
|
|
2908
|
+
if (!userId) return null;
|
|
2909
|
+
const current = await this.get();
|
|
2910
|
+
if (!current || !current.authed || typeof current.authed !== "object") return null;
|
|
2911
|
+
if (current.authed.userId !== userId) return null;
|
|
2912
|
+
const out = {};
|
|
2913
|
+
if (current.authed.compass !== void 0) out.compass = current.authed.compass;
|
|
2914
|
+
if (current.authed.address !== void 0) out.address = current.authed.address;
|
|
2915
|
+
if (current.authed.verdicts !== void 0) out.verdicts = current.authed.verdicts;
|
|
2916
|
+
if (current.authed.promotionDismissed !== void 0) {
|
|
2917
|
+
out.promotionDismissed = current.authed.promotionDismissed;
|
|
2918
|
+
}
|
|
2919
|
+
return out;
|
|
2920
|
+
},
|
|
2921
|
+
/**
|
|
2922
|
+
* Mirror an authed write into the userId-stamped `authed` slice.
|
|
2923
|
+
*
|
|
2924
|
+
* - `patch` may contain any subset of `{ compass, address, verdicts }`.
|
|
2925
|
+
* Unrecognized keys are dropped.
|
|
2926
|
+
* - If the existing `authed.userId` matches `userId`, the patch is merged
|
|
2927
|
+
* with the prior authed body (per-domain shallow merge — the patch's
|
|
2928
|
+
* compass/address/verdicts replace the prior values).
|
|
2929
|
+
* - If it does not match (user switch), the prior authed body is stomped.
|
|
2930
|
+
* - Guest top-level keys (compass, address, verdicts at the root) are
|
|
2931
|
+
* preserved untouched.
|
|
2932
|
+
*
|
|
2933
|
+
* Returns false on falsy `userId` or empty patch; otherwise returns the
|
|
2934
|
+
* underlying `set()` result.
|
|
2935
|
+
*/
|
|
2936
|
+
async setAuthedSlice(userId, patch) {
|
|
2937
|
+
if (!userId) return false;
|
|
2938
|
+
if (!patch || typeof patch !== "object") return false;
|
|
2939
|
+
const allowed = {};
|
|
2940
|
+
if (patch.compass !== void 0) allowed.compass = patch.compass;
|
|
2941
|
+
if (patch.address !== void 0) allowed.address = patch.address;
|
|
2942
|
+
if (patch.verdicts !== void 0) allowed.verdicts = patch.verdicts;
|
|
2943
|
+
if (patch.promotionDismissed !== void 0) {
|
|
2944
|
+
allowed.promotionDismissed = patch.promotionDismissed;
|
|
2945
|
+
}
|
|
2946
|
+
if (Object.keys(allowed).length === 0) return false;
|
|
2947
|
+
const current = await this.get() || {};
|
|
2948
|
+
const priorAuthed = current.authed && current.authed.userId === userId ? current.authed : null;
|
|
2949
|
+
const nextAuthed = { userId };
|
|
2950
|
+
if (priorAuthed) {
|
|
2951
|
+
if (priorAuthed.compass !== void 0) nextAuthed.compass = priorAuthed.compass;
|
|
2952
|
+
if (priorAuthed.address !== void 0) nextAuthed.address = priorAuthed.address;
|
|
2953
|
+
if (priorAuthed.verdicts !== void 0) nextAuthed.verdicts = priorAuthed.verdicts;
|
|
2954
|
+
if (priorAuthed.promotionDismissed !== void 0) {
|
|
2955
|
+
nextAuthed.promotionDismissed = priorAuthed.promotionDismissed;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
if (allowed.compass !== void 0) nextAuthed.compass = allowed.compass;
|
|
2959
|
+
if (allowed.address !== void 0) nextAuthed.address = allowed.address;
|
|
2960
|
+
if (allowed.verdicts !== void 0) nextAuthed.verdicts = allowed.verdicts;
|
|
2961
|
+
if (allowed.promotionDismissed !== void 0 && allowed.promotionDismissed !== null && typeof allowed.promotionDismissed === "object") {
|
|
2962
|
+
const prior = priorAuthed && priorAuthed.promotionDismissed && typeof priorAuthed.promotionDismissed === "object" ? priorAuthed.promotionDismissed : {};
|
|
2963
|
+
nextAuthed.promotionDismissed = { ...prior, ...allowed.promotionDismissed };
|
|
2964
|
+
}
|
|
2965
|
+
return this.set({ ...current, authed: nextAuthed });
|
|
2966
|
+
},
|
|
2967
|
+
/**
|
|
2968
|
+
* Remove the `authed` slice entirely (preserving guest top-level keys).
|
|
2969
|
+
*
|
|
2970
|
+
* Provided for completeness — consumers in the v2026.4.5 wiring plan must
|
|
2971
|
+
* NOT call this on logout. The slice is intended to go inert via userId
|
|
2972
|
+
* mismatch, so a re-login as the same user can rehydrate from cache.
|
|
2973
|
+
*/
|
|
2974
|
+
async clearAuthedSlice() {
|
|
2975
|
+
const current = await this.get();
|
|
2976
|
+
if (!current || typeof current !== "object") return true;
|
|
2977
|
+
if (!("authed" in current)) return true;
|
|
2978
|
+
const { authed: _omit, ...rest } = current;
|
|
2979
|
+
return this.set(rest);
|
|
2980
|
+
},
|
|
2893
2981
|
/**
|
|
2894
2982
|
* Subscribe to live updates. Fires when this tab or any other tab
|
|
2895
2983
|
* (any subdomain) writes to the store. Returns an unsubscribe function.
|
|
@@ -5642,8 +5730,167 @@ function computeTierCoverage(topics) {
|
|
|
5642
5730
|
return counts;
|
|
5643
5731
|
}
|
|
5644
5732
|
|
|
5733
|
+
// src/useEvContextPromotion.js
|
|
5734
|
+
import { useEffect as useEffect9, useState as useState15, useCallback, useRef as useRef3 } from "react";
|
|
5735
|
+
function useEvContextPromotion({
|
|
5736
|
+
domain,
|
|
5737
|
+
isLoggedIn,
|
|
5738
|
+
userId,
|
|
5739
|
+
apiData,
|
|
5740
|
+
apiWriter,
|
|
5741
|
+
enabled = true
|
|
5742
|
+
}) {
|
|
5743
|
+
const [shouldPrompt, setShouldPrompt] = useState15(false);
|
|
5744
|
+
const [payload, setPayload] = useState15(null);
|
|
5745
|
+
const [status, setStatus] = useState15("idle");
|
|
5746
|
+
const [error, setError] = useState15(null);
|
|
5747
|
+
const apiDataRef = useRef3(apiData);
|
|
5748
|
+
apiDataRef.current = apiData;
|
|
5749
|
+
const userIdRef = useRef3(userId);
|
|
5750
|
+
userIdRef.current = userId;
|
|
5751
|
+
const isLoggedInRef = useRef3(isLoggedIn);
|
|
5752
|
+
isLoggedInRef.current = isLoggedIn;
|
|
5753
|
+
const enabledRef = useRef3(enabled);
|
|
5754
|
+
enabledRef.current = enabled;
|
|
5755
|
+
const domainRef = useRef3(domain);
|
|
5756
|
+
domainRef.current = domain;
|
|
5757
|
+
const apiWriterRef = useRef3(apiWriter);
|
|
5758
|
+
apiWriterRef.current = apiWriter;
|
|
5759
|
+
const detect = useCallback(async () => {
|
|
5760
|
+
const _enabled = enabledRef.current;
|
|
5761
|
+
const _isLoggedIn = isLoggedInRef.current;
|
|
5762
|
+
const _userId = userIdRef.current;
|
|
5763
|
+
const _apiData = apiDataRef.current;
|
|
5764
|
+
const _domain = domainRef.current;
|
|
5765
|
+
if (!_enabled || !_isLoggedIn || !_userId) {
|
|
5766
|
+
setShouldPrompt(false);
|
|
5767
|
+
setPayload(null);
|
|
5768
|
+
return;
|
|
5769
|
+
}
|
|
5770
|
+
if (!isApiEmpty(_domain, _apiData)) {
|
|
5771
|
+
setShouldPrompt(false);
|
|
5772
|
+
setPayload(null);
|
|
5773
|
+
return;
|
|
5774
|
+
}
|
|
5775
|
+
try {
|
|
5776
|
+
const slice = await evContext.getAuthedSlice(_userId);
|
|
5777
|
+
if (slice && slice.promotionDismissed && slice.promotionDismissed[_domain] === true) {
|
|
5778
|
+
setShouldPrompt(false);
|
|
5779
|
+
setPayload(null);
|
|
5780
|
+
return;
|
|
5781
|
+
}
|
|
5782
|
+
} catch {
|
|
5783
|
+
}
|
|
5784
|
+
let guest = null;
|
|
5785
|
+
try {
|
|
5786
|
+
guest = await evContext.get();
|
|
5787
|
+
} catch {
|
|
5788
|
+
guest = null;
|
|
5789
|
+
}
|
|
5790
|
+
if (isGuestPopulated(_domain, guest)) {
|
|
5791
|
+
setShouldPrompt(true);
|
|
5792
|
+
setPayload(guest[_domain]);
|
|
5793
|
+
} else {
|
|
5794
|
+
setShouldPrompt(false);
|
|
5795
|
+
setPayload(null);
|
|
5796
|
+
}
|
|
5797
|
+
}, []);
|
|
5798
|
+
useEffect9(() => {
|
|
5799
|
+
detect();
|
|
5800
|
+
}, [detect, isLoggedIn, userId, apiData, enabled, domain]);
|
|
5801
|
+
useEffect9(() => {
|
|
5802
|
+
const unsub = evContext.subscribe(() => {
|
|
5803
|
+
detect();
|
|
5804
|
+
});
|
|
5805
|
+
return unsub;
|
|
5806
|
+
}, [detect]);
|
|
5807
|
+
const promote = useCallback(async () => {
|
|
5808
|
+
const _userId = userIdRef.current;
|
|
5809
|
+
const _domain = domainRef.current;
|
|
5810
|
+
const _writer = apiWriterRef.current;
|
|
5811
|
+
const _payload = payload;
|
|
5812
|
+
if (!_userId || !_payload || typeof _writer !== "function") return;
|
|
5813
|
+
setStatus("saving");
|
|
5814
|
+
setError(null);
|
|
5815
|
+
try {
|
|
5816
|
+
await _writer(_payload);
|
|
5817
|
+
try {
|
|
5818
|
+
await evContext.setAuthedSlice(_userId, { [_domain]: _payload });
|
|
5819
|
+
} catch {
|
|
5820
|
+
}
|
|
5821
|
+
try {
|
|
5822
|
+
await evContext.setAuthedSlice(_userId, {
|
|
5823
|
+
promotionDismissed: { [_domain]: false }
|
|
5824
|
+
});
|
|
5825
|
+
} catch {
|
|
5826
|
+
}
|
|
5827
|
+
setStatus("saved");
|
|
5828
|
+
setShouldPrompt(false);
|
|
5829
|
+
} catch (err) {
|
|
5830
|
+
setStatus("error");
|
|
5831
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5832
|
+
}
|
|
5833
|
+
}, [payload]);
|
|
5834
|
+
const dismiss = useCallback(async () => {
|
|
5835
|
+
const _userId = userIdRef.current;
|
|
5836
|
+
const _domain = domainRef.current;
|
|
5837
|
+
if (!_userId) return;
|
|
5838
|
+
try {
|
|
5839
|
+
await evContext.setAuthedSlice(_userId, {
|
|
5840
|
+
promotionDismissed: { [_domain]: true }
|
|
5841
|
+
});
|
|
5842
|
+
} catch {
|
|
5843
|
+
}
|
|
5844
|
+
setShouldPrompt(false);
|
|
5845
|
+
}, []);
|
|
5846
|
+
return { shouldPrompt, payload, promote, dismiss, status, error };
|
|
5847
|
+
}
|
|
5848
|
+
function isApiEmpty(domain, apiData) {
|
|
5849
|
+
if (apiData === null || apiData === void 0) return true;
|
|
5850
|
+
if (domain === "compass") {
|
|
5851
|
+
if (typeof apiData !== "object") return false;
|
|
5852
|
+
if (Array.isArray(apiData)) return apiData.length === 0;
|
|
5853
|
+
if (apiData.answers && typeof apiData.answers === "object") {
|
|
5854
|
+
return Object.keys(apiData.answers).length === 0;
|
|
5855
|
+
}
|
|
5856
|
+
return Object.keys(apiData).length === 0;
|
|
5857
|
+
}
|
|
5858
|
+
if (domain === "address") {
|
|
5859
|
+
if (typeof apiData === "string") return apiData.trim().length === 0;
|
|
5860
|
+
if (typeof apiData !== "object") return false;
|
|
5861
|
+
const a = apiData;
|
|
5862
|
+
const hasAddr = typeof a.formatted === "string" && a.formatted.length > 0;
|
|
5863
|
+
const hasAddrAlt = typeof a.addr === "string" && a.addr.length > 0;
|
|
5864
|
+
const hasLat = typeof a.lat === "number" || typeof a.latitude === "number";
|
|
5865
|
+
return !(hasAddr || hasAddrAlt || hasLat);
|
|
5866
|
+
}
|
|
5867
|
+
if (domain === "verdicts") {
|
|
5868
|
+
if (typeof apiData !== "object") return false;
|
|
5869
|
+
if (Array.isArray(apiData)) return apiData.length === 0;
|
|
5870
|
+
return Object.keys(apiData).length === 0;
|
|
5871
|
+
}
|
|
5872
|
+
return false;
|
|
5873
|
+
}
|
|
5874
|
+
function isGuestPopulated(domain, fullEvContext) {
|
|
5875
|
+
if (!fullEvContext || typeof fullEvContext !== "object") return false;
|
|
5876
|
+
const slice = fullEvContext[domain];
|
|
5877
|
+
if (!slice || typeof slice !== "object") return false;
|
|
5878
|
+
if (domain === "compass") {
|
|
5879
|
+
const ans = slice.answers || slice.a;
|
|
5880
|
+
if (ans && typeof ans === "object") return Object.keys(ans).length > 0;
|
|
5881
|
+
return false;
|
|
5882
|
+
}
|
|
5883
|
+
if (domain === "address") {
|
|
5884
|
+
return typeof slice.formatted === "string" && slice.formatted.length > 0 || typeof slice.addr === "string" && slice.addr.length > 0;
|
|
5885
|
+
}
|
|
5886
|
+
if (domain === "verdicts") {
|
|
5887
|
+
return Object.keys(slice).length > 0;
|
|
5888
|
+
}
|
|
5889
|
+
return false;
|
|
5890
|
+
}
|
|
5891
|
+
|
|
5645
5892
|
// src/StanceAccordion.jsx
|
|
5646
|
-
import React29, { useState as
|
|
5893
|
+
import React29, { useState as useState16, useRef as useRef4, useCallback as useCallback2, useEffect as useEffect10 } from "react";
|
|
5647
5894
|
|
|
5648
5895
|
// src/Favicon.jsx
|
|
5649
5896
|
import React28 from "react";
|
|
@@ -5704,11 +5951,11 @@ function StanceAccordion({
|
|
|
5704
5951
|
apiUrl = "https://api.empowered.vote"
|
|
5705
5952
|
}) {
|
|
5706
5953
|
var _a;
|
|
5707
|
-
const [expandedTopicId, setExpandedTopicId] =
|
|
5708
|
-
const [loadingId, setLoadingId] =
|
|
5709
|
-
const [showAll, setShowAll] =
|
|
5710
|
-
const contextCache =
|
|
5711
|
-
const quotesCache =
|
|
5954
|
+
const [expandedTopicId, setExpandedTopicId] = useState16(null);
|
|
5955
|
+
const [loadingId, setLoadingId] = useState16(null);
|
|
5956
|
+
const [showAll, setShowAll] = useState16(false);
|
|
5957
|
+
const contextCache = useRef4(/* @__PURE__ */ new Map());
|
|
5958
|
+
const quotesCache = useRef4(null);
|
|
5712
5959
|
const polAnswerMap = {};
|
|
5713
5960
|
if (polAnswers) {
|
|
5714
5961
|
polAnswers.forEach((a) => {
|
|
@@ -5767,7 +6014,7 @@ function StanceAccordion({
|
|
|
5767
6014
|
quotesCache.current = [];
|
|
5768
6015
|
}
|
|
5769
6016
|
}
|
|
5770
|
-
const handleToggle =
|
|
6017
|
+
const handleToggle = useCallback2(
|
|
5771
6018
|
async (topicId) => {
|
|
5772
6019
|
if (expandedTopicId === topicId) {
|
|
5773
6020
|
setExpandedTopicId(null);
|
|
@@ -5784,7 +6031,7 @@ function StanceAccordion({
|
|
|
5784
6031
|
},
|
|
5785
6032
|
[expandedTopicId, politicianId, apiUrl]
|
|
5786
6033
|
);
|
|
5787
|
-
|
|
6034
|
+
useEffect10(() => {
|
|
5788
6035
|
if (initialExpandedTopicId && topics && topics.length > 0) {
|
|
5789
6036
|
handleToggle(String(initialExpandedTopicId));
|
|
5790
6037
|
}
|
|
@@ -6061,6 +6308,7 @@ export {
|
|
|
6061
6308
|
spacing,
|
|
6062
6309
|
textStyles,
|
|
6063
6310
|
tierColors,
|
|
6311
|
+
useEvContextPromotion,
|
|
6064
6312
|
useMediaQuery,
|
|
6065
6313
|
zIndex
|
|
6066
6314
|
};
|