22 Commits

Author SHA1 Message Date
Walke 0a811741c7 Merge pull request 'Navbar,landing, meg a többi frontend javítás' (#60) from ujbarni into main
Reviewed-on: #60
HIHETETLEEEENÜL SZIPI SZUPER
2025-10-22 13:22:00 +00:00
Barni d16d481d86 Navbar,landing, meg a többi frontend javítás 2025-10-22 15:15:20 +02:00
Walke ad5f13a8e1 Merge pull request 'guessName+Fixes' (#58) from frontendFix+Guess into main
Reviewed-on: #58

VAOOO UGUYES VAGYYY
2025-10-21 13:09:27 +00:00
Walke 237378c208 guessName+Fixes 2025-10-21 15:08:28 +02:00
Walke a1cf327837 Merge pull request 'Ha be van jelenkezve a user akkor a /# en nem irja ki neki a belepest meg a regisztraciot VAMOOOS' (#57) from landingFlowFix into main
Reviewed-on: #57
szerintem is :D:D:D:D
2025-10-21 12:14:13 +00:00
Walke c31bf9d4fb Ha be van jelenkezve a user akkor a /# en nem irja ki neki a belepest meg a regisztraciot VAMOOOS 2025-10-21 14:13:16 +02:00
Walke ef0b1916f2 Merge pull request 'dobokocka mukodik :O' (#56) from dice into main
Reviewed-on: #56
kocka kocka kocka kocka kocka kocka kocka
2025-10-20 17:40:13 +00:00
Walke 1c01e4ce24 Merge pull request 'nagyon meno lett a tabon logo SerpentRace minden' (#55) from tab into main
sztem jo xd
2025-10-20 17:39:25 +00:00
Walke 8b5cf2c1e5 nagyon meno lett a tabon logo SerpentRace minden 2025-10-20 19:38:21 +02:00
Walke 023219e41b dobokocka mukodik :O 2025-10-20 19:20:49 +02:00
magdo 2d7778f7d1 test removed 2025-10-20 19:17:43 +02:00
magdo aa3587b60a deck card count added 2025-10-20 19:13:50 +02:00
Walke 99fa7ebd98 Merge pull request 'registracional jol navigal a loginra' (#54) from regnavigationfix into main
Reviewed-on: #54
:D
2025-10-20 16:57:15 +00:00
Walke 23c4b838d4 registracional jol navigal a loginra 2025-10-20 18:56:38 +02:00
Walke bfe977d35b Merge remote-tracking branch 'origin/deck_kezeles' 2025-10-20 18:01:41 +02:00
Walke 8d24e8ffa6 Merge pull request 'Lobby' (#50) from barni1020 into main
Reviewed-on: #50
2025-10-20 15:22:13 +00:00
Barni 1bf3253128 Lobby 2025-10-20 17:14:37 +02:00
Walke 96487fb065 Merge pull request 'Filter bar fix' (#49) from deckmanagerfrontendfix into main
Reviewed-on: #49
2025-10-18 15:52:30 +00:00
Walke 9ef83f7963 Filter bar fix 2025-10-18 17:50:39 +02:00
Walke 27fc028bad navbarban jol le vannak kezelve a redirect es letre lett hozva egy hook amivel automatikusan berakja a usernamet es ha meg nem akkor redirectel 2025-10-15 19:08:31 +02:00
Walke d1b4141e63 Merge pull request 'redirect fix' (#48) from authlocalstorage into main
Reviewed-on: #48
2025-10-15 16:40:22 +00:00
Walke 76fa204ae8 redirect fix 2025-10-15 18:39:43 +02:00
24 changed files with 592 additions and 316 deletions
@@ -1,66 +0,0 @@
import e, { Router } from 'express';
import { container, DIContainer } from '../../Application/Services/DIContainer';
import { ErrorResponseService } from '../../Application/Services/ErrorResponseService';
import { logRequest, logError, logAuth, logWarning, logOther } from '../../Application/Services/Logger';
import { GenerateBoardCommand } from '../../Application/Game/commands/GenerateBoardCommand';
const router = Router();
//function to test the search service
async function triggerAsyncBoardGeneration(gameId: string): Promise<boolean> {
try {
// Calculate default field counts based on game configuration
// For now, use reasonable defaults - this should be configurable by host in the future
const maxSpecialFieldsPercentage = parseInt(process.env.MAX_SPECIAL_FIELDS_PERCENTAGE || '67');
const maxSpecialFields = Math.floor((100 * maxSpecialFieldsPercentage) / 100);
// Default distribution: 60% positive, 25% negative, 15% luck
const positiveFieldCount = Math.floor(maxSpecialFields * 0.6);
const negativeFieldCount = Math.floor(maxSpecialFields * 0.25);
const luckFieldCount = Math.floor(maxSpecialFields * 0.15);
const command: GenerateBoardCommand = {
gameId,
positiveFieldCount,
negativeFieldCount,
luckFieldCount
};
logOther(`Triggering async board generation for game ${gameId}`, {
positiveFieldCount,
negativeFieldCount,
luckFieldCount,
totalSpecialFields: positiveFieldCount + negativeFieldCount + luckFieldCount
});
// Execute board generation in background
await DIContainer.getInstance().generateBoardCommandHandler.execute(command);
return true;
} catch (error) {
logError(`Async board generation failed for game ${gameId}`, error as Error);
// Don't propagate error - board generation failure shouldn't affect game creation
return false;
}
}
// Game board generation endpoint
router.post('/gameBoardGeneration', async (req, res) => {
try {
logRequest('Game board generation endpoint accessed', req, res);
const result = await triggerAsyncBoardGeneration("######-#####-#####-######");
if (result) {
logOther('Game board generation triggered successfully', result);
return res.json({ message: 'Game board generation triggered successfully' });
} else {
throw new Error('Game board generation failed to trigger');
}
} catch (error : any) {
logError('Error in game board generation endpoint', error);
return ErrorResponseService.sendInternalServerError(res);
}
});
export default router;
@@ -15,6 +15,7 @@ export interface ShortDeckDto {
type: number;
playedNumber: number;
ctype: number;
cardsCount: number;
}
export interface DetailDeckDto {
@@ -9,6 +9,7 @@ export class DeckMapper {
type: deck.type,
playedNumber: deck.playedNumber,
ctype: deck.ctype,
cardsCount: deck.cards.length,
};
}
@@ -26,6 +27,13 @@ export class DeckMapper {
}
static toShortDtoList(decks: DeckAggregate[]): ShortDeckDto[] {
return decks.map(this.toShortDto);
return decks.map(deck => ({
id: deck.id,
name: deck.name,
type: deck.type,
playedNumber: deck.playedNumber,
ctype: deck.ctype,
cardsCount: deck.cards.length,
}));
}
}
+3 -3
View File
@@ -1,10 +1,10 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="/src/assets/pictures/Logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<title>SerpentRace</title>
</head>
<body>
<div id="root"></div>
+2
View File
@@ -15,6 +15,7 @@ import About from "./pages/About/About"
import ScrollToTop from "./components/ScrollToTop"
import GameScreen from "./pages/Game/GameScreen"
import Reports from "./pages/Report/Reports"
import Lobby from "./pages/Lobby/Lobby"
function App() {
const [isMobile, setIsMobile] = useState(false)
@@ -46,6 +47,7 @@ function App() {
<Router>
<Routes>
<Route path="/about" element={<About />} />
<Route path="/lobby" element={<Lobby />} />
<Route path="/register" element={<AuthRegister />} />
<Route path="/login" element={<AuthLogin />} />
<Route path="/verify-email" element={<EmailVerification />} />
+4 -14
View File
@@ -1,7 +1,7 @@
import axios from "axios"
export const API_CONFIG = {
baseURL: (import.meta.env.VITE_API_URL ? import.meta.env.VITE_API_URL : '') + "/api",
baseURL: (import.meta.env.VITE_API_URL ? import.meta.env.VITE_API_URL : "") + "/api",
wsURL: "http://localhost:3000",
timeout: 10000,
retryAttempts: 3,
@@ -12,9 +12,9 @@ export const apiClient = axios.create({
timeout: API_CONFIG.timeout,
withCredentials: true, // Important for cookie-based auth
headers: {
'Content-Type': 'application/json'
}
});
"Content-Type": "application/json",
},
})
//login
export const login = async (username, password) => {
@@ -36,16 +36,6 @@ export const register = async (username, email, password, fname, lname, phone) =
}
}
//verify email
export const verifyEmail = async (token) => {
try {
const response = await apiClient.get(`/users/verify-email/${token}`)
return response.data
} catch (error) {
throw error
}
}
// Get current user's game statistics
export const getUserStats = async () => {
try {
@@ -125,9 +125,9 @@ const DeckManager = () => {
return (
<div className="w-full flex flex-col bg-[color:var(--color-background)]">
<div className="w-full max-w-6xl mx-auto px-4 py-10">
<div className="w-full max-w-[1200px] mx-auto px-4 py-10">
{/* Filters */}
<div className="flex flex-col md:flex-row gap-4 justify-between items-center mb-10 bg-[color:var(--color-surface)]/80 backdrop-blur-lg rounded-2xl px-6 py-4 shadow-lg">
<div className="flex flex-col md:flex-row gap-3 justify-between items-center mb-10 bg-[color:var(--color-surface)]/80 backdrop-blur-lg rounded-2xl px-6 py-4 shadow-lg">
<div className="flex gap-2 items-center w-full md:w-auto">
<SearchBox
value={search}
@@ -267,7 +267,7 @@ const DeckManager = () => {
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-8 mt-8">
{/* Create New Deck (Mockup) */}
<div
onClick={() => navigate('/deck-creator')}
onClick={() => navigate("/deck-creator")}
className="flex flex-col items-center justify-center h-48 bg-[color:var(--color-card)] border-2 border-dashed border-[color:var(--color-success)] rounded-2xl cursor-pointer hover:bg-[color:var(--color-success)]/20 transition-all duration-200 shadow-lg"
>
<FaPlus style={{ color: "var(--color-success)" }} className="text-5xl mb-2" />
@@ -320,12 +320,7 @@ const DeckManager = () => {
</div>
{/* Deck Info Popup */}
{selectedDeck && (
<DeckInfoPopUp
deck={selectedDeck}
onClose={() => setSelectedDeck(null)}
/>
)}
{selectedDeck && <DeckInfoPopUp deck={selectedDeck} onClose={() => setSelectedDeck(null)} />}
</div>
)
}
@@ -34,13 +34,13 @@ const Footer = () => {
return (
<footer
ref={footerRef}
className="relative bg-zinc-900 text-white border-t-2 border-zinc-800 mt-auto py-8"
className="relative bg-zinc-900 text-zinc-400 border-t-2 border-zinc-800 mt-auto py-8"
style={{ transformOrigin: "bottom center" }}
>
<div className="max-w-6xl mx-auto flex flex-wrap justify-between items-start gap-8 px-4">
{/* Logó */}
<div className="flex flex-col items-center">
<a href="/" className="hover:scale-105 hover:brightness-125">
<a href="/" className="hover:scale-105 hover:brightness-110">
<Logo size={100} />
</a>
<span className="font-extrabold text-xl mt-2 tracking-wide">SerpentRace</span>
@@ -48,30 +48,30 @@ const Footer = () => {
{/* Oldalak */}
<div className="flex flex-col gap-1">
<span className="text-lg font-semibold text-green-400 underline underline-offset-4 mb-2 drop-shadow-sm">
<span className="text-lg font-semibold text-green-600 underline underline-offset-4 mb-2 drop-shadow-sm">
Oldalak
</span>
<a href="/" className="hover:underline hover:text-green-400">
<a href="/" className="hover:underline hover:text-green-500">
Főoldal
</a>
<a href="/about" className="hover:underline hover:text-green-400">
<a href="/about" className="hover:underline hover:text-green-500">
Rólunk
</a>
<a href="/contact" className="hover:underline hover:text-green-400">
<a href="/contact" className="hover:underline hover:text-green-500">
Kapcsolat
</a>
</div>
{/* Közösség */}
<div className="flex flex-col gap-1">
<span className="text-lg font-semibold text-green-400 underline underline-offset-4 mb-2 drop-shadow-sm">
<span className="text-lg font-semibold text-green-600 underline underline-offset-4 mb-2 drop-shadow-sm">
Közösség
</span>
<a
href="https://discord.gg/"
target="_blank"
rel="noopener noreferrer"
className="hover:underline hover:text-green-400"
className="hover:underline hover:text-green-500"
>
Discord
</a>
@@ -79,7 +79,7 @@ const Footer = () => {
href="https://github.com/"
target="_blank"
rel="noopener noreferrer"
className="hover:underline hover:text-green-400"
className="hover:underline hover:text-green-500"
>
GitHub
</a>
@@ -87,11 +87,11 @@ const Footer = () => {
{/* Elérhetőség */}
<div className="flex flex-col gap-1">
<span className="text-lg font-semibold text-green-400 underline underline-offset-4 mb-2 drop-shadow-sm">
<span className="text-lg font-semibold text-green-600 underline underline-offset-4 mb-2 drop-shadow-sm">
Elérhetőség
</span>
<span className="opacity-80">Email: info@serpentrace.hu</span>
<span className="opacity-80">Telefon: +36 30 123 4567</span>
<span className="opacity-85">Email: info@serpentrace.hu</span>
<span className="opacity-85">Telefon: +36 30 123 4567</span>
</div>
</div>
@@ -7,7 +7,7 @@ export default function InputBox({ type, placeholder, value, onChange, width })
return (
<input
type={type}
className={`${widthClass} py-3 px-4 border border-battleship-gray rounded-lg focus:border-mint focus:outline-none text-text placeholder-text-muted bg-background font-semibold text-lg`}
className={`${widthClass} py-3 px-4 border border-battleship-gray rounded-lg focus:border-mint-lighter focus:outline-none text-text placeholder-text-muted bg-background font-semibold text-lg`}
placeholder={placeholder}
value={value}
onChange={onChange}
@@ -5,8 +5,13 @@ import logoImg from "../../assets/pictures/Logo.png"
import ButtonGreen from "../Buttons/ButtonGreen.jsx"
import { FaUsers, FaPaintBrush, FaHeadset } from "react-icons/fa"
import { motion } from "framer-motion"
import { isAuthenticated } from "../../hooks/useRequireAuth" // <-- added import
import { useNavigate } from "react-router-dom" // <-- NEW
const LandingPage = ({ onNavigateToPlay, onNavigateToAuth, onNavigateToGame }) => {
const auth = isAuthenticated() // <-- check without redirect
const navigate = useNavigate() // <-- NEW
const LandingPage = ({ onNavigateToPlay, onNavigateToAuth }) => {
return (
<div className="w-full">
{/* Hero Section */}
@@ -55,8 +60,16 @@ const LandingPage = ({ onNavigateToPlay, onNavigateToAuth }) => {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 1 }}
>
<ButtonGreen text="Bejelntekezés" onClick={onNavigateToPlay} width="w-60" />
<ButtonGreen text="Regisztráció" onClick={onNavigateToAuth} width="w-60" />
{/* If not authenticated show Login/Register; if authenticated show Home button */}
{!auth ? (
<>
<ButtonGreen text="Bejelntekezés" onClick={onNavigateToPlay} width="w-60" />
<ButtonGreen text="Regisztráció" onClick={onNavigateToAuth} width="w-60" />
<ButtonGreen text="Játék" onClick={onNavigateToGame} width="w-60" />
</>
) : (
<ButtonGreen text="Játék" onClick={() => navigate("/home")} width="w-60" />
)}
</motion.div>
</div>
</motion.section>
@@ -4,9 +4,14 @@ import logoImg from "../../assets/pictures/Logo.png" // <-- EZT ADD HOZZÁ
import ButtonDark from "../Buttons/ButtonDark.jsx"
import InputBoxDark from "../Inputs/InputBoxDark.jsx"
const PlayMenu = ({ onJoinGame, onCreateGame, user }) => {
const PlayMenu = ({ onJoinGame, onCreateGame, user, setUser }) => {
const [joinCode, setJoinCode] = useState("")
const [error, setError] = useState("")
const [guestName, setGuestName] = useState("")
const [guestError, setGuestError] = useState("")
// gyors username kiolvasás (ha a parent objektum user={ { name: ... } } küldi)
const username = user?.name ?? null
const handleJoin = () => {
if (!joinCode.trim()) {
@@ -21,9 +26,19 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user }) => {
onCreateGame()
}
// egyszerű segéd a kezdobetűk kinyerésére
const initials = username
? username
.split(" ")
.map((s) => s[0])
.join("")
.slice(0, 2)
.toUpperCase()
: ""
return (
<section
className="w-[95%] max-w-6xl mx-auto my-16 flex flex-col md:flex-row items-center justify-center rounded-3xl shadow-2xl min-h-[60vh] overflow-hidden"
className="w-[95%] max-w-6xl mx-auto my-16 flex flex-col md:flex-row items-center justify-center rounded-3xl shadow-2xl overflow-hidden"
style={{
background: "linear-gradient(90deg, var(--color-surface) 30%, var(--color-mint) 100%)",
}}
@@ -32,10 +47,10 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user }) => {
<div className="flex-1 flex items-center justify-center w-full h-full py-10 md:py-0 md:pl-10">
<LogoCard
imageSrc={logoImg}
containerHeight="450px"
containerWidth="450px"
imageHeight="450px"
imageWidth="450px"
containerHeight="420px"
containerWidth="420px"
imageHeight="420px"
imageWidth="420px"
rotateAmplitude={7}
scaleOnHover={1.03}
showMobileWarning={false}
@@ -43,12 +58,49 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user }) => {
displayOverlayContent={false}
/>
</div>
{/* Jobb oldali panel */}
<div className="flex-1 w-full flex flex-col items-center justify-center px-4 md:px-12 py-10">
<div className="w-full max-w-md rounded-2xl p-8 flex flex-col gap-8">
<div className="flex-1 w-full flex items-center justify-center px-6 md:px-12 py-8">
<div
className="w-full max-w-md rounded-2xl p-6 md:p-8 flex flex-col gap-6"
style={{ background: "rgba(0,0,0,0.15)", backdropFilter: "blur(6px)" }}
>
<div className="flex items-center justify-between">
{username ? (
<div className="flex items-center gap-3">
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold"
style={{ background: "rgba(34,197,94,0.12)", color: "var(--color-mint)" }}
>
{initials}
</div>
<div className="text-[32px]" style={{ color: "var(--color-muted, #cbd5e1)" }}>
<span className="font-medium" style={{ color: "var(--color-text, #fff)" }}>
{username}
</span>
</div>
</div>
) : (
<div className="w-full">
<div className="font-semibold mb-3 text-text">Nincs bejelentkezve játssz vendégként:</div>
<InputBoxDark
type="text"
placeholder="Nickname..."
value={guestName}
onChange={(e) => {
setGuestName(e.target.value)
setGuestError("")
}}
width="w-full"
/>
{guestError && <div className="text-xs mt-2 text-error">{guestError}</div>}
</div>
)}
</div>
<div>
<h2 className="text-lg font-semibold mb-2 text-text">Csatlakozás játékhoz</h2>
<div className={`${error ? "border border-error rounded-lg" : ""}`}>
<h2 className="font-semibold mb-3 text-text">Csatlakozás játékhoz</h2>
<div className={`${error ? "border border-error rounded-lg p-2" : ""}`}>
<InputBoxDark
type="text"
placeholder="Játék kódja"
@@ -57,17 +109,21 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user }) => {
width="w-full"
/>
</div>
{error && <div className="text-xs mt-1 text-error">{error}</div>}
{error && <div className="text-xs mt-2 text-error">{error}</div>}
<div className="mt-4">
<ButtonDark text="Csatlakozás" type="button" onClick={handleJoin} width="w-full" />
</div>
</div>
{user && (
<div>
<h2 className="text-lg font-semibold mb-2 text-text">Új játék létrehozása</h2>
<ButtonDark text="Játék létrehozása" type="button" onClick={handleCreate} width="w-full" />
{username ? (
<div className="border-t border-white/10 pt-4">
{username && (
<div>
<h3 className="font-semibold mb-3 text-text">Új játék létrehozása</h3>
<ButtonDark text="Játék létrehozása" type="button" onClick={handleCreate} width="w-full" />
</div>
)}
</div>
)}
) : null}
</div>
</div>
</section>
@@ -1,42 +1,84 @@
import React, { useState } from "react"
import Logo from "../../assets/pictures/Logo"
import About from "../../pages/About/About"
import Home from "../../pages/Landing/Home"
import { Link, useNavigate } from "react-router-dom"
const navLinkClass = "px-3 py-2 rounded-lg text-white transition-all duration-200 hover:bg-white/10"
const navLinkClassPlay =
"px-4 py-2 rounded-lg text-white bg-white/12 hover:bg-white/20 transition-all duration-200"
const Navbar = () => {
const [menuOpen, setMenuOpen] = useState(false)
const navigate = useNavigate()
const isLoggedIn = Boolean(localStorage.getItem("authLevel") && localStorage.getItem("username"))
const handleLogout = () => {
localStorage.removeItem("authLevel")
localStorage.removeItem("username")
navigate("/")
}
return (
<nav className="bg-gradient-to-r from-green-700 to-emerald-500 shadow-lg">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16 items-center">
{/* Logo */}
<div className="flex-shrink-0 flex items-center gap-2">
<a href="/" className="flex items-center mt-1 h-9">
<Link to="/" className="flex items-center mt-1 h-9">
<Logo size={36} />
</a>
<a href="/" className="flex items-center h-9 text-white font-bold text-2xl tracking-tight">
</Link>
<Link to="/" className="flex items-center h-9 text-white font-bold text-2xl tracking-tight">
SerpentRace
</a>
</Link>
</div>
{/* Desktop Menu */}
<div className="hidden md:flex space-x-8">
<a href="/home" className={navLinkClass}>
Home
</a>
<a href="/report" className={navLinkClass}>
Stats
</a>
<a href="/about" className={navLinkClass}>
About
</a>
<a href="/companies" className={navLinkClass}>
Contact
</a>
<div className="hidden md:flex space-x-8 items-center">
{isLoggedIn ? (
<>
<Link to="/home" className={navLinkClass}>Játék</Link>
<Link to="/decks" className={navLinkClass}>Paklik</Link>
<Link to="/report" className={navLinkClass}>Statisztikák</Link>
</>
) : (
<Link to="/" className={navLinkClass}>Főoldal</Link>
)}
<Link to="/about" className={navLinkClass}>Rólunk</Link>
<Link to="/companies" className={navLinkClass}>Kontakt</Link>
{!isLoggedIn && (
<>
<Link to="/home" className={navLinkClassPlay}>Játék</Link>
<Link
to="/login"
className="px-4 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white font-semibold transition-all"
>
Bejelentkezés / Regisztráció
</Link>
</>
)}
{isLoggedIn && (
<button
onClick={handleLogout}
className="ml-4 p-2 rounded-full bg-[#166534] hover:bg-[#1f7a45] text-white shadow-lg hover:shadow-green-400/40 transition-all transform hover:scale-105 cursor-pointer"
title="Kijelentkezés"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10 16l4-4m0 0l-4-4m4 4H3m13 4v1a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2v1"
/>
</svg>
</button>
)}
</div>
{/* Mobile Hamburger */}
<div className="md:hidden flex items-center">
<button
@@ -52,12 +94,7 @@ const Navbar = () => {
xmlns="http://www.w3.org/2000/svg"
>
{menuOpen ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8h16M4 16h16" />
)}
@@ -66,21 +103,63 @@ const Navbar = () => {
</div>
</div>
</div>
{/* Mobile Menu */}
{menuOpen && (
<div className="md:hidden bg-emerald-600 px-2 pt-2 pb-3 space-y-1">
<a href="#" className={navLinkClass}>
Home
</a>
<a href="#" className={navLinkClass}>
Leaderboard
</a>
<a href="#" className={navLinkClass}>
About
</a>
<a href="#" className={navLinkClass}>
Contact
</a>
{isLoggedIn ? (
<Link to="/home" className={navLinkClass}>Home</Link>
) : (
<Link to="/" className={navLinkClass}>Főoldal</Link>
)}
<Link to="/leaderboard" className={navLinkClass}>Leaderboard</Link>
<Link to="/about" className={navLinkClass}>Rólunk</Link>
<Link to="/companies" className={navLinkClass}>Kontakt</Link>
{!isLoggedIn && (
<>
<div className="px-2">
<button
onClick={() => {
setMenuOpen(false)
navigate("/")
}}
className="w-full text-left px-3 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white transition-all"
>
Játék
</button>
</div>
<Link
to="/login"
className="block px-3 py-2 rounded-lg bg-white/10 hover:bg-white/20 text-white font-semibold transition-all"
>
Bejelentkezés / Regisztráció
</Link>
</>
)}
{isLoggedIn && (
<div className="flex justify-end px-2 pb-2">
<button
onClick={handleLogout}
className="p-2 rounded-full bg-[#166534] hover:bg-[#1f7a45] text-white shadow-lg hover:shadow-green-400/40 transition-all transform hover:scale-105 cursor-pointer"
title="Kijelentkezés"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10 16l4-4m0 0l-4-4m4 4H3m13 4v1a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2v1"
/>
</svg>
</button>
</div>
)}
</div>
)}
</nav>
@@ -0,0 +1,52 @@
import { useState, useEffect } from "react"
import { useNavigate } from "react-router-dom"
export function requireAuthSync({ key = "username", redirectTo = "/login", replace = true } = {}) {
const value = localStorage.getItem(key)
if (!value) {
if (replace) window.location.replace(redirectTo)
else window.location.assign(redirectTo)
return false
}
return true
}
// New: non-redirecting check for auth status
export function isAuthenticated(key = "username") {
try {
return !!localStorage.getItem(key)
} catch {
return false
}
}
// Default hook: ad vissza egy [value, setValue] párt, szinkronizálja localStorage-t és opcionálisan átirányít, ha nincs érték
export default function useRequireAuth({ key = "username", redirectTo = "/login", redirect = true } = {}) {
const navigate = useNavigate()
const [value, setValue] = useState(() => {
try {
return localStorage.getItem(key)
} catch {
return null
}
})
// Ha nincs érték és redirect engedélyezve van, átirányítjuk (komponens mount-oláskor)
useEffect(() => {
if (!value && redirect) {
navigate(redirectTo)
}
}, [navigate, value, redirectTo, redirect])
// Szinkronizáljuk a localStorage-t amikor a state változik
useEffect(() => {
try {
if (value == null) localStorage.removeItem(key)
else localStorage.setItem(key, value)
} catch {
// fail silently
}
}, [key, value])
return [value, setValue]
}
+4
View File
@@ -8,9 +8,13 @@
--color-eerie-black: #181d23;
--color-mint: #15803d;
--color-mint-dark: #136636;
--color-mint-darker: #11522b;
--color-mint-light: #16a34a;
--color-mint-lighter: #22c55e;
/* Gombok */
--color-button-primary: #16a34a;
--color-button-primary-hover: #15803d;
@@ -6,7 +6,7 @@ import Button from "../../components/Buttons/Button"
import { motion } from "framer-motion"
import { useState } from "react"
import { register } from "../../api/userApi"
import { useNavigate } from "react-router-dom"
import { useNavigate, useLocation } from "react-router-dom"
export default function RegisterForm() {
const [lastname, setLastname] = useState("")
@@ -19,6 +19,7 @@ export default function RegisterForm() {
const [error, setError] = useState("")
const [showErrorPopup, setShowErrorPopup] = useState(false)
const navigate = useNavigate()
const location = useLocation()
function validateEmail(email) {
return /\S+@\S+\.\S+/.test(email)
@@ -50,7 +51,15 @@ export default function RegisterForm() {
const response = await register(username, email, password, firstname, lastname, phone)
// Check for 201 Created status
if (response && response.status === 201) {
navigate("/login", { state: { success: true } })
// Ha már a /login útvonalon van a user, a sima navigate nem biztos, hogy újraindítja a komponenst.
// Ilyenkor előbb beállítjuk a state-et, majd kényszerítünk egy teljes oldalletöltést.
if (location.pathname === "/login") {
navigate("/login", { state: { success: true } })
// teljes újratöltés, hogy a login oldal újra feldolgozza a state-et
window.location.reload()
} else {
navigate("/login", { state: { success: true } })
}
} else {
let msg = "Sikertelen regisztráció."
if (response && response.data && response.data.error) {
@@ -1,7 +1,7 @@
// src/pages/Decks/DeckManagerPage.jsx
// Deck Management Page (with Navbar, no Footer)
import DeckManager from "../../components/Landingpage/DeckManager.jsx"
import DeckManager from "../../components/DeckCreator/DeckManager.jsx"
import Navbar from "../../components/Navbar/Navbar.jsx"
export default function DeckManagerPage() {
@@ -66,12 +66,17 @@ const GameScreen = () => {
{ id: 3, name: "Fürtös", position: 68, score: 14, color: "bg-yellow-600", emoji: "😂" },
])
// New: selected dice value from dropdown (null = none)
const [selectedDice, setSelectedDice] = useState(null)
// Sort players by position in descending order
const sortedPlayers = [...players].sort((a, b) => b.position - a.position)
// Handle dice roll
// Handle dice roll completion
const handleDiceRoll = (value) => {
console.log("Rolled:", value)
// reset dropdown selection after roll
setSelectedDice(null)
// You can add logic here to move the current player based on the dice value
}
@@ -118,9 +123,6 @@ const GameScreen = () => {
{/* Háttér */}
<div className="absolute w-full h-full opacity-10 pointer-events-none overflow-hidden">
{[...Array(35)].map((_, i) => (
// Sajat pulse effect! => node_modules/tailwindcss/index.css:
// --animate-pulse8: pulse 6s cubic-bezier(0.4, 0.2, 0.6, 1) infinite;
<div
key={i}
className="absolute rounded-full bg-teal-600 animate-pulse8"
@@ -187,7 +189,7 @@ const GameScreen = () => {
{sortedPlayers.map((player, index) => (
<div
key={player.id}
className="flex items-center mb-3 p-2 bg-gray-900 rounded-lg hover:bg-gray-800 transition-colors"
className="flex items-center mb-3 p-2 bg-gray-900 rounded-lg hover:bg-gray-700 transition-colors"
>
<div
className={`w-8 h-8 ${player.color} rounded-full mr-3 flex items-center justify-center text-white text-sm font-bold shadow-md`}
@@ -222,8 +224,31 @@ const GameScreen = () => {
{/* Dice Container */}
<div className="bg-gray-800 rounded-xl p-4 shadow-lg border border-teal-700 text-center">
<h2 className="text-xl font-semibold mb-3 text-teal-300">Dobókocka</h2>
<p className="text-gray-300 text-sm mb-4">Kattints a kockára dobáshoz!</p>
<Dice onRoll={handleDiceRoll} />
<p className="text-gray-300 text-sm mb-4">
Kattints a kockára dobáshoz vagy válassz egy számot az alábbiból!
</p>
{/* Dropdown to select number 1-6 (triggers animated roll to that number) */}
<div className="mb-3">
<select
value={selectedDice ?? ""}
onChange={(e) => {
const v = e.target.value ? Number(e.target.value) : null
setSelectedDice(v)
}}
className="bg-gray-900 text-gray-200 rounded-md p-2 border border-gray-700"
>
<option value="">Válassz számot...</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select>
</div>
<Dice onRoll={handleDiceRoll} selectedValue={selectedDice} />
</div>
</div>
</div>
@@ -1,46 +0,0 @@
// src/pages/Home/Home.jsx
// Régi PlayMenu-s oldal, "Home" néven
import { useState, useEffect } from "react"
import { useNavigate } from "react-router-dom"
import Navbar from "../../components/Navbar/Navbar"
import Footer from "../../components/Footer/Footer.jsx"
import Background from "../../assets/backgrounds/Background.jsx"
import PlayMenu from "../../components/Landingpage/PlayMenu.jsx"
export default function Home() {
const navigate = useNavigate()
useEffect(() => {
const username = localStorage.getItem("username")
const authLevel = localStorage.getItem("authLevel")
if (!username || !authLevel) {
navigate("/login")
}
}, [navigate])
// Dummy callbackok és user példa
const handleJoinGame = (code) => {
alert(`Csatlakozás játékhoz: ${code}`)
}
const handleCreateGame = () => {
alert("Új játék létrehozása")
}
const user = { name: localStorage.getItem("username") }
return (
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">
<div className="fixed inset-0 -z-10 pointer-events-none">
<Background />
</div>
<div className="fixed top-0 left-0 right-0 z-30">
<Navbar />
</div>
<main className="flex-1 flex flex-col items-center justify-start py-15 min-h-0 mt-[64px]">
<PlayMenu onJoinGame={handleJoinGame} onCreateGame={handleCreateGame} user={user} />
{/* Ide jöhetnek további szekciók, ha szeretnél még tartalmat */}
</main>
<Footer />
</div>
)
}
@@ -1,13 +1,17 @@
// src/pages/Home/Home.jsx
// Régi PlayMenu-s oldal, "Home" néven
import { useState } from "react"
import Navbar from "../../components/Navbar/Navbar.jsx"
import { useEffect } from "react"
import useRequireAuth from "../../hooks/useRequireAuth"
import Navbar from "../../components/Navbar/Navbar"
import Footer from "../../components/Footer/Footer.jsx"
import Background from "../../assets/backgrounds/Background.jsx"
import PlayMenu from "../../components/Landingpage/PlayMenu.jsx"
export default function Home() {
// a hook inicializálja a user-t a localStorage-ból és visszaadja a state-et + settert
const [user, setUser] = useRequireAuth({ redirect: false }) // no redirect on unauthenticated visitors
// Dummy callbackok és user példa
const handleJoinGame = (code) => {
alert(`Csatlakozás játékhoz: ${code}`)
@@ -15,7 +19,9 @@ export default function Home() {
const handleCreateGame = () => {
alert("Új játék létrehozása")
}
const user = { name: "Teszt Elek" }
const userObj = { name: user }
// ha szükséges a user módosítása máshol: setUser("újnév") automatikusan menti localStorage-be
return (
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">
@@ -25,8 +31,13 @@ export default function Home() {
<div className="fixed top-0 left-0 right-0 z-30">
<Navbar />
</div>
<main className="flex-1 flex flex-col items-center justify-start py-15 min-h-0 mt-[64px]">
<PlayMenu onJoinGame={handleJoinGame} onCreateGame={handleCreateGame} user={user} />
<main className="flex-1 min-h-[calc(100vh-64px)] flex mt-[64px] flex-col items-center justify-center">
<PlayMenu
onJoinGame={handleJoinGame}
onCreateGame={handleCreateGame}
user={userObj}
setUser={setUser}
/>
{/* Ide jöhetnek további szekciók, ha szeretnél még tartalmat */}
</main>
<Footer />
@@ -19,6 +19,10 @@ export default function LandingPageMain() {
navigate("/register");
};
const handleNavigateToGame = () => {
navigate("/home");
};
return (
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">
<div className="fixed inset-0 -z-10 pointer-events-none">
@@ -28,7 +32,7 @@ export default function LandingPageMain() {
<Navbar />
</div>
<main className="flex-1 flex flex-col items-center justify-start py-15 min-h-0 mt-[64px]">
<LandingPage onNavigateToPlay={handleNavigateToPlay} onNavigateToAuth={handleNavigateToAuth} />
<LandingPage onNavigateToPlay={handleNavigateToPlay} onNavigateToAuth={handleNavigateToAuth} onNavigateToGame={handleNavigateToGame} />
</main>
<Footer />
</div>
@@ -0,0 +1,96 @@
import React, { useEffect, useRef, useState } from "react"
import { useNavigate, useLocation } from "react-router-dom"
import Navbar from "../../components/Navbar/Navbar"
import Background from "../../assets/backgrounds/Background.jsx"
import useRequireAuth from "../../hooks/useRequireAuth"
const Lobby = () => {
const [visible, setVisible] = useState(false)
const sectionRef = useRef(null)
const navigate = useNavigate()
const location = useLocation()
const [user, setUser] = useRequireAuth()
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) setVisible(true)
},
{ threshold: 0.3 }
)
if (sectionRef.current) observer.observe(sectionRef.current)
return () => observer.disconnect()
}, [])
const handleExit = () => {
if (window.confirm("Biztosan ki szeretnél lépni a lobbyból?")) {
navigate("/home")
}
}
const getInitials = (name) => {
return name
.split(" ")
.map((n) => n[0])
.join("")
.slice(0, 2)
.toUpperCase()
}
return (
<div className="flex flex-col min-h-screen overflow-y-auto relative">
<div className="fixed top-0 left-0 w-full h-full -z-10">
<Background />
</div>
<div className="fixed top-0 left-0 right-0 z-30">
<Navbar />
</div>
<main className="flex-grow text-white px-6 pt-16 mt-0 mb-20 flex items-center justify-center">
<section
ref={sectionRef}
className={`w-full max-w-3xl rounded-2xl p-8 md:p-10 transition-all duration-1000 ease-out backdrop-blur-md shadow-2xl ${
visible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-10"
}`}
style={{ background: "rgba(0,0,0,0.25)" }}
>
<h1 className="text-4xl md:text-5xl font-extrabold text-green-300 mb-4 text-center tracking-wide drop-shadow-lg">
{user} Lobby-ja
</h1>
<p className="text-lg text-zinc-300 mb-8 text-center">
Játékosok, akik csatlakoztak ehhez a szobához:
</p>
<div className="bg-zinc-800/90 rounded-xl shadow-lg p-6 mb-8">
<ul className="flex flex-col gap-4">
<li className="bg-zinc-700 py-3 px-4 rounded-xl text-green-400 font-semibold flex items-center gap-4 shadow hover:shadow-green-500/20 transition">
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold"
style={{ background: "rgba(34,197,94,0.12)", color: "var(--color-mint)" }}
>
{getInitials(user)}
</div>
<span className="text-white text-lg">{user}</span>
</li>
</ul>
</div>
<div className="flex justify-center">
<button
onClick={handleExit}
className="bg-gradient-to-r from-green-700 to-green-500 hover:from-green-600 hover:to-green-400 text-white px-8 py-3 rounded-xl font-semibold shadow-lg hover:shadow-green-400/30 transition-transform transform hover:scale-105"
>
Kilépés
</button>
</div>
</section>
</main>
</div>
)
}
export default Lobby
@@ -4,8 +4,11 @@ import Navbar from "../../components/Navbar/Navbar.jsx"
import Footer from "../../components/Footer/Footer.jsx"
import Background from "../../assets/backgrounds/Background.jsx"
import { getUserStats } from "../../api/userApi.js"
import useRequireAuth from "../../hooks/useRequireAuth.jsx"
export default function Reports() {
const [username] = useRequireAuth({ key: "username", redirectTo: "/login" })
return (
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">
{/* Háttér */}
@@ -24,9 +27,8 @@ export default function Reports() {
{/* Fejléc */}
<div className="text-center mb-8">
<h2 className="text-3xl font-bold text-white">Játék Riportok</h2>
<p className="text-gray-300 mt-2">
Áttekintés a legutóbbi játékokról és statisztikákról
</p>
<p className="text-gray-300 mt-2">Áttekintés a legutóbbi játékokról és statisztikákról</p>
{username && <p className="text-sm text-gray-400 mt-1">Bejelentkezett: {username}</p>}
</div>
{/* Statisztikai kártyák */}
+99 -67
View File
@@ -1,74 +1,103 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef } from "react"
import { dotPositions } from "./diceDotPositions"
const Dice = ({ onRoll }) => {
const [diceValue, setDiceValue] = useState(1);
const [isRolling, setIsRolling] = useState(false);
const animationRef = useRef(null);
const rollTimeoutRef = useRef(null);
const Dice = ({ onRoll, selectedValue }) => {
const [diceValue, setDiceValue] = useState(1)
const [isRolling, setIsRolling] = useState(false)
const animationRef = useRef(null)
const rollTimeoutRef = useRef(null)
const diceFaces = [
[<div key="center" className="dice-dot"></div>],
[<div key="top-left" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot"></div>],
[<div key="top-left" className="dice-dot"></div>, <div key="center" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot"></div>],
[<div key="top-left" className="dice-dot"></div>, <div key="top-right" className="dice-dot"></div>,
<div key="bottom-left" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot"></div>],
[<div key="top-left" className="dice-dot"></div>, <div key="top-right" className="dice-dot"></div>,
<div key="center" className="dice-dot"></div>,
<div key="bottom-left" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot"></div>],
[<div key="top-left" className="dice-dot"></div>, <div key="top-right" className="dice-dot"></div>,
<div key="middle-left" className="dice-dot"></div>, <div key="middle-right" className="dice-dot"></div>,
<div key="bottom-left" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot"></div>]
];
[<div key="center" className="dice-dot" style={dotPositions.center}></div>],
[
<div key="top-left" className="dice-dot" style={dotPositions.topLeft}></div>,
<div key="bottom-right" className="dice-dot" style={dotPositions.bottomRight}></div>,
],
[
<div key="top-left" className="dice-dot" style={dotPositions.topLeft}></div>,
<div key="center" className="dice-dot" style={dotPositions.center}></div>,
<div key="bottom-right" className="dice-dot" style={dotPositions.bottomRight}></div>,
],
[
<div key="top-left" className="dice-dot" style={dotPositions.topLeft}></div>,
<div key="top-right" className="dice-dot" style={dotPositions.topRight}></div>,
<div key="bottom-left" className="dice-dot" style={dotPositions.bottomLeft}></div>,
<div key="bottom-right" className="dice-dot" style={dotPositions.bottomRight}></div>,
],
[
<div key="top-left" className="dice-dot" style={dotPositions.topLeft}></div>,
<div key="top-right" className="dice-dot" style={dotPositions.topRight}></div>,
<div key="center" className="dice-dot" style={dotPositions.center}></div>,
<div key="bottom-left" className="dice-dot" style={dotPositions.bottomLeft}></div>,
<div key="bottom-right" className="dice-dot" style={dotPositions.bottomRight}></div>,
],
[
<div key="top-left" className="dice-dot" style={dotPositions.topLeft}></div>,
<div key="top-right" className="dice-dot" style={dotPositions.topRight}></div>,
<div key="middle-left" className="dice-dot" style={dotPositions.middleLeft}></div>,
<div key="middle-right" className="dice-dot" style={dotPositions.middleRight}></div>,
<div key="bottom-left" className="dice-dot" style={dotPositions.bottomLeft}></div>,
<div key="bottom-right" className="dice-dot" style={dotPositions.bottomRight}></div>,
],
]
useEffect(() => {
return () => {
if (animationRef.current) cancelAnimationFrame(animationRef.current);
if (rollTimeoutRef.current) clearTimeout(rollTimeoutRef.current);
};
}, []);
if (animationRef.current) cancelAnimationFrame(animationRef.current)
if (rollTimeoutRef.current) clearTimeout(rollTimeoutRef.current)
}
}, [])
const rollDice = () => {
if (isRolling) return;
// Helper that starts the rolling animation and finishes with targetValue if provided
const startRoll = (targetValue = null) => {
if (isRolling) return
setIsRolling(true);
setIsRolling(true)
let duration = 0;
const rollInterval = 80; // ms between dice face changes
const maxDuration = 1500; // total animation time
let duration = 0
const rollInterval = 80 // ms between dice face changes
const maxDuration = 1500 // total animation time
const rollAnimation = () => {
const randomValue = Math.floor(Math.random() * 6) + 1;
setDiceValue(randomValue);
const randomValue = Math.floor(Math.random() * 6) + 1
setDiceValue(randomValue)
duration += rollInterval;
duration += rollInterval
if (duration < maxDuration) {
// Speed effect: slow down towards the end
const nextInterval = rollInterval * (1 + (duration / maxDuration) * 2);
const nextInterval = rollInterval * (1 + (duration / maxDuration) * 2)
rollTimeoutRef.current = setTimeout(() => {
animationRef.current = requestAnimationFrame(rollAnimation);
}, nextInterval);
animationRef.current = requestAnimationFrame(rollAnimation)
}, nextInterval)
} else {
// Final roll
const finalValue = Math.floor(Math.random() * 6) + 1;
setDiceValue(finalValue);
setIsRolling(false);
// Final roll (use targetValue if provided)
const finalValue = targetValue != null ? Number(targetValue) : Math.floor(Math.random() * 6) + 1
setDiceValue(finalValue)
setIsRolling(false)
if (onRoll) onRoll(finalValue);
if (onRoll) onRoll(finalValue)
}
};
}
animationRef.current = requestAnimationFrame(rollAnimation);
};
animationRef.current = requestAnimationFrame(rollAnimation)
}
// Click to roll randomly
const rollDice = () => {
startRoll(null)
}
// If parent provides a selectedValue, animate to that value
useEffect(() => {
if (selectedValue != null) {
startRoll(Number(selectedValue))
}
}, [selectedValue])
return (
<div
className={`dice-container ${isRolling ? 'rolling' : ''}`}
onClick={rollDice}
>
<div className="dice">
{diceFaces[diceValue - 1]}
</div>
<div className={`dice-container ${isRolling ? "rolling" : ""}`} onClick={rollDice}>
<div className="dice">{diceFaces[diceValue - 1]}</div>
<style jsx>{`
.dice-container {
width: 80px;
@@ -98,7 +127,7 @@ const Dice = ({ onRoll }) => {
justify-content: center;
background-color: #e63946;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
padding: 8px;
}
@@ -107,11 +136,21 @@ const Dice = ({ onRoll }) => {
}
@keyframes roll {
0% { transform: rotateX(0deg) rotateY(0deg); }
25% { transform: rotateX(90deg) rotateY(45deg); }
50% { transform: rotateX(180deg) rotateY(90deg); }
75% { transform: rotateX(270deg) rotateY(135deg); }
100% { transform: rotateX(360deg) rotateY(180deg); }
0% {
transform: rotateX(0deg) rotateY(0deg);
}
25% {
transform: rotateX(90deg) rotateY(45deg);
}
50% {
transform: rotateX(180deg) rotateY(90deg);
}
75% {
transform: rotateX(270deg) rotateY(135deg);
}
100% {
transform: rotateX(360deg) rotateY(180deg);
}
}
.dice-dot {
@@ -120,20 +159,13 @@ const Dice = ({ onRoll }) => {
border-radius: 50%;
background-color: white;
position: absolute;
box-shadow: inset 0 0 3px rgba(0,0,0,0.3);
box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3);
}
/* Positioning dots */
.dice-dot:nth-child(1) { top: 50%; left: 50%; transform: translate(-50%, -50%); } /* Center */
.dice-dot:nth-child(2) { top: 20%; left: 20%; } /* Top-left */
.dice-dot:nth-child(3) { bottom: 20%; right: 20%; } /* Bottom-right */
.dice-dot:nth-child(4) { top: 20%; right: 20%; } /* Top-right */
.dice-dot:nth-child(5) { bottom: 20%; left: 20%; } /* Bottom-left */
.dice-dot:nth-child(6) { top: 50%; left: 20%; transform: translateY(-50%); } /* Middle-left */
.dice-dot:nth-child(7) { top: 50%; right: 20%; transform: translateY(-50%); } /* Middle-right */
/* removed :nth-child positioning — positions are provided inline from diceDotPositions.js */
`}</style>
</div>
);
};
)
}
export default Dice;
export default Dice
@@ -0,0 +1,9 @@
export const dotPositions = {
center: { top: "50%", left: "50%", transform: "translate(-50%, -50%)" },
topLeft: { top: "20%", left: "20%" },
bottomRight: { bottom: "20%", right: "20%" },
topRight: { top: "20%", right: "20%" },
bottomLeft: { bottom: "20%", left: "20%" },
middleLeft: { top: "50%", left: "20%", transform: "translateY(-50%)" },
middleRight: { top: "50%", right: "20%", transform: "translateY(-50%)" },
}