Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Make the connection with the new gateway #1110

Merged
merged 7 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions packages/controller/src/iframe/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ export class ProfileIFrame extends IFrame<Profile> {
const _url = new URL(
slot
? namespace
? `${_profileUrl}/account/${username}/slot/${slot}?ns=${encodeURIComponent(
namespace,
? `${_profileUrl}/account/${username}/slot/${slot}?ps=${encodeURIComponent(
slot,
)}&ns=${encodeURIComponent(namespace)}`
: `${_profileUrl}/account/${username}/slot/${slot}?ps=${encodeURIComponent(
slot,
)}`
: `${_profileUrl}/account/${username}/slot/${slot}`
: `${_profileUrl}/account/${username}`,
);

Expand Down
2 changes: 1 addition & 1 deletion packages/profile/src/components/achievements/trophies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
setPages(pages);
const page = filtereds.find((a) => !a.completed);
setPage(page ? page.index : pages[pages.length - 1]);
}, [items]);
}, []);

Check warning on line 124 in packages/profile/src/components/achievements/trophies.tsx

View workflow job for this annotation

GitHub Actions / ts-lint

React Hook useEffect has a missing dependency: 'items'. Either include it or remove the dependency array

const handleNext = useCallback(() => {
const index = pages.indexOf(page);
Expand Down
8 changes: 7 additions & 1 deletion packages/profile/src/components/context/connection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type ConnectionContextType = {
provider: RpcProvider;
chainId: string;
erc20: string[];
project?: string;
namespace?: string;
isVisible: boolean;
setIsVisible: (isVisible: boolean) => void;
Expand All @@ -34,7 +35,7 @@ type ParentMethods = {
};

const initialState: ConnectionContextType = {
origin: "",
origin: location.origin,
parent: {
close: () => {},
openSettings: () => {},
Expand Down Expand Up @@ -65,6 +66,11 @@ export function ConnectionProvider({ children }: { children: ReactNode }) {
});
}

const psParam = searchParams.get("ps");
if (psParam && !state.project) {
state.project = decodeURIComponent(psParam);
}

const nsParam = searchParams.get("ns");
if (nsParam && !state.namespace) {
state.namespace = decodeURIComponent(nsParam);
Expand Down
262 changes: 131 additions & 131 deletions packages/profile/src/hooks/achievements.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { useEffect, useMemo, useState } from "react";
import { TROPHY, PROGRESS } from "@/constants";
import { useEvents } from "./events";
import { Trophy, Progress } from "@/models";
import { Task } from "@/components/achievements/trophy";
import { Trophy, Progress, Task } from "@/models";
import { useConnection } from "./context";
import { useAccount } from "./account";

// Number of events to fetch at a time, could be increased if needed
const LIMIT = 1000;
import { useProgressions } from "./progressions";
import { useTrophies } from "./trophies";
import { Task as ItemTask } from "@/components/achievements/trophy";

export interface Item {
id: string;
Expand All @@ -22,7 +20,7 @@
percentage: string;
completed: boolean;
pinned: boolean;
tasks: Task[];
tasks: ItemTask[];
}

export interface Counters {
Expand All @@ -41,7 +39,7 @@
}

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

const [isLoading, setIsLoading] = useState(true);
Expand All @@ -52,158 +50,160 @@
return accountAddress || address;
}, [accountAddress, address]);

const { events: trophies, isFetching: isFetchingTrophiess } =
useEvents<Trophy>({
namespace: namespace || "",
const { trophies: rawTrophies, isFetching: isFetchingTrophies } = useTrophies(
{
namespace: namespace ?? "",
name: TROPHY,
limit: LIMIT,
parse: Trophy.parse,
});
const { events: progresses, isFetching: isFetchingProgresses } =
useEvents<Progress>({
namespace: namespace || "",
project: project ?? "",
parser: Trophy.parse,
},
);

const { progressions: rawProgressions, isFetching: isFetchingProgressions } =
useProgressions({
namespace: namespace ?? "",
name: PROGRESS,
limit: LIMIT,
parse: Progress.parse,
project: project ?? "",
parser: Progress.parse,
});

// Compute achievements and players
useEffect(() => {
if (
isFetchingTrophiess ||
isFetchingProgresses ||
!trophies.length ||
isFetchingTrophies ||
isFetchingProgressions ||
!rawTrophies.length ||
!currentAddress
)
return;

// Compute counters
const counters: Counters = {};
progresses.forEach(({ player, task, count, timestamp }) => {
counters[player] = counters[player] || {};
counters[player][task] = counters[player][task] || [];
counters[player][task].push({ count, timestamp });
// Merge trophies
const trophies: { [id: string]: Trophy } = {};
rawTrophies.forEach((trophy) => {
if (Object.keys(trophies).includes(trophy.id)) {
trophy.tasks.forEach((task) => {
if (!trophies[trophy.id].tasks.find((t) => t.id === task.id)) {
trophies[trophy.id].tasks.push(task);
}
});
} else {
trophies[trophy.id] = trophy;
}
});

// Compute players and achievement stats
const dedupedTrophies = trophies.filter(
(trophy, index) =>
trophies.findIndex((t) => t.id === trophy.id) === index,
);
const data: {
[playerId: string]: {
[achievementId: string]: {
[taskId: string]: {
completion: boolean;
timestamp: number;
count: number;
};
};
};
} = {};
rawProgressions.forEach((progress: Progress) => {
const { achievementId, playerId, taskId, taskTotal, total, timestamp } =
progress;

// Compute player
const detaultTasks: { [taskId: string]: boolean } = {};
trophies[achievementId].tasks.forEach((task: Task) => {
detaultTasks[task.id] = false;
});
data[playerId] = data[playerId] || {};
data[playerId][achievementId] =
data[playerId][achievementId] || detaultTasks;
data[playerId][achievementId][taskId] = {
completion: total >= taskTotal,
timestamp,
count: total,
};
});

const stats: Stats = {};
const players: Player[] = Object.keys(counters)
.map((playerAddress) => {
let timestamp = 0;
const completeds: string[] = [];
const earnings = dedupedTrophies.reduce(
(total: number, trophy: Trophy) => {
// Compute at which timestamp the latest achievement was completed
let completed = true;
trophy.tasks.forEach((task) => {
let count = 0;
let completion = false;
counters[playerAddress]?.[task.id]
?.sort((a, b) => a.timestamp - b.timestamp)
.forEach(
({
count: c,
timestamp: t,
}: {
count: number;
timestamp: number;
}) => {
count += c;
if (!completion && count >= task.total) {
timestamp = t > timestamp ? t : timestamp;
completion = true;
}
},
);
completed = completed && completion;
});
// Add trophy to list if completed
if (completed) completeds.push(trophy.id);
// Update stats
stats[trophy.id] = stats[trophy.id] || 0;
stats[trophy.id] += completed ? 1 : 0;
return completed ? total + trophy.earning : total;
},
0,
const players: Player[] = [];
Object.keys(data).forEach((playerId) => {
const player = data[playerId];
const completeds: string[] = [];
let timestamp = 0;
const earnings = Object.keys(player).reduce((acc, achievementId) => {
const completion = Object.values(player[achievementId]).every(
(task) => task.completion,
);
return {
address: playerAddress,
earnings,
timestamp,
completeds,
};
})
.sort((a, b) => a.timestamp - b.timestamp) // Oldest to newest
.sort((a, b) => b.earnings - a.earnings); // Highest to lowest
setPlayers(players);
timestamp = Math.max(
...Object.values(player[achievementId]).map((task) => task.timestamp),
);
if (completion) {
completeds.push(achievementId);
stats[achievementId] = stats[achievementId] || 0;
stats[achievementId] += 1;
}
return acc + (completion ? trophies[achievementId].earning : 0);
}, 0);
players.push({
address: playerId,
earnings,
timestamp: timestamp,
completeds,
});
});

setPlayers(
Object.values(players)
.sort((a, b) => a.timestamp - b.timestamp) // Oldest to newest
.sort((a, b) => b.earnings - a.earnings), // Highest to lowest
);

// Compute achievements
const achievements: Item[] = dedupedTrophies
.map((trophy) => {
// Compute at which timestamp the achievement was completed
let timestamp = 0;
let completed = true;
const tasks: Task[] = [];
trophy.tasks.forEach((task) => {
let count = 0;
let completion = false;
counters[currentAddress]?.[task.id]
?.sort((a, b) => a.timestamp - b.timestamp)
.forEach(
({
count: c,
timestamp: t,
}: {
count: number;
timestamp: number;
}) => {
count += c;
if (!completion && count >= task.total) {
timestamp = t;
completion = true;
}
},
);
tasks.push({
id: task.id,
count,
total: task.total,
description: task.description,
});
completed = completed && completion;
});
const achievements: Item[] = Object.values(trophies).map(
(trophy: Trophy) => {
const achievement = data[currentAddress]?.[trophy.id] || {};
const completion =
Object.values(achievement).length > 0 &&
Object.values(achievement).every((task) => task.completion);
const timestamp = Math.max(
...Object.values(achievement).map((task) => task.timestamp),
);
// Compute percentage of players who completed the achievement
const percentage = (
players.length ? (100 * stats[trophy.id]) / players.length : 0
players.length ? (100 * (stats[trophy.id] ?? 0)) / players.length : 0
).toFixed(0);
const tasks: ItemTask[] = trophy.tasks.map((task) => {
return {
...task,
count: achievement[task.id]?.count || 0,
};
});
return {
...trophy,
completed,
id: trophy.id,
hidden: trophy.hidden,
index: trophy.index,
earning: trophy.earning,
group: trophy.group,
icon: trophy.icon,
title: trophy.title,
description: trophy.description,
completed: completion,
percentage,
timestamp,
pinned: false,
tasks,
};
})
.sort((a, b) => a.index - b.index) // Lowest index to greatest
.sort((a, b) => (a.id > b.id ? 1 : -1)) // A to Z
.sort((a, b) => (b.hidden ? -1 : 1) - (a.hidden ? -1 : 1)) // Visible to hidden
.sort((a, b) => b.timestamp - a.timestamp) // Newest to oldest
.sort((a, b) => (b.completed ? 1 : 0) - (a.completed ? 1 : 0)); // Completed to uncompleted
setAchievements(achievements);
},
);
setAchievements(
achievements
.sort((a, b) => a.index - b.index) // Lowest index to greatest
.sort((a, b) => (a.id > b.id ? 1 : -1)) // A to Z
.sort((a, b) => (b.hidden ? -1 : 1) - (a.hidden ? -1 : 1)) // Visible to hidden
.sort((a, b) => b.timestamp - a.timestamp) // Newest to oldest
.sort((a, b) => (b.completed ? 1 : 0) - (a.completed ? 1 : 0)), // Completed to uncompleted
);
// Update loading state
setIsLoading(false);
}, [
currentAddress,
trophies,
progresses,
isFetchingTrophiess,
isFetchingProgresses,
]);
}, [currentAddress, isFetchingTrophies, isFetchingProgressions]);

Check warning on line 206 in packages/profile/src/hooks/achievements.ts

View workflow job for this annotation

GitHub Actions / ts-lint

React Hook useEffect has missing dependencies: 'rawProgressions' and 'rawTrophies'. Either include them or remove the dependency array

return { achievements, players, isLoading };
}
Loading
Loading