Skip to content

Commit 8843e8c

Browse files
authored
Merge pull request #7 from metaversecloud-com/dev
Non-admin functionality
2 parents 7301b46 + 85f3a81 commit 8843e8c

File tree

10 files changed

+133
-27
lines changed

10 files changed

+133
-27
lines changed

.github/workflows/aws_dev_release.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ jobs:
6262
- name: Login to Amazon ECR
6363
id: login-ecr
6464
uses: aws-actions/amazon-ecr-login@v2
65-
with:
66-
mask-password: 'false'
6765

6866
- name: Image Metadata
6967
id: metadata

.github/workflows/aws_prod_release.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ jobs:
6666
- name: Login to Amazon ECR
6767
id: login-ecr
6868
uses: aws-actions/amazon-ecr-login@v2
69-
with:
70-
mask-password: 'false'
7169

7270
- name: Set up Docker Buildx
7371
uses: docker/setup-buildx-action@v3

client/src/components/PageFooter.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export const PageFooter = ({ children }: { children: ReactNode }) => {
66
style={{
77
position: "fixed",
88
bottom: 0,
9-
width: "90%",
10-
padding: 20,
9+
padding: "10px 0 20px",
10+
minWidth: "90%",
1111
}}
1212
>
1313
{children}

client/src/pages/Home.tsx

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,25 @@ const Home = () => {
1717
const [isLoading, setIsLoading] = useState(true);
1818
const [areButtonsDisabled, setAreButtonsDisabled] = useState(false);
1919
const [isAdmin, setIsAdmin] = useState(false);
20+
const [canSwapScenes, setCanSwapScenes] = useState<boolean>(false);
2021
const [scenes, setScenes] = useState<{ id: string; name: string; description: string; previewImgUrl: string }[]>([]);
2122
const [selectedSceneId, setSelectedSceneId] = useState("");
22-
const [title, setTitle] = useState("Scene Swapper");
23-
const [description, setDescription] = useState();
24-
const [showConfirmationModal, setShowConfirmationModal] = useState(false);
23+
const [title, setTitle] = useState<string>("Scene Swapper");
24+
const [description, setDescription] = useState<string>("");
25+
const [allowNonAdmins, setAllowNonAdmins] = useState<boolean>(false);
26+
const [message, setMessage] = useState<string>("");
27+
const [showConfirmationModal, setShowConfirmationModal] = useState<boolean>(false);
2528

2629
useEffect(() => {
2730
if (hasSetupBackend) {
2831
backendAPI
2932
.get("/game-state")
3033
.then((response) => {
31-
const { isAdmin, scenes, selectedSceneId, title, description } = response.data;
34+
const { allowNonAdmins, isAdmin, lastSwappedDate, scenes, selectedSceneId, title, description } =
35+
response.data;
36+
updateLastSwappedDate(lastSwappedDate);
3237
setIsAdmin(isAdmin);
38+
setAllowNonAdmins(allowNonAdmins);
3339
setScenes(scenes);
3440
setSelectedSceneId(selectedSceneId);
3541
if (title) setTitle(title);
@@ -46,13 +52,42 @@ const Home = () => {
4652
setAreButtonsDisabled(true);
4753
backendAPI
4854
.post("/replace-scene", { selectedSceneId })
49-
.then(() => {})
55+
.then(() => {
56+
updateLastSwappedDate(new Date());
57+
})
5058
.catch((error) => setErrorMessage(dispatch, error))
5159
.finally(() => {
5260
setAreButtonsDisabled(false);
5361
});
5462
};
5563

64+
const updateAllowNonAdmins = (value: boolean) => {
65+
setAllowNonAdmins(value);
66+
backendAPI
67+
.post("/allow-non-admins", { allowNonAdmins: value })
68+
.then(() => {})
69+
.catch((error) => setErrorMessage(dispatch, error));
70+
};
71+
72+
const updateLastSwappedDate = (lastSwappedDate?: Date) => {
73+
if (!isAdmin && lastSwappedDate) {
74+
const lastSwappedDateObj = new Date(lastSwappedDate);
75+
const currentDate = new Date();
76+
const timeDifference = currentDate.getTime() - lastSwappedDateObj.getTime();
77+
const minutesDifference = Math.floor(timeDifference / (1000 * 60));
78+
if (minutesDifference >= 30) {
79+
setCanSwapScenes(true);
80+
setMessage("");
81+
} else {
82+
setCanSwapScenes(false);
83+
setMessage("Hang tight! Scenes can only be updated every 30 minutes. Please try again soon.");
84+
// setMessage(`Scene recently swapped. Please wait ${30 - minutesDifference} minutes before swapping again.`);
85+
}
86+
} else {
87+
setCanSwapScenes(true);
88+
}
89+
};
90+
5691
if (!hasSetupBackend) return <div />;
5792

5893
return (
@@ -63,16 +98,16 @@ const Home = () => {
6398

6499
<PageContainer isLoading={isLoading}>
65100
<>
66-
{isAdmin ? (
101+
{isAdmin || allowNonAdmins ? (
67102
<>
68103
{title && <h1 className="h2">{title}</h1>}
69104
{description && <p>{description}</p>}
70-
<div className="mt-4">
105+
<div className="mt-4 mb-10">
71106
{scenes?.map((scene) => (
72107
<div key={scene.id} className="mb-2" onClick={() => setSelectedSceneId(scene.id)}>
73108
<div className={`card small ${selectedSceneId === scene.id ? "success" : ""}`}>
74-
<div className="card-image" style={{ height: "auto" }}>
75-
<img src={scene.previewImgUrl} alt={scene.name} />
109+
<div className="card-image">
110+
<img src={scene.previewImgUrl} alt={scene.name} style={{ maxHeight: "100%" }} />
76111
</div>
77112
<div className="card-details">
78113
<h4 className="card-title h4">{scene.name}</h4>
@@ -81,19 +116,42 @@ const Home = () => {
81116
</div>
82117
</div>
83118
))}
119+
{message && <p className="text-danger py-4">{message}</p>}
84120
</div>
85121

86122
<PageFooter>
87-
<button className="btn mb-2" disabled={areButtonsDisabled || !selectedSceneId} onClick={replaceScene}>
88-
Update Scene
89-
</button>
123+
{isAdmin && (
124+
<label className="label text-center mb-4">
125+
<input
126+
checked={allowNonAdmins}
127+
className="input-checkbox mr-2"
128+
type="checkbox"
129+
onChange={(event) => updateAllowNonAdmins(event.target.checked)}
130+
/>
131+
Allow all users to swap scenes?
132+
</label>
133+
)}
90134
<button
91-
className="btn btn-danger"
92-
disabled={areButtonsDisabled}
93-
onClick={() => setShowConfirmationModal(true)}
135+
className="btn mb-2"
136+
disabled={areButtonsDisabled || !selectedSceneId || !canSwapScenes}
137+
onClick={replaceScene}
94138
>
95-
Clear Current Scene
139+
<div className={!canSwapScenes ? "tooltip" : ""} style={{ width: "100%" }}>
140+
<div className="tooltip-content" style={{ width: "100%", top: "-60px" }}>
141+
{message}
142+
</div>
143+
Update Scene
144+
</div>
96145
</button>
146+
{isAdmin && (
147+
<button
148+
className="btn btn-danger"
149+
disabled={areButtonsDisabled}
150+
onClick={() => setShowConfirmationModal(true)}
151+
>
152+
Clear Current Scene
153+
</button>
154+
)}
97155
</PageFooter>
98156
</>
99157
) : (

server/controllers/handleGetGameState.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,19 @@ export const handleGetGameState = async (req: Request, res: Response) => {
1212
const visitor: VisitorInterface = await Visitor.get(visitorId, urlSlug, { credentials });
1313
const { isAdmin } = visitor;
1414

15-
if (!isAdmin) return res.json({ isAdmin, success: true });
16-
1715
const droppedAsset = await DroppedAsset.get(assetId, urlSlug, { credentials });
18-
const { currentSceneIndex, droppableSceneIds, title, description } = droppedAsset.dataObject as DataObjectType;
16+
if (!droppedAsset.dataObject || Object.keys(droppedAsset.dataObject).length === 0) {
17+
throw "Dropped asset data object not found.";
18+
}
19+
20+
const {
21+
allowNonAdmins = false,
22+
currentSceneIndex,
23+
droppableSceneIds,
24+
lastSwappedDate,
25+
title,
26+
description,
27+
} = droppedAsset.dataObject as DataObjectType;
1928

2029
const results = await Promise.allSettled(droppableSceneIds.map((sceneId) => Scene.get(sceneId, { credentials })));
2130

@@ -37,7 +46,9 @@ export const handleGetGameState = async (req: Request, res: Response) => {
3746
});
3847

3948
return res.json({
40-
isAdmin: true,
49+
allowNonAdmins,
50+
isAdmin,
51+
lastSwappedDate,
4152
scenes,
4253
selectedSceneId: droppableSceneIds[currentSceneIndex],
4354
title,

server/controllers/handleReplaceScene.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export const handleReplaceScene = async (req: Request, res: Response) => {
1010

1111
await swapScene(credentials, droppedAsset, selectedSceneId);
1212

13+
await droppedAsset.updateDataObject({ lastSwappedDate: new Date() });
14+
1315
return res.json({ success: true });
1416
} catch (error) {
1517
return errorHandler({
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Request, Response } from "express";
2+
import { DroppedAsset } from "../topiaInit.js";
3+
import { errorHandler, getCredentials } from "../utils/index.js";
4+
5+
export const handleUpdateAllowNonAdmins = async (req: Request, res: Response) => {
6+
try {
7+
const { allowNonAdmins } = req.body;
8+
const credentials = getCredentials(req.query);
9+
const { assetId, urlSlug } = credentials;
10+
11+
const droppedAsset = await DroppedAsset.get(assetId, urlSlug, { credentials });
12+
if (!droppedAsset.dataObject || Object.keys(droppedAsset.dataObject).length === 0)
13+
throw "Dropped asset data object not found.";
14+
15+
await droppedAsset.updateDataObject({ allowNonAdmins });
16+
17+
return res.json({
18+
success: true,
19+
});
20+
} catch (error) {
21+
return errorHandler({
22+
error,
23+
functionName: "handleUpdateAllowNonAdmins",
24+
message: "Error updating dropped asset data object.",
25+
req,
26+
res,
27+
});
28+
}
29+
};

server/controllers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from "./handleGetGameState.js";
22
export * from "./handleReplaceScene.js";
33
export * from "./handleRemoveScene.js";
44
export * from "./handleSwapScene.js";
5+
export * from "./handleUpdateAllowNonAdmins.js";

server/routes.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import express from "express";
22
import { getVersion } from "./utils/getVersion.js";
3-
import { handleGetGameState, handleRemoveScene, handleReplaceScene, handleSwapScene } from "./controllers/index.js";
3+
import {
4+
handleGetGameState,
5+
handleRemoveScene,
6+
handleReplaceScene,
7+
handleSwapScene,
8+
handleUpdateAllowNonAdmins,
9+
} from "./controllers/index.js";
410

511
const router = express.Router();
612

@@ -26,6 +32,7 @@ router.get("/system/health", (req, res) => {
2632
router.get("/game-state", handleGetGameState);
2733
router.post("/replace-scene", handleReplaceScene);
2834
router.post("/remove-scene", handleRemoveScene);
35+
router.post("/allow-non-admins", handleUpdateAllowNonAdmins);
2936

3037
// webhooks
3138
router.post("/swap", handleSwapScene);

server/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ export interface Credentials {
1313
}
1414

1515
export type DataObjectType = {
16+
allowNonAdmins: boolean;
1617
currentSceneIndex: number;
1718
droppableSceneIds: string[];
19+
lastSwappedDate: Date;
1820
persistentDroppedAssets: string[];
1921
positionOffset: {
2022
x: number;

0 commit comments

Comments
 (0)