Skip to content

Commit

Permalink
⚡️ Fetch achievements data at iframe creation (#992)
Browse files Browse the repository at this point in the history
* ⚡️ Fetch achievements data at iframe creation

* 🔥 Remove useless useCallback dep

* ⚡️ Move data from app component to context

* 🐛 Fix pinned icons not updated when changing profile

* 💄 Code style

* ✨ Refetch events every 5 minutes
  • Loading branch information
bal7hazar authored Nov 6, 2024
1 parent 06abf9a commit ed8a564
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 31 deletions.
32 changes: 32 additions & 0 deletions packages/profile/src/components/context/data.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createContext, useState, ReactNode } from "react";
import { useAchievements } from "@/hooks/achievements";

type DataContextType = {
trophies: ReturnType<typeof useAchievements>;
setAccountAddress: (address: string | undefined) => void;
};

const initialState: DataContextType = {
trophies: {
achievements: [],
players: [],
isLoading: false,
},
setAccountAddress: () => {},
};

export const DataContext = createContext<DataContextType>(initialState);

export function DataProvider({ children }: { children: ReactNode }) {
const [accountAddress, setAccountAddress] = useState<string | undefined>(
undefined,
);

const trophies = useAchievements(accountAddress);

return (
<DataContext.Provider value={{ trophies, setAccountAddress }}>
{children}
</DataContext.Provider>
);
}
1 change: 1 addition & 0 deletions packages/profile/src/components/context/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { ConnectionContext } from "./connection";
export { DataContext } from "./data";
export { ThemeContext } from "./theme";
export { Provider } from "./provider";
5 changes: 4 additions & 1 deletion packages/profile/src/components/context/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ConnectionProvider } from "./connection";
import { BrowserRouter } from "react-router-dom";
import { CartridgeAPIProvider } from "@cartridge/utils/api/cartridge";
import { IndexerAPIProvider } from "@cartridge/utils/api/indexer";
import { DataProvider } from "./data";

export function Provider({ children }: PropsWithChildren) {
const queryClient = new QueryClient();
Expand All @@ -17,7 +18,9 @@ export function Provider({ children }: PropsWithChildren) {
>
<IndexerAPIProvider credentials="omit">
<QueryClientProvider client={queryClient}>
<ConnectionProvider>{children}</ConnectionProvider>
<ConnectionProvider>
<DataProvider>{children}</DataProvider>
</ConnectionProvider>
</QueryClientProvider>
</IndexerAPIProvider>
</CartridgeAPIProvider>
Expand Down
2 changes: 1 addition & 1 deletion packages/profile/src/components/trophies/achievements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ function Page({

const handleMouseLeave = useCallback(() => {
setHover(false);
}, [highlighted]);
}, []);

return (
<div
Expand Down
20 changes: 12 additions & 8 deletions packages/profile/src/components/trophies/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@ import { TrophiesTab, LeaderboardTab, Scoreboard } from "./tab";
import { useAccount, useUsername } from "@/hooks/account";
import { CopyAddress } from "@cartridge/ui-next";
import { Navigation } from "../navigation";
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useParams } from "react-router-dom";
import { Achievements } from "./achievements";
import { Pinneds } from "./pinneds";
import { Leaderboard } from "./leaderboard";
import { useAchievements } from "@/hooks/achievements";
import { useConnection } from "@/hooks/context";
import { useData } from "@/hooks/context";

export function Trophies() {
const { username: selfname, address: self } = useAccount();
const {
trophies: { achievements, players, isLoading },
setAccountAddress,
} = useData();

const location = useLocation();
const { namespace } = useConnection();
const { address } = useParams<{ address: string }>();
const { username } = useUsername({ address: address || self || "" });
const { achievements, players, isLoading } = useAchievements({
namespace: namespace ?? "",
address: address || self || "",
});

const [activeTab, setActiveTab] = useState<"trophies" | "leaderboard">(
"trophies",
);
Expand Down Expand Up @@ -54,6 +54,10 @@ export function Trophies() {
return !address || address === self;
}, [address, self]);

useEffect(() => {
setAccountAddress(address || self || "");
}, [address, self, setAccountAddress]);

return (
<LayoutContainer
left={
Expand Down
8 changes: 6 additions & 2 deletions packages/profile/src/components/trophies/pinneds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import { Pinned, Empty } from "./pinned";
export function Pinneds({ achievements }: { achievements: Item[] }) {
return (
<div className="grid grid-cols-3 gap-4">
{achievements.map((achievement, index) => (
<Pinned key={index} icon={achievement.icon} title={achievement.title} />
{achievements.map((achievement) => (
<Pinned
key={achievement.id}
icon={achievement.icon}
title={achievement.title}
/>
))}
{Array.from({ length: 3 - achievements.length }).map((_, index) => (
<Empty key={index} />
Expand Down
29 changes: 16 additions & 13 deletions packages/profile/src/hooks/achievements.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { TROPHY, PROGRESS } from "@/constants";
import { useEvents } from "./events";
import { Trophy, Progress } from "@/models";
import { AchievementTask } from "@/components/trophies/achievement";
import { useConnection } from "./context";
import { useAccount } from "./account";

// Number of events to fetch at a time, could be increased if needed
const LIMIT = 1000;
Expand Down Expand Up @@ -37,27 +39,28 @@ export interface Player {
timestamp: number;
}

export function useAchievements({
namespace,
address,
}: {
namespace: string;
address: string;
}) {
export function useAchievements(accountAddress?: string) {
const { namespace } = useConnection();
const { address } = useAccount();

const [isLoading, setIsLoading] = useState(true);
const [achievements, setAchievements] = useState<Item[]>([]);
const [players, setPlayers] = useState<Player[]>([]);

const currentAddress = useMemo(() => {
return accountAddress || address;
}, [accountAddress, address]);

const { events: trophies, isFetching: isFetchingTrophiess } =
useEvents<Trophy>({
namespace,
namespace: namespace || "",
name: TROPHY,
limit: LIMIT,
parse: Trophy.parse,
});
const { events: progresses, isFetching: isFetchingProgresses } =
useEvents<Progress>({
namespace,
namespace: namespace || "",
name: PROGRESS,
limit: LIMIT,
parse: Progress.parse,
Expand All @@ -69,7 +72,7 @@ export function useAchievements({
isFetchingTrophiess ||
isFetchingProgresses ||
!trophies.length ||
!address
!currentAddress
)
return;

Expand Down Expand Up @@ -136,7 +139,7 @@ export function useAchievements({
trophy.tasks.forEach((task) => {
let count = 0;
let completion = false;
counters[address]?.[task.id]
counters[currentAddress]?.[task.id]
?.sort((a, b) => a.timestamp - b.timestamp)
.forEach(
({
Expand Down Expand Up @@ -183,7 +186,7 @@ export function useAchievements({
// Update loading state
setIsLoading(false);
}, [
address,
currentAddress,
trophies,
progresses,
isFetchingTrophiess,
Expand Down
10 changes: 9 additions & 1 deletion packages/profile/src/hooks/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { useContext } from "react";
import { ThemeContext, ConnectionContext } from "@/components/context";
import {
ThemeContext,
ConnectionContext,
DataContext,
} from "@/components/context";

export function useTheme() {
return useContext(ThemeContext);
Expand All @@ -8,3 +12,7 @@ export function useTheme() {
export function useConnection() {
return useContext(ConnectionContext);
}

export function useData() {
return useContext(DataContext);
}
11 changes: 6 additions & 5 deletions packages/profile/src/hooks/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import { Event, EventNode, useEventsQuery } from "@cartridge/utils/api/indexer";
import { Trophy, Progress } from "@/models";
import { hash, byteArray, ByteArray } from "starknet";
import { useIndexerAPI } from "@cartridge/utils";

const EVENT_WRAPPER = "EventEmitted";

Expand Down Expand Up @@ -46,13 +47,13 @@ export function useEvents<TEvent extends Trophy | Progress>({
limit: number;
parse: (node: EventNode) => TEvent;
}) {
const { indexerUrl } = useIndexerAPI();
const [offset, setOffset] = useState(0);
const [isFetching, setIsFetching] = useState(true);
const [nodes, setNodes] = useState<{ [key: string]: boolean }>({});
const [events, setEvents] = useState<TEvent[]>([]);

// Fetch achievement creations from raw events
const { refetch: fetchEvents } = useEventsQuery(
const { refetch: fetchEvents, isFetching } = useEventsQuery(
{
keys: [
getSelectorFromName(EVENT_WRAPPER),
Expand All @@ -63,12 +64,11 @@ export function useEvents<TEvent extends Trophy | Progress>({
},
{
enabled: false,
refetchInterval: 300_000, // Refetch every 5 minutes
onSuccess: ({ events }: { events: Event }) => {
// Update offset
if (events.pageInfo.hasNextPage) {
setOffset(offset + limit);
} else {
setIsFetching(false);
}
// Parse the events
const results: TEvent[] = [];
Expand All @@ -85,8 +85,9 @@ export function useEvents<TEvent extends Trophy | Progress>({
);

useEffect(() => {
if (!indexerUrl) return;
fetchEvents();
}, [offset, fetchEvents]);
}, [offset, indexerUrl, fetchEvents]);

return { events, isFetching };
}

0 comments on commit ed8a564

Please sign in to comment.