Skip to content

Commit 4741622

Browse files
authored
Merge pull request #14 from hack4impact-calpoly/K1-32-Player-Dashboard-for-Website
K1 32 player dashboard for website
2 parents 679fb21 + a3303cf commit 4741622

File tree

12 files changed

+294
-8
lines changed

12 files changed

+294
-8
lines changed

public/Icons/FaPenguin.png

16 KB
Loading

public/Icons/FaSeedling.png

16.2 KB
Loading

public/Icons/FaShoppingBag.png

13.4 KB
Loading

public/Icons/FaStar.png

15.8 KB
Loading

src/app/api/gameData/[saveId]/route.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import connectDB from "@/database/db";
2+
import { auth } from "@clerk/nextjs/server";
23
import GameData from "@/database/gameDataSchema";
34
import { NextResponse, NextRequest } from "next/server";
45

56
export async function GET(_req: Request, { params }: { params: { saveId: string } }) {
7+
const { userId } = await auth.protect();
68
await connectDB();
79

8-
const data = await GameData.findOne({ saveId: params.saveId }).lean();
10+
const data = await GameData.findOne({ saveId: params.saveId, userId }).lean();
911
if (!data) {
1012
return NextResponse.json({ error: "Game data not found" }, { status: 404 });
1113
}

src/app/playerDashboard/page.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { auth } from "@clerk/nextjs/server";
2+
import { GameCard } from "@/components/GameCard";
3+
import { PlayerNavbar } from "@/components/PlayerNavbar";
4+
import { Box } from "@chakra-ui/react";
5+
import connectDB from "@/database/db";
6+
import GameData from "@/database/gameDataSchema";
7+
8+
//in the mongodb, saveId's are unique,
9+
//this is cool but this unique is enforced across all users (better if only for specific user)
10+
11+
//TEST IT OUT!
12+
//hi u can return <h1>{userId}</h1>; below, then paste ur userId into mongo and yay u can see progress
13+
14+
export default async function PlayerDashboard() {
15+
// VERY helpful info if your confused on auth https://clerk.com/docs/reference/nextjs/app-router/auth
16+
const { userId } = await auth.protect(); //redirects if signed out
17+
await connectDB();
18+
19+
//Ensure the saveId route is protected /api/gameData/:saveId
20+
const saves = await GameData.find(
21+
{ userId, gameId: { $in: ["statesOfMatterGame", "penguinRunGame"] } }, // gameId should be one of these two
22+
{ gameId: 1, saveId: 1, completedLevels: 1 }, //tell mongo to only return these fields from the document
23+
).lean();
24+
25+
const states = saves.find((s) => s.gameId === "statesOfMatterGame");
26+
const penguin = saves.find((s) => s.gameId === "penguinRunGame");
27+
28+
const statesCompleted = states?.completedLevels?.length ?? 0;
29+
const penguinCompleted = penguin?.completedLevels?.length ?? 0;
30+
31+
return (
32+
<main>
33+
<PlayerNavbar role="EXPLORER" name="Alex Cloudwalker" coins="1240" />
34+
<Box minH="100vh" bg="blue.50" display="flex" justifyContent="space-around" alignItems="center" p={8}>
35+
<GameCard game="statesOfMatterGame" completedLevels={statesCompleted} saveId={states?.saveId} />
36+
<GameCard game="penguinRunGame" completedLevels={penguinCompleted} saveId={penguin?.saveId} />
37+
</Box>
38+
</main>
39+
);
40+
}

src/app/shop/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Shop() {
2+
return <main></main>;
3+
}

src/components/ChakraButton.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Button, LinkBox, LinkOverlay } from "@chakra-ui/react";
2+
import Image from "next/image";
3+
import Link from "next/link";
4+
5+
type ButtonProps = {
6+
href: string;
7+
color: string; //ex: lowercase 'blue'
8+
label: string;
9+
width?: string;
10+
minW?: string;
11+
iconSrc?: string;
12+
};
13+
14+
export default function ChakraButton({ href, color, label, width, minW, iconSrc }: ButtonProps) {
15+
return (
16+
<LinkBox>
17+
<LinkOverlay as={Link} href={href}>
18+
<Button
19+
w="full"
20+
size="lg"
21+
width={width}
22+
minW={minW}
23+
py={8}
24+
position="relative"
25+
overflow="hidden" // keeps the blue bottom highlight within the button
26+
borderRadius="18px"
27+
bg={`${color}.500`}
28+
color="white"
29+
fontSize="lg"
30+
fontWeight="900"
31+
letterSpacing="0.05em"
32+
_hover={{ bg: `${color}.600` }}
33+
boxShadow="0 14px 28px rgba(15, 23, 42, 0.16)"
34+
_after={{
35+
content: '""',
36+
position: "absolute",
37+
left: 0,
38+
right: 0,
39+
bottom: 0,
40+
height: "8px",
41+
bg: "rgba(0,0,0,0.12)",
42+
pointerEvents: "none",
43+
}}
44+
>
45+
{iconSrc && (
46+
<Image
47+
src={iconSrc}
48+
alt={`${label} icon`}
49+
width={22}
50+
height={22}
51+
style={{ objectFit: "contain", display: "block" }}
52+
/>
53+
)}
54+
{label}
55+
</Button>
56+
</LinkOverlay>
57+
</LinkBox>
58+
);
59+
}

src/components/GameCard.tsx

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { Box, Circle, HStack, Progress, Text, VStack } from "@chakra-ui/react";
2+
import Image from "next/image";
3+
import ChakraButton from "./ChakraButton";
4+
5+
type GameCardProps = {
6+
game: "statesOfMatterGame" | "penguinRunGame"; // name of game folder (used for link)
7+
completedLevels: number;
8+
saveId?: string;
9+
};
10+
11+
// can be placed in a gameConfig.ts file for future customization
12+
export const GAME_CONFIG = {
13+
statesOfMatterGame: {
14+
title: "Matter Lab",
15+
subtitle: "Explore solids, liquids, and gases!",
16+
iconSrc: "/Icons/FaSeedling.png",
17+
defaultColor: "green",
18+
totalLevels: 5,
19+
},
20+
penguinRunGame: {
21+
title: "Penguin Run",
22+
subtitle: "Race across the icy glaciers!",
23+
iconSrc: "/Icons/FaPenguin.png",
24+
defaultColor: "orange",
25+
totalLevels: 20,
26+
},
27+
};
28+
29+
export function GameCard({ game, completedLevels, saveId }: GameCardProps) {
30+
const config = GAME_CONFIG[game];
31+
const levelProgress = Math.round((completedLevels / config.totalLevels) * 100);
32+
33+
//Here is where the saveId is being passed
34+
const href = saveId ? `/${game}?saveId=${saveId}` : `/${game}`;
35+
const label = saveId ? "CONTINUE MISSION" : "START MISSION";
36+
37+
return (
38+
// OUTER WRAPPER: This is so outer icons can extend outside the box
39+
<Box position="relative" w="full" maxW="420px">
40+
{/* Floating icon (NOT clipped) */}
41+
<Circle
42+
position="absolute"
43+
top="-76px" // adjust this to move icon up/down
44+
left="50%"
45+
transform="translateX(-50%)"
46+
size="120px"
47+
bg="white"
48+
border="2px solid"
49+
borderColor="gray.100"
50+
boxShadow="0 10px 22px rgba(15, 23, 42, 0.12)"
51+
zIndex={3}
52+
>
53+
<Image
54+
src={config.iconSrc}
55+
alt={`${config.title} icon`}
56+
width={64}
57+
height={64}
58+
style={{ objectFit: "contain" }}
59+
priority
60+
/>
61+
</Circle>
62+
63+
{/* INNER SURFACE: Overflow is hidden */}
64+
<Box
65+
position="relative"
66+
bg="white"
67+
borderRadius="28px"
68+
overflow="hidden"
69+
boxShadow="10px 40px 40px rgba(15, 23, 42, 0.12)"
70+
px={{ base: 6, md: 8 }}
71+
pt={{ base: 12, md: 14 }}
72+
pb={{ base: 6, md: 7 }}
73+
_before={{
74+
content: '""',
75+
position: "absolute",
76+
left: 0,
77+
right: 0,
78+
bottom: 0,
79+
height: "14px",
80+
bg: `${config.defaultColor}.200`,
81+
pointerEvents: "none",
82+
}}
83+
>
84+
{/* Title and description */}
85+
<VStack py={8} align="stretch" textAlign="center" position="relative" zIndex={1}>
86+
<VStack>
87+
<Text fontSize={{ base: "2xl", md: "3xl" }} fontWeight="800" color="gray.800">
88+
{config.title}
89+
</Text>
90+
<Text fontSize="sm" color="gray.500">
91+
{config.subtitle}
92+
</Text>
93+
</VStack>
94+
{/* Progress bar */}
95+
<Box pt={2} mb={4} p={4} bg="gray.100" borderRadius="28px">
96+
<HStack justify="space-between" mb={2}>
97+
<Text fontSize="xs" fontWeight="800" letterSpacing="0.06em" color={`${config.defaultColor}.400`}>
98+
LEVEL {completedLevels}/{config.totalLevels}
99+
</Text>
100+
<Text fontSize="xs" fontWeight="700" color="gray.500">
101+
{/* completedLevels here as well */}
102+
{levelProgress}%
103+
</Text>
104+
</HStack>
105+
106+
{/* pass the users completedLevels % * 100 for the value. Range: 0-100 */}
107+
<Progress.Root value={levelProgress} min={0} max={100}>
108+
<Progress.Track height="12px" borderRadius="50px" bg="gray.200">
109+
<Progress.Range bg={`${config.defaultColor}.400`} borderRadius="50px" />
110+
</Progress.Track>
111+
</Progress.Root>
112+
</Box>
113+
{/* Button */}
114+
<ChakraButton href={href} color="blue" label={label} />
115+
</VStack>
116+
</Box>
117+
</Box>
118+
);
119+
}

src/components/Navbar.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Button, Link, Text } from "@chakra-ui/react";
2+
import { SignOutButton } from "@clerk/nextjs";
23

34
export default function Navbar() {
45
return (
@@ -19,9 +20,12 @@ export default function Navbar() {
1920
<Text>Profile</Text>
2021
</Button>
2122
</Link>
22-
<Button className="signOut" onClick={undefined}>
23-
<Text>Sign-out</Text>
24-
</Button>
23+
{/* added signout button for testing purposes - Nathaniel */}
24+
<SignOutButton>
25+
<Button className="signOut" onClick={undefined}>
26+
<Text>Sign-out</Text>
27+
</Button>
28+
</SignOutButton>
2529
</nav>
2630
</header>
2731
);

0 commit comments

Comments
 (0)