Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add swipper for phone play #74

Merged
merged 7 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"@dojoengine/state": "1.0.0",
"@dojoengine/torii-client": "1.0.0",
"@dojoengine/utils": "1.0.0",
"@egjs/flicking-plugins": "^4.7.1",
"@egjs/react-flicking": "^4.11.4",
Comment on lines +35 to +36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Remove unused @egjs/flicking dependencies

Based on the codebase analysis, only the Swiper library is being actively used in the implementation (Swipper.tsx component). The @egjs/flicking-plugins and @egjs/react-flicking packages are included in package.json but are not used anywhere in the codebase. These should be removed to avoid unnecessary dependencies.

  • Remove @egjs/flicking-plugins from package.json
  • Remove @egjs/react-flicking from package.json
🔗 Analysis chain

Consider consolidating slider libraries

I notice that both Swiper and @egjs/flicking (with plugins) are being added, which serve similar purposes for creating slider/carousel components. Consider using just one of these libraries to reduce bundle size and maintain consistency.

Let's check the bundle sizes and peer dependencies:

If you decide to proceed with both libraries, please document the specific use case for each to justify maintaining both dependencies.

Also applies to: 89-89


Let me gather more information about how these libraries are actually used in the codebase.


Let me try again with correct file type flags for ripgrep.


Let me check the actual implementation of the Swiper component and the CSS to understand if both libraries are actually being used.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check package sizes and peer dependencies

# Get package sizes
npm view swiper size
npm view @egjs/react-flicking size
npm view @egjs/flicking-plugins size

# Check peer dependencies
npm view swiper peerDependencies --json
npm view @egjs/react-flicking peerDependencies --json
npm view @egjs/flicking-plugins peerDependencies --json

Length of output: 287


Script:

#!/bin/bash
# Search for usage of both slider libraries
echo "=== Searching for Swiper usage ==="
rg -l "swiper" --type ts --type tsx --type js --type jsx

echo -e "\n=== Searching for Flicking usage ==="
rg -l "flicking" --type ts --type tsx --type js --type jsx

echo -e "\n=== Checking specific component implementations ==="
rg -A 5 "import.*[SF]licking|import.*[Ss]wiper" --type ts --type tsx --type js --type jsx

Length of output: 578


Script:

#!/bin/bash
# Search for usage of both slider libraries
echo "=== Searching for Swiper usage ==="
rg -l "swiper"

echo -e "\n=== Searching for Flicking usage ==="
rg -l "flicking"

echo -e "\n=== Checking specific component implementations ==="
rg -A 5 "import.*[SF]licking|import.*[Ss]wiper"

# Also check for any CSS/SCSS files that might use these components
echo -e "\n=== Checking CSS/SCSS files ==="
rg -l "swiper|flicking" -g "*.{css,scss}"

Length of output: 1483


Script:

#!/bin/bash
# Check the Swiper component implementation
echo "=== Swiper Component Implementation ==="
cat client/src/ui/components/Swipper.tsx

echo -e "\n=== CSS Implementation ==="
cat client/src/index.css | grep -A 10 -B 10 "swiper\|flicking"

Length of output: 1801

"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fortawesome/fontawesome-svg-core": "^6.6.0",
Expand Down Expand Up @@ -84,6 +86,7 @@
"rxjs": "^7.8.1",
"sonner": "^1.5.0",
"starknet": "6.11.0",
"swiper": "^11.1.15",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"tsparticles": "^3.5.0",
Expand Down
1,127 changes: 557 additions & 570 deletions client/pnpm-lock.yaml

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions client/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,14 @@ body[style*="pointer-events: none"] #controller {
body[style*="pointer-events: none"] #controller * {
pointer-events: auto !important;
}

.swiper {
width: 280px;
height: 400px;
}

.swiper-slide {
display: flex;
align-items: center;
justify-content: center;
}
2 changes: 1 addition & 1 deletion client/src/ui/components/GameModeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const GameModeCard: React.FC<GameModeCardProps> = ({
}, [mode]);

return (
<Card className="w-full max-w-sm bg-gray-900 text-white border-0 relative font-semibold md:font-normal">
<Card className="w-full h-full max-w-sm bg-gray-900 text-white border-0 relative font-semibold md:font-normal">
{isMdOrLarger &&
(mode === ModeType.Daily || mode === ModeType.Normal) && (
<div className="absolute top-0 bg-white text-black text-xs px-2 py-1 rounded-br-lg rounded-tl-xl">
Expand Down
146 changes: 146 additions & 0 deletions client/src/ui/components/GameModeCardMobile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React, { useMemo } from "react";
import { Hourglass, Trophy, Coins, Zap, HelpCircle } from "lucide-react";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "../elements/card";
import { ModeType } from "@/dojo/game/types/mode";
import { useSettings } from "@/hooks/useSettings";
import useTournament from "@/hooks/useTournament";
import { Start } from "../actions/Start";
import TournamentTimer from "./TournamentTimer";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../elements/tooltip";
import { useMediaQuery } from "react-responsive";
import { formatPrize } from "@/utils/price";
import NftImage from "./ImageNFTZkube";

const { VITE_PUBLIC_GAME_TOKEN_SYMBOL } = import.meta.env;

interface GameModeCardProps {
mode: ModeType;
handleGameMode: () => void;
}

const GameModeCardMobile: React.FC<GameModeCardProps> = ({
mode,
handleGameMode,
}) => {
const { settings } = useSettings();

const { endTimestamp, tournament } = useTournament(mode);

Comment on lines +32 to +39
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for hook failures.

The hooks useSettings and useTournament might fail, but there's no error handling in place.

 const GameModeCardMobile: React.FC<GameModeCardProps> = ({
   mode,
   handleGameMode,
 }) => {
-  const { settings } = useSettings();
+  const { settings, error: settingsError } = useSettings();
+  const { endTimestamp, tournament, error: tournamentError } = useTournament(mode);
 
-  const { endTimestamp, tournament } = useTournament(mode);
+  if (settingsError || tournamentError) {
+    return (
+      <Card className="w-full h-full bg-gray-800 text-white border-2 border-white">
+        <CardContent>
+          <div className="text-center text-red-500">
+            Failed to load game mode data. Please try again.
+          </div>
+        </CardContent>
+      </Card>
+    );
+  }

Committable suggestion skipped: line range outside the PR's diff.

const potentialWin = useMemo(() => {
if (!tournament) return formatPrize(0n, VITE_PUBLIC_GAME_TOKEN_SYMBOL);
return formatPrize(tournament.prize, VITE_PUBLIC_GAME_TOKEN_SYMBOL);
}, [tournament]);

const cost = useMemo(() => {
if (!settings) return formatPrize(0n, VITE_PUBLIC_GAME_TOKEN_SYMBOL);
const weiCost = settings.game_price;
return formatPrize(weiCost, VITE_PUBLIC_GAME_TOKEN_SYMBOL);
}, [settings]);

const difficultyRule = useMemo(() => {
switch (mode) {
case ModeType.Daily:
return {
name: "Pro Mode",
description:
"A fixed high-difficulty challenge that tests your skills at their peak.",
};
case ModeType.Normal:
return {
name: "Escalation Mode",
description:
"Difficulty increases over time, pushing you to adapt and improve.",
};
case ModeType.Free:
return {
name: "Escalation Mode",
description:
"Difficulty increases over time, pushing you to adapt and improve.",
};
default:
return {
name: "Normal Mode",
description:
"A standard mode with a fixed difficulty level for consistent play.",
};
}
}, [mode]);
Comment on lines +40 to +78
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor duplicate difficulty rules and add price formatting error handling.

There are two issues to address:

  1. Duplicate difficulty rules for Free and Normal modes
  2. Missing error handling for price formatting
 const potentialWin = useMemo(() => {
-  if (!tournament) return formatPrize(0n, VITE_PUBLIC_GAME_TOKEN_SYMBOL);
-  return formatPrize(tournament.prize, VITE_PUBLIC_GAME_TOKEN_SYMBOL);
+  try {
+    if (!tournament) return formatPrize(0n, VITE_PUBLIC_GAME_TOKEN_SYMBOL);
+    return formatPrize(tournament.prize, VITE_PUBLIC_GAME_TOKEN_SYMBOL);
+  } catch (error) {
+    console.error('Error formatting prize:', error);
+    return { withImage: '0' };
+  }
 }, [tournament]);

 const difficultyRule = useMemo(() => {
+  const ESCALATION_MODE = {
+    name: "Escalation Mode",
+    description: "Difficulty increases over time, pushing you to adapt and improve.",
+  };
+
   switch (mode) {
     case ModeType.Daily:
       return {
         name: "Pro Mode",
         description:
           "A fixed high-difficulty challenge that tests your skills at their peak.",
       };
     case ModeType.Normal:
-      return {
-        name: "Escalation Mode",
-        description:
-          "Difficulty increases over time, pushing you to adapt and improve.",
-      };
+      return ESCALATION_MODE;
     case ModeType.Free:
-      return {
-        name: "Escalation Mode",
-        description:
-          "Difficulty increases over time, pushing you to adapt and improve.",
-      };
+      return ESCALATION_MODE;
     default:
       return {
         name: "Normal Mode",

Committable suggestion skipped: line range outside the PR's diff.


return (
<Card className="w-full h-full bg-gray-800 text-white border-2 border-white">
<CardHeader className="pt-4 pb-10 md:pb-4">
<CardTitle className="text-2xl md:text-2xl font-bold text-center">
{mode}
</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm md:text-base pb-4">
<div className="flex items-center space-x-2 gap-2">
<Coins className="h-5 w-5 flex-shrink-0" />
<div className="flex-grow flex justify-between items-center">
<span className="font-semibold">Cost</span>
<div className="text-slate-300">
{mode === ModeType.Free ? (
"Free"
) : (
<div className="flex items-center gap-3 -mr-1">
{cost.withImage} <p>or</p>
<div className="flex items-center gap-1">
<p>1</p>
<NftImage />
</div>
</div>
)}
</div>
</div>
</div>

<div className="flex items-center space-x-2 gap-2">
<Zap className="h-5 w-5 flex-shrink-0" />
<div className="flex-grow flex justify-between items-center">
<span className="font-semibold">Difficulty</span>
<span className="flex items-center text-slate-300 relative">
{difficultyRule.name}
</span>
</div>
</div>

{(mode === ModeType.Daily || mode === ModeType.Normal) && (
<>
<div className="flex items-center space-x-2 w-full gap-2">
<Hourglass className="h-5 w-5 flex-shrink-0" />
<div className="flex-grow flex justify-between items-center">
<span className="font-semibold">Ends in</span>
<div className="text-slate-300">
<TournamentTimer mode={mode} endTimestamp={endTimestamp} />
</div>
</div>
</div>
<div className="flex items-center space-x-2 w-full gap-2">
<Trophy className="h-5 w-5 flex-shrink-0" />
<div className="flex-grow flex justify-between items-center">
<span className="font-semibold">Prize Pool</span>
{potentialWin.withImage}
</div>
</div>
</>
)}
</CardContent>
<CardFooter className="mt-8">
<Start mode={mode} handleGameMode={handleGameMode} />
</CardFooter>
</Card>
);
};

export default GameModeCardMobile;
2 changes: 1 addition & 1 deletion client/src/ui/components/HeaderNftBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const HeaderNftBalance = React.memo(() => {

if (account) {
return (
<span className="text-xs font-semibold md:font-normal flex items-center ">
<span className="text-xs font-semibold md:font-normal flex items-center">
{`${balance}`} <NftImage />
</span>
);
Expand Down
2 changes: 1 addition & 1 deletion client/src/ui/components/MobileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const MobileHeader = ({ onStartTutorial }: MobileHeaderProps) => {
{!!player && account ? (
<div className="flex gap-3 items-center">
<DropdownMenu>
<DropdownMenuTrigger>
<DropdownMenuTrigger asChild>
<Button variant={"outline"}>
<FontAwesomeIcon icon={faCoins} />
</Button>
Expand Down
48 changes: 48 additions & 0 deletions client/src/ui/components/Swipper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Swiper, SwiperSlide } from "swiper/react";

import "swiper/css";
import "swiper/css/effect-cards";

import "@/index.css";

import { EffectCards } from "swiper/modules";
import { ModeType } from "@/dojo/game/types/mode";
import GameModeCardMobile from "./GameModeCardMobile";

interface SwiperProps {
setIsGameOn: (isOn: string) => void;
}
Comment on lines +12 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using more specific types for better type safety.

The setIsGameOn callback accepts any string, but it appears to only handle "isOn". This could lead to runtime errors if different strings are passed.

Consider this improvement:

 interface SwiperProps {
-  setIsGameOn: (isOn: string) => void;
+  setIsGameOn: (state: boolean) => void;
 }

Then update the usage:

-  handleGameMode={() => setIsGameOn("isOn")}
+  handleGameMode={() => setIsGameOn(true)}

Committable suggestion skipped: line range outside the PR's diff.


export default function Swipper({ setIsGameOn }: SwiperProps) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix component name spelling.

The component name "Swipper" is misspelled. It should be "Swiper" to maintain consistency with the library name and common naming conventions.

-export default function Swipper({ setIsGameOn }: SwiperProps) {
+export default function Swiper({ setIsGameOn }: SwiperProps) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default function Swipper({ setIsGameOn }: SwiperProps) {
export default function Swiper({ setIsGameOn }: SwiperProps) {

return (
<>
<Swiper
effect={"cards"}
grabCursor={true}
modules={[EffectCards]}
className="mySwiper"
initialSlide={1}
>
Comment on lines +19 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add accessibility attributes to the Swiper component.

The slider component lacks proper accessibility attributes which could make it difficult for users with assistive technologies.

 <Swiper
   effect={"cards"}
   grabCursor={true}
   modules={[EffectCards]}
   className="mySwiper"
   initialSlide={1}
+  a11y={{
+    prevSlideMessage: 'Previous game mode',
+    nextSlideMessage: 'Next game mode',
+    firstSlideMessage: 'This is the first game mode',
+    lastSlideMessage: 'This is the last game mode'
+  }}
 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Swiper
effect={"cards"}
grabCursor={true}
modules={[EffectCards]}
className="mySwiper"
initialSlide={1}
>
<Swiper
effect={"cards"}
grabCursor={true}
modules={[EffectCards]}
className="mySwiper"
initialSlide={1}
a11y={{
prevSlideMessage: 'Previous game mode',
nextSlideMessage: 'Next game mode',
firstSlideMessage: 'This is the first game mode',
lastSlideMessage: 'This is the last game mode'
}}
>

<SwiperSlide>
<GameModeCardMobile
mode={ModeType.Free}
handleGameMode={() => setIsGameOn("isOn")}
/>
</SwiperSlide>

<SwiperSlide>
<GameModeCardMobile
mode={ModeType.Daily}
handleGameMode={() => setIsGameOn("isOn")}
/>
</SwiperSlide>
<SwiperSlide>
<GameModeCardMobile
mode={ModeType.Normal}
handleGameMode={() => setIsGameOn("isOn")}
/>
</SwiperSlide>
</Swiper>
</>
);
}
30 changes: 20 additions & 10 deletions client/src/ui/screens/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ import MaxComboIcon from "../components/MaxComboIcon";
import GameModeCard from "../components/GameModeCard";
import useAccountCustom from "@/hooks/useAccountCustom";
import { useMediaQuery } from "react-responsive";
import { Start } from "../actions/Start";
import { ChevronLeft } from "lucide-react";
import CollectiveTreasureChest from "../components/TreasureChest";
import GameOverDialog from "../components/GameOverDialog";
import useViewport from "@/hooks/useViewport";
import { TweetPreview } from "../components/TweetPreview";
import { useGrid } from "@/hooks/useGrid";
import Tutorial from "../components/Tutorial/Tutorial";
import Swipper from "../components/Swipper";
import HeaderBalance from "../components/HeaderBalance";

export const Home = () => {
useViewport();
Expand Down Expand Up @@ -231,15 +232,24 @@ export const Home = () => {

const renderMobileView = () => (
<div className="flex flex-col w-full gap-4 px-4 mt-4">
<Start mode={ModeType.Free} handleGameMode={handlePlay} />
<Dialog>
<DialogTrigger asChild>
<Button
className="w-full bg-primary text-white text-lg py-6 border-4 shadow-lg bg-sky-900 font-sans rounded-none"
variant="brutal"
>
Play !
</Button>
</DialogTrigger>
<DialogContent className="w-[95%] flex flex-col justify-center p-8">
<DialogHeader className="flex flex-row gap-3 items-center justify-center w-full space-y-0">
<HeaderBalance />
</DialogHeader>

<Swipper setIsGameOn={() => setIsGameOn("isOn")}></Swipper>
</DialogContent>
</Dialog>

<Button
variant={"brutal"}
onClick={handleTournaments}
className="w-full bg-primary text-secondary text-lg py-6 border-4 shadow-lg bg-sky-200 font-sans rounded-none"
>
<p>Tournaments</p>
</Button>
<Button
variant={"brutal"}
onClick={() => setChestIsOpen(true)}
Expand All @@ -261,7 +271,7 @@ export const Home = () => {
<Dialog open={isSigning} modal>
<DialogContent
aria-describedby={undefined}
className="flex flex-col items-center justify-center p-6 "
className="flex flex-col items-center justify-center p-6"
>
<p className="mt-8 mb-7">Aligning the blocks for your signup...</p>
</DialogContent>
Expand Down
Loading