fix: clean up wallet favorites state
This commit is contained in:
@@ -30,19 +30,22 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const wallet = useWallet();
|
const wallet = useWallet();
|
||||||
|
const { address, openLoginModal, status, token } = wallet;
|
||||||
const [favoriteIds, setFavoriteIds] = useState<Set<string>>(() => new Set());
|
const [favoriteIds, setFavoriteIds] = useState<Set<string>>(() => new Set());
|
||||||
const [knownIds, setKnownIds] = useState<Set<string>>(() => new Set());
|
const [knownIds, setKnownIds] = useState<Set<string>>(() => new Set());
|
||||||
const [pendingIds, setPendingIds] = useState<Set<string>>(() => new Set());
|
const [pendingIds, setPendingIds] = useState<Set<string>>(() => new Set());
|
||||||
const pendingAfterLoginRef = useRef<string | null>(null);
|
const pendingAfterLoginRef = useRef<string | null>(null);
|
||||||
|
const lastAddressRef = useRef<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (wallet.status === "loggedOut") {
|
const nextAddress = status === "loggedIn" ? address : null;
|
||||||
setFavoriteIds(new Set());
|
if (lastAddressRef.current === nextAddress) return;
|
||||||
setKnownIds(new Set());
|
lastAddressRef.current = nextAddress;
|
||||||
setPendingIds(new Set());
|
setFavoriteIds(new Set());
|
||||||
pendingAfterLoginRef.current = null;
|
setKnownIds(new Set());
|
||||||
}
|
setPendingIds(new Set());
|
||||||
}, [wallet.status]);
|
if (!nextAddress) pendingAfterLoginRef.current = null;
|
||||||
|
}, [address, status]);
|
||||||
|
|
||||||
const markFavorite = useCallback((resourceId: string, favorited: boolean) => {
|
const markFavorite = useCallback((resourceId: string, favorited: boolean) => {
|
||||||
setKnownIds((prev) => new Set(prev).add(resourceId));
|
setKnownIds((prev) => new Set(prev).add(resourceId));
|
||||||
@@ -56,12 +59,12 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
const ensureFavoriteIds = useCallback(
|
const ensureFavoriteIds = useCallback(
|
||||||
async (resourceIds: string[]) => {
|
async (resourceIds: string[]) => {
|
||||||
if (!wallet.token || wallet.status !== "loggedIn") return;
|
if (!token || status !== "loggedIn") return;
|
||||||
const missing = [...new Set(resourceIds)].filter(
|
const missing = [...new Set(resourceIds)].filter(
|
||||||
(id) => !knownIds.has(id),
|
(id) => !knownIds.has(id),
|
||||||
);
|
);
|
||||||
if (missing.length === 0) return;
|
if (missing.length === 0) return;
|
||||||
const ids = await getFavoriteIds(wallet.token, missing);
|
const ids = await getFavoriteIds(token, missing);
|
||||||
setKnownIds((prev) => {
|
setKnownIds((prev) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
missing.forEach((id) => next.add(id));
|
missing.forEach((id) => next.add(id));
|
||||||
@@ -73,18 +76,18 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
|
|||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[knownIds, wallet.status, wallet.token],
|
[knownIds, status, token],
|
||||||
);
|
);
|
||||||
|
|
||||||
const runFavoriteMutation = useCallback(
|
const runFavoriteMutation = useCallback(
|
||||||
async (resourceId: string) => {
|
async (resourceId: string) => {
|
||||||
if (!wallet.token) return;
|
if (!token) return;
|
||||||
const currentlyFavorite = favoriteIds.has(resourceId);
|
const currentlyFavorite = favoriteIds.has(resourceId);
|
||||||
setPendingIds((prev) => new Set(prev).add(resourceId));
|
setPendingIds((prev) => new Set(prev).add(resourceId));
|
||||||
markFavorite(resourceId, !currentlyFavorite);
|
markFavorite(resourceId, !currentlyFavorite);
|
||||||
try {
|
try {
|
||||||
if (currentlyFavorite) await removeFavorite(wallet.token, resourceId);
|
if (currentlyFavorite) await removeFavorite(token, resourceId);
|
||||||
else await addFavorite(wallet.token, resourceId);
|
else await addFavorite(token, resourceId);
|
||||||
showToast(
|
showToast(
|
||||||
currentlyFavorite ? t("favoriteRemoved") : t("favoriteAdded"),
|
currentlyFavorite ? t("favoriteRemoved") : t("favoriteAdded"),
|
||||||
);
|
);
|
||||||
@@ -100,29 +103,29 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[favoriteIds, markFavorite, showToast, t, wallet.token],
|
[favoriteIds, markFavorite, showToast, t, token],
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleFavorite = useCallback(
|
const toggleFavorite = useCallback(
|
||||||
async (resourceId: string) => {
|
async (resourceId: string) => {
|
||||||
if (!wallet.token || wallet.status !== "loggedIn") {
|
if (!token || status !== "loggedIn") {
|
||||||
pendingAfterLoginRef.current = resourceId;
|
pendingAfterLoginRef.current = resourceId;
|
||||||
wallet.openLoginModal();
|
openLoginModal();
|
||||||
showToast(t("favoriteLoginRequired"));
|
showToast(t("favoriteLoginRequired"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await runFavoriteMutation(resourceId);
|
await runFavoriteMutation(resourceId);
|
||||||
},
|
},
|
||||||
[runFavoriteMutation, showToast, t, wallet],
|
[openLoginModal, runFavoriteMutation, showToast, status, t, token],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (wallet.status !== "loggedIn" || !wallet.token) return;
|
if (status !== "loggedIn" || !token) return;
|
||||||
const pending = pendingAfterLoginRef.current;
|
const pending = pendingAfterLoginRef.current;
|
||||||
if (!pending) return;
|
if (!pending) return;
|
||||||
pendingAfterLoginRef.current = null;
|
pendingAfterLoginRef.current = null;
|
||||||
void runFavoriteMutation(pending).catch(() => undefined);
|
void runFavoriteMutation(pending).catch(() => undefined);
|
||||||
}, [runFavoriteMutation, wallet.status, wallet.token]);
|
}, [runFavoriteMutation, status, token]);
|
||||||
|
|
||||||
const statusFor = useCallback(
|
const statusFor = useCallback(
|
||||||
(resourceId: string): FavoriteStatus => {
|
(resourceId: string): FavoriteStatus => {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ function FavoriteResourceCard({ resource }: { resource: Resource }) {
|
|||||||
const lp = useLocalizedPath();
|
const lp = useLocalizedPath();
|
||||||
const unavailable = resource.availability === "unavailable";
|
const unavailable = resource.availability === "unavailable";
|
||||||
const cover = resource.coverImage || resource.previewUrl;
|
const cover = resource.coverImage || resource.previewUrl;
|
||||||
const content = (
|
return (
|
||||||
<article
|
<article
|
||||||
className={`group relative flex min-h-[132px] gap-4 rounded-2xl border bg-[#272632] p-3 transition md:p-4 ${
|
className={`group relative flex min-h-[132px] gap-4 rounded-2xl border bg-[#272632] p-3 transition md:p-4 ${
|
||||||
unavailable
|
unavailable
|
||||||
@@ -60,7 +60,14 @@ function FavoriteResourceCard({ resource }: { resource: Resource }) {
|
|||||||
: "border-[#27292E] hover:border-ark-gold/50"
|
: "border-[#27292E] hover:border-ark-gold/50"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="relative h-[96px] w-[112px] shrink-0 overflow-hidden rounded-xl bg-[#111116] md:h-[116px] md:w-[150px]">
|
{!unavailable ? (
|
||||||
|
<Link
|
||||||
|
to={lp(`/resource/${resource.id}`)}
|
||||||
|
aria-label={resource.title}
|
||||||
|
className="absolute inset-0 z-0 rounded-2xl outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/80"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div className="relative z-10 h-[96px] w-[112px] shrink-0 overflow-hidden rounded-xl bg-[#111116] md:h-[116px] md:w-[150px]">
|
||||||
{cover && !unavailable ? (
|
{cover && !unavailable ? (
|
||||||
<img
|
<img
|
||||||
src={assetUrl(cover)}
|
src={assetUrl(cover)}
|
||||||
@@ -81,7 +88,7 @@ function FavoriteResourceCard({ resource }: { resource: Resource }) {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex min-w-0 flex-1 flex-col gap-2 pr-11">
|
<div className="pointer-events-none relative z-10 flex min-w-0 flex-1 flex-col gap-2 pr-11">
|
||||||
<h2 className="line-clamp-2 text-base font-bold leading-snug text-white md:text-lg">
|
<h2 className="line-clamp-2 text-base font-bold leading-snug text-white md:text-lg">
|
||||||
{resource.title}
|
{resource.title}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -107,17 +114,10 @@ function FavoriteResourceCard({ resource }: { resource: Resource }) {
|
|||||||
|
|
||||||
<FavoriteButton
|
<FavoriteButton
|
||||||
resourceId={resource.id}
|
resourceId={resource.id}
|
||||||
className="absolute right-3 top-3 z-10"
|
className="absolute right-3 top-3 z-20"
|
||||||
/>
|
/>
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (unavailable) return content;
|
|
||||||
return (
|
|
||||||
<Link to={lp(`/resource/${resource.id}`)} className="block">
|
|
||||||
{content}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Favorites() {
|
export default function Favorites() {
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ type ModalState = "idle" | "tpLoading" | "tpPolling" | "rainbowSigning";
|
|||||||
export function WalletLoginModal() {
|
export function WalletLoginModal() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const wallet = useWallet();
|
const { closeLoginModal, completeLogin, loginModalOpen, signInInjected } =
|
||||||
|
useWallet();
|
||||||
const { openConnectModal } = useConnectModal();
|
const { openConnectModal } = useConnectModal();
|
||||||
const { address, isConnected } = useAccount();
|
const { address, isConnected } = useAccount();
|
||||||
const { signMessageAsync } = useSignMessage();
|
const { signMessageAsync } = useSignMessage();
|
||||||
@@ -35,12 +36,12 @@ export function WalletLoginModal() {
|
|||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
if (state === "tpLoading" || state === "rainbowSigning") return;
|
if (state === "tpLoading" || state === "rainbowSigning") return;
|
||||||
wallet.closeLoginModal();
|
closeLoginModal();
|
||||||
setError("");
|
setError("");
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!wallet.loginModalOpen || !tpRequest) return;
|
if (!loginModalOpen || !tpRequest) return;
|
||||||
if (state !== "tpPolling") return;
|
if (state !== "tpPolling") return;
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
@@ -55,7 +56,7 @@ export function WalletLoginModal() {
|
|||||||
signature: result.signature,
|
signature: result.signature,
|
||||||
});
|
});
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
wallet.completeLogin(verified.token, verified.wallet);
|
completeLogin(verified.token, verified.wallet);
|
||||||
showToast(t("walletLoginSuccess"));
|
showToast(t("walletLoginSuccess"));
|
||||||
setState("idle");
|
setState("idle");
|
||||||
setTpRequest(null);
|
setTpRequest(null);
|
||||||
@@ -76,7 +77,7 @@ export function WalletLoginModal() {
|
|||||||
cancelled = true;
|
cancelled = true;
|
||||||
window.clearInterval(timer);
|
window.clearInterval(timer);
|
||||||
};
|
};
|
||||||
}, [state, tpRequest, t, showToast, wallet]);
|
}, [completeLogin, loginModalOpen, state, tpRequest, t, showToast]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!rainbowPending || !isConnected || !address) return;
|
if (!rainbowPending || !isConnected || !address) return;
|
||||||
@@ -94,7 +95,7 @@ export function WalletLoginModal() {
|
|||||||
message: nonce.message,
|
message: nonce.message,
|
||||||
signature,
|
signature,
|
||||||
});
|
});
|
||||||
wallet.completeLogin(verified.token, verified.wallet);
|
completeLogin(verified.token, verified.wallet);
|
||||||
showToast(t("walletLoginSuccess"));
|
showToast(t("walletLoginSuccess"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message =
|
const message =
|
||||||
@@ -114,15 +115,15 @@ export function WalletLoginModal() {
|
|||||||
showToast,
|
showToast,
|
||||||
signMessageAsync,
|
signMessageAsync,
|
||||||
t,
|
t,
|
||||||
wallet,
|
completeLogin,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!wallet.loginModalOpen) return null;
|
if (!loginModalOpen) return null;
|
||||||
|
|
||||||
const startInjected = async () => {
|
const startInjected = async () => {
|
||||||
setError("");
|
setError("");
|
||||||
setState("idle");
|
setState("idle");
|
||||||
await wallet.signInInjected().catch(() => undefined);
|
await signInInjected().catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const startTokenPocketQr = async () => {
|
const startTokenPocketQr = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user