terry-wallet-login #15
@@ -23,6 +23,7 @@ type FavoriteStatus = "unknown" | "favorited" | "notFavorited";
|
|||||||
type FavoritesContextValue = {
|
type FavoritesContextValue = {
|
||||||
favoriteIds: Set<string>;
|
favoriteIds: Set<string>;
|
||||||
pendingIds: Set<string>;
|
pendingIds: Set<string>;
|
||||||
|
mutationVersion: number;
|
||||||
statusFor: (resourceId: string) => FavoriteStatus;
|
statusFor: (resourceId: string) => FavoriteStatus;
|
||||||
ensureFavoriteIds: (resourceIds: string[]) => Promise<void>;
|
ensureFavoriteIds: (resourceIds: string[]) => Promise<void>;
|
||||||
toggleFavorite: (resourceId: string) => Promise<boolean | null>;
|
toggleFavorite: (resourceId: string) => Promise<boolean | null>;
|
||||||
@@ -45,6 +46,7 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
|
|||||||
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 [mutationVersion, setMutationVersion] = useState(0);
|
||||||
const pendingAfterLoginRef = useRef<string | null>(null);
|
const pendingAfterLoginRef = useRef<string | null>(null);
|
||||||
const lastAddressRef = useRef<string | null>(null);
|
const lastAddressRef = useRef<string | null>(null);
|
||||||
const knownIdsRef = useRef<Set<string>>(new Set());
|
const knownIdsRef = useRef<Set<string>>(new Set());
|
||||||
@@ -170,6 +172,7 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
|
|||||||
showToast(
|
showToast(
|
||||||
currentlyFavorite ? t("favoriteRemoved") : t("favoriteAdded"),
|
currentlyFavorite ? t("favoriteRemoved") : t("favoriteAdded"),
|
||||||
);
|
);
|
||||||
|
setMutationVersion((value) => value + 1);
|
||||||
return nextFavorited;
|
return nextFavorited;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
markFavorite(resourceId, currentlyFavorite);
|
markFavorite(resourceId, currentlyFavorite);
|
||||||
@@ -221,6 +224,7 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
|
|||||||
() => ({
|
() => ({
|
||||||
favoriteIds,
|
favoriteIds,
|
||||||
pendingIds,
|
pendingIds,
|
||||||
|
mutationVersion,
|
||||||
statusFor,
|
statusFor,
|
||||||
ensureFavoriteIds,
|
ensureFavoriteIds,
|
||||||
toggleFavorite,
|
toggleFavorite,
|
||||||
@@ -230,6 +234,7 @@ export function FavoritesProvider({ children }: { children: ReactNode }) {
|
|||||||
ensureFavoriteIds,
|
ensureFavoriteIds,
|
||||||
favoriteIds,
|
favoriteIds,
|
||||||
markFavorite,
|
markFavorite,
|
||||||
|
mutationVersion,
|
||||||
pendingIds,
|
pendingIds,
|
||||||
statusFor,
|
statusFor,
|
||||||
toggleFavorite,
|
toggleFavorite,
|
||||||
|
|||||||
@@ -12,6 +12,17 @@ import { useWallet } from "../../wallet/WalletProvider";
|
|||||||
|
|
||||||
const pageSize = 50;
|
const pageSize = 50;
|
||||||
|
|
||||||
|
type FavoritePosts = Awaited<ReturnType<typeof listFavorites>>["items"];
|
||||||
|
|
||||||
|
type FavoriteListCache = {
|
||||||
|
address: string;
|
||||||
|
lang: Lang;
|
||||||
|
mutationVersion: number;
|
||||||
|
posts: FavoritePosts;
|
||||||
|
};
|
||||||
|
|
||||||
|
let favoriteListCache: FavoriteListCache | null = null;
|
||||||
|
|
||||||
function useCategories(lang: Lang): Category[] {
|
function useCategories(lang: Lang): Category[] {
|
||||||
const [categories, setCategories] = useState<Category[]>(() => {
|
const [categories, setCategories] = useState<Category[]>(() => {
|
||||||
const cached = readJSONCache<Category[]>(
|
const cached = readJSONCache<Category[]>(
|
||||||
@@ -43,11 +54,9 @@ function useCategories(lang: Lang): Category[] {
|
|||||||
export default function Favorites() {
|
export default function Favorites() {
|
||||||
const { lang, t } = useI18n();
|
const { lang, t } = useI18n();
|
||||||
const wallet = useWallet();
|
const wallet = useWallet();
|
||||||
const { markFavorite } = useFavorites();
|
const { markFavorite, mutationVersion } = useFavorites();
|
||||||
const categories = useCategories(lang);
|
const categories = useCategories(lang);
|
||||||
const [posts, setPosts] = useState<
|
const [posts, setPosts] = useState<FavoritePosts>([]);
|
||||||
Awaited<ReturnType<typeof listFavorites>>["items"]
|
|
||||||
>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
@@ -56,7 +65,7 @@ export default function Favorites() {
|
|||||||
useSetPageTitle(t("favorites"));
|
useSetPageTitle(t("favorites"));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!wallet.token || wallet.status !== "loggedIn") {
|
if (!wallet.token || wallet.status !== "loggedIn" || !wallet.address) {
|
||||||
setPosts([]);
|
setPosts([]);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setLoaded(false);
|
setLoaded(false);
|
||||||
@@ -64,18 +73,40 @@ export default function Favorites() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const walletAddress = wallet.address;
|
||||||
|
const walletToken = wallet.token;
|
||||||
|
|
||||||
|
if (
|
||||||
|
reloadKey === 0 &&
|
||||||
|
favoriteListCache?.address === walletAddress &&
|
||||||
|
favoriteListCache.lang === lang &&
|
||||||
|
favoriteListCache.mutationVersion === mutationVersion
|
||||||
|
) {
|
||||||
|
setPosts(favoriteListCache.posts);
|
||||||
|
setLoading(false);
|
||||||
|
setLoaded(true);
|
||||||
|
setError("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setLoaded(false);
|
setLoaded(false);
|
||||||
setError("");
|
setError("");
|
||||||
|
|
||||||
listFavorites(wallet.token, {
|
listFavorites(walletToken, {
|
||||||
limit: pageSize,
|
limit: pageSize,
|
||||||
includeUnavailable: true,
|
includeUnavailable: true,
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
const items = itemsOrEmpty(data.items);
|
const items = itemsOrEmpty(data.items);
|
||||||
|
favoriteListCache = {
|
||||||
|
address: walletAddress,
|
||||||
|
lang,
|
||||||
|
mutationVersion,
|
||||||
|
posts: items,
|
||||||
|
};
|
||||||
setPosts(items);
|
setPosts(items);
|
||||||
items.forEach((post) => markFavorite(post.id, true));
|
items.forEach((post) => markFavorite(post.id, true));
|
||||||
setLoaded(true);
|
setLoaded(true);
|
||||||
@@ -97,7 +128,7 @@ export default function Favorites() {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [markFavorite, reloadKey, t, wallet]);
|
}, [lang, markFavorite, mutationVersion, reloadKey, t, wallet]);
|
||||||
|
|
||||||
if (wallet.status === "loading") {
|
if (wallet.status === "loading") {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user