Skip to content

Commit

Permalink
feat: bind get games from game store api
Browse files Browse the repository at this point in the history
  • Loading branch information
ngolapnguyen committed Mar 10, 2023
1 parent 3b70f3f commit c7c4ff6
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 61 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@sentry/react": "^7.36.0",
"@sentry/tracing": "^7.36.0",
"@tanstack/react-virtual": "^3.0.0-beta.48",
"@tippyjs/react": "^4.2.6",
"@unocss/preset-uno": "^0.48.3",
"@unocss/transformer-variant-group": "^0.48.3",
"@unocss/vite": "^0.48.3",
Expand Down
10 changes: 10 additions & 0 deletions src/stores/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { Socket, Channel } from "phoenix";
import { Ad } from "types/ads";
import produce from "immer";
import { Minigame } from "types/games";
import { Pagination } from "types/apis";

export const DEFAULT_PLAYER = {
name: "Nez",
Expand Down Expand Up @@ -126,6 +127,9 @@ interface State {
closeMenu: () => void;

minigame?: Minigame;
getMinigames: (
params: Partial<Pagination>
) => Promise<FullResponse<Minigame>>;
startMinigame: (game: Minigame) => void;
stopMinigame: () => void;

Expand Down Expand Up @@ -403,6 +407,12 @@ export const useGameState = create<State>((set, get) => ({
},

minigame: undefined,
getMinigames: (params) => {
const queryString = new URLSearchParams(params as any);
return fetch(
`https://game-store-api.console.so/api/v1/games?${queryString.toString()}`
).then((res) => res.json());
},
startMinigame: (minigame: Minigame) => {
const game = get().game;

Expand Down
26 changes: 20 additions & 6 deletions src/types/games.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
export interface Minigame {
export interface MinigamePublisher {
website: string;
updated_at: string;
name: string;
inserted_at: string;
id: string;
src: string;
eth_address: string;
country: string;
}
export interface Minigame {
version: string;
updated_at: string;
thumbnail: string;
tags: string[];
status: string;
runner_url: string;
publisher: MinigamePublisher;
platform: string;
name: string;
inserted_at: string;
id: string;
icon: string;
description: string;
type: "browser";
tags: string[];
thumbnailSrc: string;
logoSrc: string;
}
119 changes: 66 additions & 53 deletions src/ui/components/Menu/MinigameMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,27 @@ import { useGameState } from "stores/game";
import { GradientContainer } from "../GradientContainer";
import { Minigame } from "types/games";
import { Fragment, useMemo, useState } from "react";
import useSWR from "swr";
import Tippy from "@tippyjs/react";
import { Pagination } from "../Pagination";
import { MinigameGridSkeleton } from "../skeletons/MinigameGridSkeleton";
import "tippy.js/dist/tippy.css";

const CACHE_KEY = "pod-minigame-cache";

const mockData: Minigame[] = [
{
thumbnailSrc: "/assets/images/title-screen-bg.jpeg",
logoSrc: "/minigames/tripod.png",
name: "Tripod",
description:
"A match-3 game where players progress by combining 3 pieces of the same type to get the next piece, all the way to the top!",
id: "tripod",
src: "https://tripod-web.vercel.app/",
type: "browser",
tags: ["puzzle", "strategy"],
},
{
thumbnailSrc: "/assets/images/title-screen-bg.jpeg",
logoSrc: "/minigames/hunger-game.webp",
name: "Hunger Game",
description:
"Collect points while racing to find the portal before your opponents. Just watch out for explosions!",
id: "hunger-game",
src: "https://the-hunger-game.vercel.app/",
type: "browser",
tags: ["puzzle", "team"],
},
];
const PAGE_SIZE = 12;

export const MinigameMenu = () => {
const { closeMenu, startMinigame, getActiveScene } = useGameState();
const { closeMenu, startMinigame, getActiveScene, getMinigames } =
useGameState();
const [searchQuery, setSearchQuery] = useState("");

const [page, setPage] = useState(1);
const { data, isLoading } = useSWR(["minigames", page], () =>
getMinigames({ page, size: PAGE_SIZE })
);
const minigames = data?.data ?? [];
const isLastPage = minigames.length < PAGE_SIZE;

const updateCache = (game: Minigame) => {
const cache = JSON.parse(window.localStorage.getItem(CACHE_KEY) || "{}");
if (Array.isArray(cache.recentlyPlayedGames)) {
Expand All @@ -54,13 +43,26 @@ export const MinigameMenu = () => {
});
};

const renderTags = (tags: string[]) => {
return tags.map((tag) => {
return (
<span
key={tag}
className="text-[10px] px-1.5 py-0.5 rounded text-black bg-white uppercase font-bold flex-shrink-0"
>
{tag}
</span>
);
});
};

const renderGameCard = (game: Minigame) => {
return (
<div className="grid-cols-1 transition-all rounded border border-#1f1f42 hover:(shadow-md shadow-#2FD4D6/30 border-#2FD4D6/30 -translate-y-[0.2rem]) overflow-hidden">
<img src={game.thumbnailSrc} className="aspect-16/9" />
<div className="grid-cols-1 transition-all rounded border border-#1f1f42 hover:(shadow-md shadow-#2FD4D6/30 border-#2FD4D6/30) overflow-hidden">
<img src={game.thumbnail} className="aspect-16/9 bg-white/30 w-full" />
<div className="p-2 2xl:p-4 flex flex-col space-y-2">
<div className="flex gap-2 items-center">
<img src={game.logoSrc} className="w-8 h-8" />
<img src={game.icon} className="w-8 h-8" />
<div className="text-base font-bold">{game.name}</div>
</div>
<div
Expand All @@ -77,19 +79,21 @@ export const MinigameMenu = () => {
Play on browser
</div>
<div className="flex justify-between gap-4">
<div className="flex flex-wrap items-center gap-1 flex-1">
{game.tags.map((tag) => (
<span
key={tag}
className="text-[10px] px-1.5 py-0.5 rounded text-black bg-white uppercase font-bold"
>
{tag}
</span>
))}
</div>
<Tippy
placement="bottom"
content={
<div className="flex flex-wrap items-center gap-1 flex-1 max-w-96 py-1">
{renderTags(game.tags)}
</div>
}
>
<div className="flex items-center gap-1 flex-1 overflow-hidden relative after:block after:content-none after:absolute after:(w-16 h-full right-0 top-0 bg-gradient-linear bg-gradient-from-transparent bg-gradient-to-#140F29 bg-gradient-to-r)">
{renderTags(game.tags)}
</div>
</Tippy>
<button
type="button"
className="btn btn-primary-blue btn-sm text-xs 2xl:text-sm"
className="btn btn-primary-blue btn-sm text-xs 2xl:text-sm flex-shrink-0"
onClick={() => {
play(game);
}}
Expand All @@ -103,22 +107,18 @@ export const MinigameMenu = () => {
};

const { allGames, recentlyPlayedGames } = useMemo(() => {
const filteredGames = mockData.filter((game) =>
game.name.match(searchQuery)
);

const cache = JSON.parse(window.localStorage.getItem(CACHE_KEY) || "{}");
const recentlyPlayedGames = filteredGames.filter(
const recentlyPlayedGames = minigames.filter(
(game) =>
Array.isArray(cache.recentlyPlayedGames) &&
cache.recentlyPlayedGames.includes(game.id)
);

return {
allGames: filteredGames,
allGames: minigames,
recentlyPlayedGames,
};
}, [searchQuery]);
}, [minigames]);

return (
<div className="lg:w-800px 2xl:w-1200px text-white flex flex-col h-80vh overflow-hidden">
Expand All @@ -136,7 +136,7 @@ export const MinigameMenu = () => {
/>
</GradientContainer>
</div>
<div className="flex-1 overflow-auto">
<div className="flex-1 overflow-auto flex flex-col">
{recentlyPlayedGames.length > 0 && (
<div className="mb-8">
<div className="font-bold mb-4 uppercase text-white text-opacity-50">
Expand All @@ -155,12 +155,25 @@ export const MinigameMenu = () => {
<div className="font-bold mb-4 uppercase text-white text-opacity-50">
All Games
</div>
<div className="grid grid-cols-2 2xl:grid-cols-3 gap-6">
{allGames.map((game) => {
return <Fragment key={game.id}>{renderGameCard(game)}</Fragment>;
})}
</div>
{isLoading ? (
<MinigameGridSkeleton />
) : (
<div className="grid grid-cols-2 2xl:grid-cols-3 gap-6">
{allGames.map((game) => {
return (
<Fragment key={game.id}>{renderGameCard(game)}</Fragment>
);
})}
</div>
)}
</div>
<Pagination
className="mt-auto"
page={page}
onChange={(v) => setPage(v)}
hideOnSinglePage
isLastPage={isLastPage}
/>
</div>
</div>
);
Expand Down
41 changes: 41 additions & 0 deletions src/ui/components/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import clsx from "clsx";

interface Props {
page: number;
className?: string;
isLastPage?: boolean;
hideOnSinglePage?: boolean;
onChange: (page: number) => void;
}

export const Pagination = (props: Props) => {
const { page, className, isLastPage, hideOnSinglePage, onChange } = props;

if (hideOnSinglePage && isLastPage && page === 1) {
return null;
}

return (
<div className={clsx("flex items-center space-x-2", className)}>
<span className="font-medium">Page {page}</span>
<span>-</span>
<button
type="button"
disabled={page === 1}
onClick={() => onChange(page - 1)}
className="disabled:opacity-30 underline"
>
Prev
</button>
<span>-</span>
<button
type="button"
disabled={isLastPage}
onClick={() => onChange(page + 1)}
className="disabled:opacity-30 underline"
>
Next
</button>
</div>
);
};
4 changes: 2 additions & 2 deletions src/ui/components/minigames/MinigameIframes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const MinigameIframes = () => {
if (minigame) {
const hs = new Postmate({
container: document.getElementById("minigame-frame"),
url: minigame.src,
url: minigame.runner_url,
name: minigame.name,
classListArray: ["flex-1"],
});
Expand Down Expand Up @@ -44,7 +44,7 @@ export const MinigameIframes = () => {
>
<div className="h-12 flex justify-between items-center p-4">
<div className="flex space-x-2">
<img src={minigame.logoSrc} className="w-6 h-6" />
<img src={minigame.icon} className="w-6 h-6" />
<span className="capitalize text-white">{minigame.name}</span>
</div>
<button
Expand Down
30 changes: 30 additions & 0 deletions src/ui/components/skeletons/MinigameGridSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export const MinigameGridSkeleton = () => {
return (
<div className="grid grid-cols-2 2xl:grid-cols-3 gap-6 animate-pulse">
{new Array(2).fill(0).map((_, index) => {
return (
<div className="grid-cols-1 rounded bg-white/10" key={index}>
<div className="w-full aspect-16/9 bg-white/10" />
<div className="p-2 2xl:p-4 flex flex-col space-y-2">
<div className="flex gap-2 items-center">
<div className="w-8 h-8 bg-white/10" />
<div className="w-24 h-8 bg-white/10" />
</div>
<div className="h-16 bg-white/10" />
<div className="bg-white/10 h-6 w-24" />
<div className="flex justify-between gap-4">
<div className="flex items-center gap-1 flex-1 overflow-hidden relative after:block after:content-none after:absolute ">
{new Array(3).fill(0).map((_, index) => {
return (
<span className="w-16 h-6 bg-white/10" key={index} />
);
})}
</div>
</div>
</div>
</div>
);
})}
</div>
);
};
19 changes: 19 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2001,6 +2001,11 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==

"@popperjs/core@^2.9.0":
version "2.11.6"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==

"@remix-run/[email protected]":
version "1.12.0"
resolved "https://registry.yarnpkg.com/@remix-run/dev/-/dev-1.12.0.tgz#b2e5791d5a526849c74c5e15ddf1eb8737a70de7"
Expand Down Expand Up @@ -2425,6 +2430,13 @@
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.49.tgz#058d876a4ccdb1c980b24b39824a8eae2a438791"
integrity sha512-tPmuUiQwUW3PdQirBjlqeZD1JMn7bcyNE6stS50RUu5vyopzLFALnN0uTBftrZOcHpkRtYVa0ifz9fB9yKKJYw==

"@tippyjs/react@^4.2.6":
version "4.2.6"
resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.2.6.tgz#971677a599bf663f20bb1c60a62b9555b749cc71"
integrity sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==
dependencies:
tippy.js "^6.3.1"

"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
Expand Down Expand Up @@ -9752,6 +9764,13 @@ [email protected]:
dependencies:
convert-hrtime "^3.0.0"

tippy.js@^6.3.1:
version "6.3.7"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==
dependencies:
"@popperjs/core" "^2.9.0"

tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
Expand Down

0 comments on commit c7c4ff6

Please sign in to comment.