Skip to content

Commit ea53724

Browse files
committed
refactor data objects
1 parent 5b71460 commit ea53724

39 files changed

+579
-456
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,21 @@ WEB_IMAGE_ASSET_ID=webImageAsset
6565

6666
_We use data objects to store information about each implementation of the app per world._
6767

68-
- Dropped Assets: the data object attached to the dropped assets can optionally include `questItemImage` which will be used instead of the default image when someone first interacts with the instance of Quest
68+
- Dropped Assets: the data object attached to the dropped assets can optionally include `questItemImage` which will be used instead of the default image when someone first interacts with the instance of Quest. Additionally it'll capture user progress to be displayed in the leaderboard
69+
70+
- leaderboard (`leaderboard.${profileId}`)
71+
6972
- World: the data object attached to the world will store information for every instance of the app in a given world by sceneDropId and will persist if a specific instance is removed manually from world instead of through the Admin screen.
7073

71-
- itemsCollectedByUser (`scenes.${sceneDropId}.itemsCollectedByUser.${profileId}`)
72-
- totalItemsCollected (`scenes.${sceneDropId}.totalItemsCollected`)
7374
- questItems (`scenes.${sceneDropId}.questItems.${assetId}`)
7475

76+
- User: the data object attached to the user will store user's progress per instance of Quest across all worlds.
77+
- currentStreak
78+
- lastCollectedDate
79+
- longestStreak
80+
- totalCollected
81+
- totalCollectedToday
82+
7583
### Helpful links
7684

7785
- [SDK Developer docs](https://metaversecloud-com.github.io/mc-sdk-js/index.html)

client/src/App.tsx

Lines changed: 14 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
1-
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
1+
import { useContext, useEffect, useMemo, useState } from "react";
22
import { Route, Routes, useNavigate, useSearchParams } from "react-router-dom";
33

44
// pages
55
import Home from "@pages/Home";
66
import Error from "@pages/Error";
77
import QuestItemClicked from "@pages/QuestItemClicked";
88

9-
// components
10-
import { Loading } from "./components";
11-
129
// context
1310
import { GlobalDispatchContext } from "./context/GlobalContext";
14-
import { InteractiveParams, SET_INTERACTIVE_PARAMS, SET_QUEST_DETAILS, SET_VISITOR_INFO } from "./context/types";
11+
import { InteractiveParams, SET_HAS_INTERACTIVE_PARAMS } from "./context/types";
1512

1613
// utils
17-
import { backendAPI, setupBackendAPI } from "./utils/backendAPI";
14+
import { setupBackendAPI } from "./utils/backendAPI";
1815

1916
const App = () => {
2017
const navigate = useNavigate();
2118
const [searchParams] = useSearchParams();
2219
const [hasInitBackendAPI, setHasInitBackendAPI] = useState(false);
23-
const [isLoading, setIsLoading] = useState(true);
2420

2521
const dispatch = useContext(GlobalDispatchContext);
2622

@@ -40,99 +36,28 @@ const App = () => {
4036
};
4137
}, [searchParams]);
4238

43-
const setInteractiveParams = useCallback(
44-
({
45-
assetId,
46-
displayName,
47-
identityId,
48-
interactiveNonce,
49-
interactivePublicKey,
50-
profileId,
51-
sceneDropId,
52-
uniqueName,
53-
urlSlug,
54-
username,
55-
visitorId,
56-
}: InteractiveParams) => {
57-
const isInteractiveIframe = visitorId && interactiveNonce && interactivePublicKey && assetId;
39+
useEffect(() => {
40+
if (interactiveParams.assetId) {
5841
dispatch!({
59-
type: SET_INTERACTIVE_PARAMS,
60-
payload: {
61-
assetId,
62-
displayName,
63-
identityId,
64-
interactiveNonce,
65-
interactivePublicKey,
66-
isInteractiveIframe,
67-
profileId,
68-
sceneDropId,
69-
uniqueName,
70-
urlSlug,
71-
username,
72-
visitorId,
73-
},
42+
type: SET_HAS_INTERACTIVE_PARAMS,
43+
payload: { hasInteractiveParams: true },
7444
});
75-
},
76-
[dispatch],
77-
);
45+
}
46+
}, [interactiveParams]);
47+
48+
useEffect(() => {
49+
if (!hasInitBackendAPI) setupBackend();
50+
}, [hasInitBackendAPI, interactiveParams]);
7851

7952
const setupBackend = () => {
8053
setupBackendAPI(interactiveParams)
8154
.catch((error) => {
8255
console.error(error?.response?.data?.message);
8356
navigate("*");
8457
})
85-
.finally(() => setHasInitBackendAPI(true))
86-
};
87-
88-
const getVisitor = () => {
89-
backendAPI.get("/visitor")
90-
.then((result) => {
91-
dispatch!({
92-
type: SET_VISITOR_INFO,
93-
payload: result.data.visitor,
94-
});
95-
})
96-
.catch((error) => {
97-
console.error(error?.response?.data?.message);
98-
navigate("*");
99-
})
100-
.finally(() => setIsLoading(false))
101-
};
102-
103-
const getQuestDetails = () => {
104-
backendAPI.get("/quest")
105-
.then((result) => {
106-
dispatch!({
107-
type: SET_QUEST_DETAILS,
108-
payload: result.data,
109-
});
110-
})
111-
.catch((error) => {
112-
console.error(error?.response?.data?.message);
113-
navigate("*");
114-
})
115-
.finally(() => setIsLoading(false))
58+
.finally(() => setHasInitBackendAPI(true));
11659
};
11760

118-
useEffect(() => {
119-
if (interactiveParams.assetId) {
120-
setInteractiveParams({
121-
...interactiveParams,
122-
});
123-
}
124-
}, [interactiveParams, setInteractiveParams]);
125-
126-
useEffect(() => {
127-
if (!hasInitBackendAPI) setupBackend();
128-
else {
129-
getVisitor();
130-
getQuestDetails();
131-
}
132-
}, [hasInitBackendAPI, interactiveParams]);
133-
134-
if (isLoading || !hasInitBackendAPI) return <Loading />;
135-
13661
return (
13762
<Routes>
13863
<Route element={<Home />} path="/" />

client/src/components/Admin.tsx

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ import { useContext, useEffect, useState } from "react";
44
import { AdminForm, Loading, PlacedItems } from "./index.js";
55

66
// utils
7-
import { backendAPI } from "@utils/backendAPI";
7+
import { backendAPI, setErrorMessage } from "@/utils";
88

99
// context
10-
import { GlobalStateContext } from "@context/GlobalContext";
10+
import { GlobalDispatchContext, GlobalStateContext } from "@context/GlobalContext";
11+
import { ErrorType } from "@/context/types.js";
1112

1213
export const Admin = () => {
1314
const [questItems, setQuestItems] = useState({});
1415
const [questItemCount, setQuestItemCount] = useState(0);
1516
const [areButtonsDisabled, setAreButtonsDisabled] = useState(false);
1617
const [isLoading, setIsLoading] = useState(true);
17-
const [errorMessage, setErrorMessage] = useState("");
1818

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

2324
useEffect(() => {
2425
if (hasInteractiveParams) {
@@ -27,42 +28,42 @@ export const Admin = () => {
2728
}, [hasInteractiveParams]);
2829

2930
const getQuestItems = () => {
30-
setErrorMessage("");
31-
backendAPI.get("/quest-items")
32-
.then((result) => {
33-
setQuestItems(result.data.droppedAssets)
34-
setQuestItemCount(Object.keys(result.data.droppedAssets).length)
31+
backendAPI
32+
.get("/quest-items")
33+
.then((response) => {
34+
setQuestItems(response.data.droppedAssets);
35+
setQuestItemCount(Object.keys(response.data.droppedAssets).length);
3536
})
36-
.catch((error) => setErrorMessage(error?.response?.data?.message || error.message))
37-
.finally(() => setIsLoading(false))
37+
.catch((error) => setErrorMessage(dispatch, error as ErrorType))
38+
.finally(() => setIsLoading(false));
3839
};
3940

4041
const dropItem = () => {
41-
setErrorMessage("");
4242
setAreButtonsDisabled(true);
43-
backendAPI.post("/drop-quest-item")
43+
backendAPI
44+
.post("/drop-quest-item")
4445
.then(() => getQuestItems())
45-
.catch((error) => setErrorMessage(error?.response?.data?.message || error.message))
46-
.finally(() => setAreButtonsDisabled(false))
46+
.catch((error) => setErrorMessage(dispatch, error as ErrorType))
47+
.finally(() => setAreButtonsDisabled(false));
4748
};
4849

4950
const removeAllQuestItems = () => {
50-
setErrorMessage("");
5151
setAreButtonsDisabled(true);
52-
backendAPI.post("/dropped-asset/remove-all-with-unique-name")
52+
backendAPI
53+
.post("/dropped-asset/remove-all-with-unique-name")
5354
.then(() => {
54-
setQuestItems({})
55-
setQuestItemCount(0)
55+
setQuestItems({});
56+
setQuestItemCount(0);
5657
})
57-
.catch((error) => setErrorMessage(error?.response?.data?.message || error.message))
58-
.finally(() => setAreButtonsDisabled(false))
58+
.catch((error) => setErrorMessage(dispatch, error as ErrorType))
59+
.finally(() => setAreButtonsDisabled(false));
5960
};
6061

6162
if (isLoading) return <Loading />;
6263

6364
return (
6465
<>
65-
<AdminForm setErrorMessage={setErrorMessage} />
66+
<AdminForm />
6667

6768
<div className="container py-6 items-center justify-center">
6869
<h5 className="h5 flex items-center justify-center pb-4">
@@ -92,9 +93,7 @@ export const Admin = () => {
9293
</div>
9394
</div>
9495

95-
{questItemCount > 0 && <PlacedItems questItems={questItems} getQuestItems={getQuestItems} setErrorMessage={setErrorMessage} />}
96-
97-
{errorMessage && <p className="p3 text-error">{`${errorMessage}`}</p>}
96+
{questItemCount > 0 && <PlacedItems questItems={questItems} getQuestItems={getQuestItems} />}
9897
</>
9998
);
100-
}
99+
};
Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,58 @@
11
import { useContext, useState } from "react";
22
import { useForm } from "react-hook-form";
33

4-
// utils
5-
import { backendAPI } from "@utils/backendAPI";
6-
74
// context
85
import { GlobalStateContext } from "@context/GlobalContext";
96
import { GlobalDispatchContext } from "@context/GlobalContext";
10-
import { SET_QUEST_DETAILS } from "@/context/types";
7+
import { ErrorType, SET_QUEST_DETAILS } from "@/context/types";
118

12-
export const AdminForm = ({ setErrorMessage }: { setErrorMessage: (value: string) => void }) => {
9+
// utils
10+
import { backendAPI, setErrorMessage } from "@/utils";
11+
12+
export const AdminForm = () => {
1313
const [areButtonsDisabled, setAreButtonsDisabled] = useState(false);
1414
const [showModal, setShowModal] = useState(false);
1515
const [isLoading, setIsLoading] = useState(false);
1616

1717
// context
1818
const { questDetails } = useContext(GlobalStateContext);
19-
const { numberAllowedToCollect, questItemImage } = questDetails
19+
const { numberAllowedToCollect, questItemImage } = questDetails || {};
2020
const dispatch = useContext(GlobalDispatchContext);
2121

22-
const {
23-
handleSubmit,
24-
register,
25-
} = useForm();
22+
const { handleSubmit, register } = useForm();
2623

2724
const onSubmit = handleSubmit((data) => {
28-
const { numberAllowedToCollect, questItemImage } = data
25+
const { numberAllowedToCollect, questItemImage } = data;
2926
setAreButtonsDisabled(true);
30-
setErrorMessage("");
31-
backendAPI.post("/admin-settings", { numberAllowedToCollect, questItemImage })
32-
.then(() => {
27+
backendAPI
28+
.post("/admin-settings", { numberAllowedToCollect, questItemImage })
29+
.then((response) => {
3330
dispatch!({
3431
type: SET_QUEST_DETAILS,
35-
payload: { ...questDetails, numberAllowedToCollect, questItemImage },
32+
payload: { questDetails: response.data.questDetails },
3633
});
3734
})
38-
.catch((error) => setErrorMessage(error?.response?.data?.message || error.message))
39-
.finally(() => setAreButtonsDisabled(false))
35+
.catch((error) => setErrorMessage(dispatch, error as ErrorType))
36+
.finally(() => setAreButtonsDisabled(false));
4037
});
4138

4239
const removeQuest = () => {
43-
setErrorMessage("");
4440
setAreButtonsDisabled(true);
4541
setIsLoading(true);
46-
backendAPI.delete("/quest")
47-
.catch((error) => setErrorMessage(error?.response?.data?.message || error.message))
42+
backendAPI
43+
.delete("/quest")
44+
.catch((error) => setErrorMessage(dispatch, error as ErrorType))
4845
.finally(() => {
4946
setAreButtonsDisabled(false);
5047
setIsLoading(false);
5148
setShowModal(false);
52-
})
49+
});
5350
};
5451

5552
return (
56-
<div className="container grid gap-2">
53+
<div className="grid gap-2">
5754
<hr />
58-
<form onSubmit={onSubmit}>
55+
<form className="grid gap-4" onSubmit={onSubmit}>
5956
<div className="mt-4">
6057
<label htmlFor="numberAllowedToCollect">Number Allowed To Collect Per Day:</label>
6158
<input
@@ -66,19 +63,18 @@ export const AdminForm = ({ setErrorMessage }: { setErrorMessage: (value: string
6663

6764
<div>
6865
<label htmlFor="questItemImage">Quest Item Image URL:</label>
69-
<input
70-
className="input"
71-
{...register("questItemImage", { required: true, value: questItemImage })}
72-
/>
66+
<input className="input" {...register("questItemImage", { required: true, value: questItemImage })} />
7367
<p className="p3">Update image for all Quest Items in world. This will not change the Key Asset image.</p>
7468
</div>
7569

76-
<button className="btn my-2" disabled={areButtonsDisabled} type="submit">
77-
Save
78-
</button>
79-
<button className="btn btn-danger" disabled={areButtonsDisabled} onClick={() => setShowModal(true)}>
80-
Remove Quest from world
81-
</button>
70+
<div>
71+
<button className="btn mb-2" disabled={areButtonsDisabled} type="submit">
72+
Save
73+
</button>
74+
<button className="btn btn-danger" disabled={areButtonsDisabled} onClick={() => setShowModal(true)}>
75+
Remove Quest from world
76+
</button>
77+
</div>
8278
</form>
8379
<hr className="mt-4 mb-2" />
8480

@@ -102,4 +98,4 @@ export const AdminForm = ({ setErrorMessage }: { setErrorMessage: (value: string
10298
)}
10399
</div>
104100
);
105-
}
101+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const AdminIconButton = ({
2+
setShowSettings,
3+
showSettings,
4+
}: {
5+
setShowSettings: (value: boolean) => void;
6+
showSettings: boolean;
7+
}) => {
8+
return (
9+
<button className="icon-with-rounded-border mb-2" onClick={() => setShowSettings(showSettings)}>
10+
<img src={`https://sdk-style.s3.amazonaws.com/icons/${showSettings ? "arrow" : "cog"}.svg`} />
11+
</button>
12+
);
13+
};
14+
15+
export default AdminIconButton;

0 commit comments

Comments
 (0)