Skip to content

Commit fc9dbf5

Browse files
committed
add badges to QuestItemClicked
1 parent 5a38e0a commit fc9dbf5

File tree

9 files changed

+136
-114
lines changed

9 files changed

+136
-114
lines changed

client/src/components/Leaderboard.tsx

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { useContext, useEffect, useState } from "react";
44
import { Loading } from "./Loading";
55

66
// context
7-
import { GlobalDispatchContext, GlobalStateContext } from "@context/GlobalContext";
8-
import { SET_VISITOR_INFO } from "@/context/types";
7+
import { GlobalStateContext } from "@context/GlobalContext";
98

109
// utils
1110
import { backendAPI } from "@utils/backendAPI";
11+
import { useSearchParams } from "react-router-dom";
1212

1313
type LeaderboardType = {
1414
name: string;
@@ -25,22 +25,18 @@ export const Leaderboard = ({ isKeyAsset }: { isKeyAsset: boolean }) => {
2525
const [isLoading, setIsLoading] = useState(true);
2626

2727
// context
28-
const dispatch = useContext(GlobalDispatchContext);
2928
const { questDetails } = useContext(GlobalStateContext);
3029
const { questItemImage } = questDetails || {};
3130

31+
const [searchParams] = useSearchParams();
32+
const profileId = searchParams.get("profileId");
33+
3234
useEffect(() => {
3335
backendAPI
3436
.get(`/leaderboard?isKeyAsset=${isKeyAsset}`)
3537
.then((response) => {
36-
const { leaderboard, visitor, visitorInventory } = response.data;
37-
38-
dispatch!({
39-
type: SET_VISITOR_INFO,
40-
payload: { visitor, visitorInventory },
41-
});
42-
43-
const index = leaderboard.findIndex((item: { profileId: string }) => item.profileId === visitor.profileId);
38+
const { leaderboard } = response.data;
39+
const index = leaderboard.findIndex((item: { profileId: string }) => item.profileId === profileId);
4440
setMyData(leaderboard[index]);
4541
setCurrentPosition(index + 1);
4642
setTotal(leaderboard.length);
@@ -53,8 +49,6 @@ export const Leaderboard = ({ isKeyAsset }: { isKeyAsset: boolean }) => {
5349

5450
if (isLoading) return <Loading />;
5551

56-
if (visibleData.length === 0) return <p>No quest items have been found yet. Search the world and be the first!</p>;
57-
5852
return (
5953
<div className="container">
6054
{currentPosition && currentPosition > 0 ? (

client/src/components/PageContainer.tsx

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,69 @@ import { AdminIconButton, Loading, Admin } from "@/components";
66
// context
77
import { GlobalStateContext } from "@context/GlobalContext";
88

9-
export const PageContainer = ({ children, isLoading }: { children: ReactNode; isLoading: boolean }) => {
10-
const { error, questDetails, visitor } = useContext(GlobalStateContext);
9+
export const PageContainer = ({
10+
children,
11+
isLoading,
12+
showAdminIcon,
13+
}: {
14+
children: ReactNode;
15+
isLoading: boolean;
16+
showAdminIcon: boolean;
17+
}) => {
18+
const { error, questDetails, visitor, badges, visitorInventory } = useContext(GlobalStateContext);
1119
const { questItemImage } = questDetails || {};
1220
const { isAdmin } = visitor || {};
1321

22+
const [activeTab, setActiveTab] = useState("leaderboard");
1423
const [showSettings, setShowSettings] = useState(false);
1524

1625
if (isLoading) return <Loading />;
1726

1827
return (
1928
<div className="p-4 mb-28">
20-
{isAdmin && (
29+
{isAdmin && showAdminIcon && (
2130
<AdminIconButton setShowSettings={() => setShowSettings(!showSettings)} showSettings={showSettings} />
2231
)}
2332
{questItemImage ? <img alt="Find me" className="mx-auto" src={questItemImage} /> : <div />}
2433
<div className="flex flex-col mb-4 mt-2">
2534
<h1 className="h2 text-center">Quest</h1>
2635
</div>
27-
{showSettings ? <Admin /> : children}
36+
{showSettings ? (
37+
<Admin />
38+
) : (
39+
<>
40+
<div className="tab-container mb-4">
41+
<button
42+
className={activeTab === "leaderboard" ? "btn" : "btn btn-text"}
43+
onClick={() => setActiveTab("leaderboard")}
44+
>
45+
Leaderboard
46+
</button>
47+
<button className={activeTab === "badges" ? "btn" : "btn btn-text"} onClick={() => setActiveTab("badges")}>
48+
Badges
49+
</button>
50+
</div>
51+
52+
{activeTab === "leaderboard" ? (
53+
children
54+
) : (
55+
<div className="grid grid-cols-3 gap-6 pt-4">
56+
{badges &&
57+
Object.values(badges).map((badge) => {
58+
const hasBadge = visitorInventory && Object.keys(visitorInventory).includes(badge.name);
59+
const style = { width: "90px", filter: "none" };
60+
if (!hasBadge) style.filter = "grayscale(1)";
61+
return (
62+
<div className="tooltip" key={badge.id}>
63+
<span className="tooltip-content">{badge.name}</span>
64+
<img src={badge.icon} alt={badge.name} style={style} />
65+
</div>
66+
);
67+
})}
68+
</div>
69+
)}
70+
</>
71+
)}
2872
{error && <p className="p3 pt-10 text-center text-error">{error}</p>}
2973
</div>
3074
);

client/src/pages/Home.tsx

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,18 @@ import { ErrorType, SET_QUEST_DETAILS, SET_VISITOR_INFO } from "@/context/types"
1111
import { backendAPI, setErrorMessage } from "@/utils";
1212

1313
export const Home = () => {
14-
const [activeTab, setActiveTab] = useState("leaderboard");
1514
const [isLoading, setIsLoading] = useState(true);
1615

1716
// context
1817
const dispatch = useContext(GlobalDispatchContext);
19-
const { hasInteractiveParams, badges, visitorInventory } = useContext(GlobalStateContext);
18+
const { hasInteractiveParams } = useContext(GlobalStateContext);
2019

2120
useEffect(() => {
2221
if (hasInteractiveParams) {
2322
backendAPI
2423
.get("/quest")
2524
.then((response) => {
26-
const { questDetails, visitor, visitorInventory, badges } = response.data;
25+
const { questDetails, visitor, badges, visitorInventory } = response.data;
2726
dispatch!({
2827
type: SET_QUEST_DETAILS,
2928
payload: { questDetails, badges },
@@ -39,39 +38,8 @@ export const Home = () => {
3938
}, [hasInteractiveParams]);
4039

4140
return (
42-
<PageContainer isLoading={isLoading}>
43-
<>
44-
<div className="tab-container mb-4">
45-
<button
46-
className={activeTab === "leaderboard" ? "btn" : "btn btn-text"}
47-
onClick={() => setActiveTab("leaderboard")}
48-
>
49-
Leaderboard
50-
</button>
51-
<button className={activeTab === "badges" ? "btn" : "btn btn-text"} onClick={() => setActiveTab("badges")}>
52-
Badges
53-
</button>
54-
</div>
55-
56-
{activeTab === "leaderboard" ? (
57-
<Leaderboard isKeyAsset={true} />
58-
) : (
59-
<div className="grid grid-cols-3 gap-6 pt-4">
60-
{badges &&
61-
Object.values(badges).map((badge) => {
62-
const hasBadge = visitorInventory && Object.keys(visitorInventory).includes(badge.name);
63-
const style = { width: "90px", filter: "none" };
64-
if (!hasBadge) style.filter = "grayscale(1)";
65-
return (
66-
<div className="tooltip" key={badge.id}>
67-
<span className="tooltip-content">{badge.name}</span>
68-
<img src={badge.icon} alt={badge.name} style={style} />
69-
</div>
70-
);
71-
})}
72-
</div>
73-
)}
74-
</>
41+
<PageContainer isLoading={isLoading} showAdminIcon={true}>
42+
<Leaderboard isKeyAsset={true} />
7543
</PageContainer>
7644
);
7745
};

client/src/pages/QuestItemClicked.tsx

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { useContext, useEffect, useState } from "react";
22

33
// components
4-
import { Leaderboard, Loading } from "@/components";
4+
import { Leaderboard, Loading, PageContainer } from "@/components";
55

66
// context
77
import { GlobalDispatchContext, GlobalStateContext } from "@context/GlobalContext";
8-
import { SET_QUEST_DETAILS } from "@/context/types";
8+
import { SET_QUEST_DETAILS, SET_VISITOR_INFO } from "@/context/types";
99

1010
// utils
1111
import { backendAPI } from "@utils/backendAPI";
@@ -17,18 +17,29 @@ export const QuestItemClicked = () => {
1717

1818
// context
1919
const dispatch = useContext(GlobalDispatchContext);
20-
const { questDetails, hasInteractiveParams } = useContext(GlobalStateContext);
21-
const { questItemImage } = questDetails || {};
20+
const { hasInteractiveParams } = useContext(GlobalStateContext);
2221

2322
useEffect(() => {
2423
if (hasInteractiveParams) {
2524
backendAPI
2625
.post("/quest-item-clicked")
2726
.then((response) => {
28-
const { addedClick, numberAllowedToCollect, totalCollectedToday, questDetails } = response.data;
27+
const {
28+
addedClick,
29+
numberAllowedToCollect,
30+
totalCollectedToday,
31+
questDetails,
32+
visitor,
33+
visitorInventory,
34+
badges,
35+
} = response.data;
2936
dispatch!({
3037
type: SET_QUEST_DETAILS,
31-
payload: { questDetails },
38+
payload: { questDetails, badges },
39+
});
40+
dispatch!({
41+
type: SET_VISITOR_INFO,
42+
payload: { visitor, visitorInventory },
3243
});
3344
if (addedClick) {
3445
setCollectedText(`${totalCollectedToday}/${numberAllowedToCollect} collected today`);
@@ -50,25 +61,13 @@ export const QuestItemClicked = () => {
5061
if (isLoading) return <Loading />;
5162

5263
return (
53-
<div className="container p-6 items-center justify-start">
54-
{questItemImage ? <img alt="Find me" className="mx-auto" src={questItemImage} /> : <div />}
55-
<div className="flex flex-col mb-6 mt-4">
56-
<h1 className="h2 text-center">Quest</h1>
57-
</div>
58-
<div className="container py-6 items-center justify-start">
59-
{message && (
60-
<div className="flex flex-col p-1">
61-
<p>{message}</p>
62-
</div>
63-
)}
64-
{collectedText && (
65-
<div className="flex flex-col p-1">
66-
<p>{collectedText}</p>
67-
</div>
68-
)}
64+
<PageContainer isLoading={isLoading} showAdminIcon={false}>
65+
<div className="grid gap-4">
66+
{message && <p>{message}</p>}
67+
{collectedText && <p>{collectedText}</p>}
68+
<Leaderboard isKeyAsset={false} />
6969
</div>
70-
<Leaderboard isKeyAsset={false} />
71-
</div>
70+
</PageContainer>
7271
);
7372
};
7473

server/controllers/droppedAssets/handleGetQuestDetails.ts

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { Request, Response } from "express";
2-
import {
3-
errorHandler,
4-
getCachedInventoryItems,
5-
getCredentials,
6-
getVisitor,
7-
getWorldDetails,
8-
} from "../../utils/index.js";
2+
import { errorHandler, getBadges, getCredentials, getVisitor, getWorldDetails } from "../../utils/index.js";
93

104
export const handleGetQuestDetails = async (req: Request, res: Response) => {
115
try {
@@ -21,28 +15,7 @@ export const handleGetQuestDetails = async (req: Request, res: Response) => {
2115

2216
const { visitor, visitorInventory } = getVisitorResponse;
2317

24-
const inventoryItems = await getCachedInventoryItems({ credentials });
25-
26-
const badges: {
27-
[name: string]: {
28-
id: string;
29-
name: string;
30-
icon: string;
31-
description: string;
32-
};
33-
} = {};
34-
35-
for (const item of inventoryItems) {
36-
const { id, name, image_path, description, type, status } = item;
37-
if (name && type === "BADGE" && status === "ACTIVE") {
38-
badges[name] = {
39-
id: id,
40-
name,
41-
icon: image_path || "",
42-
description: description || "",
43-
};
44-
}
45-
}
18+
const badges = await getBadges(credentials);
4619

4720
return res.json({
4821
questDetails: dataObject,

server/controllers/droppedAssets/handleQuestItemClicked.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
getVisitor,
1313
getWorldDetails,
1414
awardBadge,
15+
getBadges,
1516
} from "../../utils/index.js";
1617
import { AxiosError } from "axios";
1718

@@ -46,6 +47,8 @@ export const handleQuestItemClicked = async (req: Request, res: Response) => {
4647

4748
let { currentStreak, lastCollectedDate, longestStreak, totalCollected, totalCollectedToday } = visitorProgress;
4849

50+
const badges = await getBadges(credentials);
51+
4952
// Award First Find badge if visitor collected their first quest item
5053
if (totalCollected === 0) {
5154
promises.push(
@@ -65,7 +68,13 @@ export const handleQuestItemClicked = async (req: Request, res: Response) => {
6568
if (!hasCollectedToday) analytics.push({ analyticName: "starts", profileId, urlSlug, uniqueKey: profileId });
6669

6770
if (hasCollectedToday && totalCollectedToday >= numberAllowedToCollect) {
68-
return res.json({ addedClick: false, numberAllowedToCollect, questDetails: worldDataObject });
71+
return res.json({
72+
addedClick: false,
73+
numberAllowedToCollect,
74+
questDetails: worldDataObject,
75+
badges,
76+
visitorInventory,
77+
});
6978
} else {
7079
analytics.push({ analyticName: "itemsCollected" });
7180

@@ -261,11 +270,18 @@ export const handleQuestItemClicked = async (req: Request, res: Response) => {
261270
if (result.status === "rejected") console.error(result.reason);
262271
});
263272

273+
const getVisitorResponse = await getVisitor(credentials, keyAssetId);
274+
if (getVisitorResponse instanceof Error) throw getVisitorResponse;
275+
276+
const { visitorInventory: updatedInventory } = getVisitorResponse;
277+
264278
return res.json({
265279
addedClick: true,
266280
numberAllowedToCollect,
267281
totalCollectedToday,
268282
questDetails: worldDataObject,
283+
badges,
284+
visitorInventory: updatedInventory,
269285
});
270286
}
271287
} catch (error) {

server/controllers/handleGetLeaderboard.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,8 @@ export const handleGetLeaderboard = async (req: Request, res: Response) => {
4141

4242
formattedLeaderboard.sort((a, b) => b.collected - a.collected);
4343

44-
const getVisitorResponse = await getVisitor(credentials, keyAssetId);
45-
if (getVisitorResponse instanceof Error) throw getVisitorResponse;
46-
47-
const { visitor, visitorInventory } = getVisitorResponse;
48-
4944
return res.json({
5045
leaderboard: formattedLeaderboard,
51-
visitor: { isAdmin: visitor.isAdmin, profileId: credentials.profileId },
52-
visitorInventory,
5346
});
5447
} catch (error) {
5548
return errorHandler({

0 commit comments

Comments
 (0)