18 Commits

Author SHA1 Message Date
zsola03 3ad9ba3e3f kartyatorles popup 2025-10-22 14:24:24 +02:00
zsola03 825e9d1a08 hibak.txt feladatai 2025-10-22 09:30:09 +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
mategergely33 5194308f7c deckkezeles, es deckek eltarolasa 2025-10-20 17:26:27 +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
27 changed files with 886 additions and 445 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; type: number;
playedNumber: number; playedNumber: number;
ctype: number; ctype: number;
cardsCount: number;
} }
export interface DetailDeckDto { export interface DetailDeckDto {
@@ -9,6 +9,7 @@ export class DeckMapper {
type: deck.type, type: deck.type,
playedNumber: deck.playedNumber, playedNumber: deck.playedNumber,
ctype: deck.ctype, ctype: deck.ctype,
cardsCount: deck.cards.length,
}; };
} }
@@ -26,6 +27,13 @@ export class DeckMapper {
} }
static toShortDtoList(decks: DeckAggregate[]): ShortDeckDto[] { 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"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <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" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title> <title>SerpentRace</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+28 -20
View File
@@ -15,6 +15,10 @@ import About from "./pages/About/About"
import ScrollToTop from "./components/ScrollToTop" import ScrollToTop from "./components/ScrollToTop"
import GameScreen from "./pages/Game/GameScreen" import GameScreen from "./pages/Game/GameScreen"
import Reports from "./pages/Report/Reports" import Reports from "./pages/Report/Reports"
import Lobby from "./pages/Lobby/Lobby"
import { ToastConfig } from "./components/Toastify/toastifyServices" // ✅ fontos: named import, nem default!
function App() { function App() {
const [isMobile, setIsMobile] = useState(false) const [isMobile, setIsMobile] = useState(false)
@@ -43,27 +47,31 @@ function App() {
// } // }
return ( return (
<Router> <>
<Routes> <Router>
<Route path="/about" element={<About />} /> <Routes>
<Route path="/register" element={<AuthRegister />} /> <Route path="/about" element={<About />} />
<Route path="/login" element={<AuthLogin />} /> <Route path="/lobby" element={<Lobby />} />
<Route path="/verify-email" element={<EmailVerification />} /> <Route path="/register" element={<AuthRegister />} />
<Route path="/forgot-password" element={<ForgotPassword />} /> <Route path="/login" element={<AuthLogin />} />
<Route path="/reset-password" element={<ResetPassword />} /> <Route path="/verify-email" element={<EmailVerification />} />
<Route path="/test" element={<Test />} /> <Route path="/forgot-password" element={<ForgotPassword />} />
<Route path="/" element={<Landingpage />} /> <Route path="/reset-password" element={<ResetPassword />} />
<Route path="/home" element={<Home />} /> <Route path="/test" element={<Test />} />
<Route path="/decks" element={<DeckManagerPage />} /> <Route path="/" element={<Landingpage />} />
<Route path="/deck-creator" element={<DeckCreator />} /> <Route path="/home" element={<Home />} />
<Route path="/deck-creator/:deckId" element={<DeckCreator />} /> <Route path="/decks" element={<DeckManagerPage />} />
<Route path="/game" element={<GameScreen />} /> <Route path="/deck-creator" element={<DeckCreator />} />
<Route path="/companies" element={<CompanyHub />} /> <Route path="/deck-creator/:deckId" element={<DeckCreator />} />
<Route path="/report" element={<Reports />} /> <Route path="/game" element={<GameScreen />} />
<Route path="/companies" element={<CompanyHub />} />
<Route path="/report" element={<Reports />} />
</Routes>
</Router>
{/* Add more routes as needed */} {/* ✅ Toastify Container */}
</Routes> <ToastConfig />
</Router> </>
) )
} }
+10
View File
@@ -10,6 +10,16 @@ export const createDeck = async (deck) => {
} }
} }
// Get paginated decks (authenticated)
export const getDecksPage = async (from = 0, to = 49) => {
try {
const response = await apiClient.get(`/decks/page/${from}/${to}`)
return response.data
} catch (err) {
throw err
}
}
export default { export default {
createDeck createDeck
} }
+4 -14
View File
@@ -1,7 +1,7 @@
import axios from "axios" import axios from "axios"
export const API_CONFIG = { 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", wsURL: "http://localhost:3000",
timeout: 10000, timeout: 10000,
retryAttempts: 3, retryAttempts: 3,
@@ -12,9 +12,9 @@ export const apiClient = axios.create({
timeout: API_CONFIG.timeout, timeout: API_CONFIG.timeout,
withCredentials: true, // Important for cookie-based auth withCredentials: true, // Important for cookie-based auth
headers: { headers: {
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}); })
//login //login
export const login = async (username, password) => { 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 // Get current user's game statistics
export const getUserStats = async () => { export const getUserStats = async () => {
try { try {
@@ -7,6 +7,8 @@ import TaskCardEditor from "./TaskCardEditor.jsx"
import JokerCardEditor from "./JokerCardEditor.jsx" import JokerCardEditor from "./JokerCardEditor.jsx"
import LuckCardEditor from "./LuckCardEditor.jsx" import LuckCardEditor from "./LuckCardEditor.jsx"
import CardPreview from "./CardPreview.jsx" import CardPreview from "./CardPreview.jsx"
import { notifySuccess, notifyError,notifyWarning } from "../../components/Toastify/toastifyServices"
export default function CardEditor({ card, isCreating, cardType, onSave, onCancel }) { export default function CardEditor({ card, isCreating, cardType, onSave, onCancel }) {
const [cardData, setCardData] = useState(null) const [cardData, setCardData] = useState(null)
@@ -65,33 +67,24 @@ export default function CardEditor({ card, isCreating, cardType, onSave, onCance
} }
}, [card, isCreating, cardType]) }, [card, isCreating, cardType])
const handleSave = () => {
if (!cardData) return
// Validáció
if (!validateCard(cardData)) return
onSave(cardData)
}
const validateCard = (data) => { const validateCard = (data) => {
if (data.type === 'task') { if (data.type === 'task') {
if (!data.question && !data.statement) { if (!data.question && !data.statement) {
alert("Kérdés vagy állítás megadása kötelező!") notifyError("Kérdés vagy állítás megadása kötelező!")
return false return false
} }
if (data.subType === 'quiz' && data.options.some(opt => !opt.trim())) { if (data.subType === 'quiz' && data.options.some(opt => !opt.trim())) {
alert("Minden válaszlehetőséget ki kell tölteni!") notifyError("Minden válaszlehetőséget ki kell tölteni!")
return false return false
} }
} else if (data.type === 'joker') { } else if (data.type === 'joker') {
if (!data.text || !data.text.trim()) { if (!data.text || !data.text.trim()) {
alert("Joker kártya szövege nem lehet üres!") notifyError("Joker kártya szövege nem lehet üres!")
return false return false
} }
} else if (data.type === 'luck') { } else if (data.type === 'luck') {
if (!data.text || !data.text.trim()) { if (!data.text || !data.text.trim()) {
alert("Szerencse kártya szövege nem lehet üres!") notifyError("Szerencse kártya szövege nem lehet üres!")
return false return false
} }
} }
@@ -103,6 +96,19 @@ export default function CardEditor({ card, isCreating, cardType, onSave, onCance
setCardData(prev => prev ? { ...prev, ...updates } : null) setCardData(prev => prev ? { ...prev, ...updates } : null)
} }
const handleSave = () => {
if (!cardData) return
if (!validateCard(cardData)) return
try {
onSave(cardData)
notifySuccess('Kártya sikeresen mentve!')
} catch (error) {
notifyError('Hiba történt a kártya mentése során: ' + (error?.message || String(error)))
}
}
// Ha nincs kiválasztott kártya vagy új kártya létrehozás // Ha nincs kiválasztott kártya vagy új kártya létrehozás
if (!cardData) { if (!cardData) {
return ( return (
@@ -134,7 +140,7 @@ export default function CardEditor({ card, isCreating, cardType, onSave, onCance
</div> </div>
<div> <div>
<h2 className="text-xl font-bold text-[color:var(--color-text)]"> <h2 className="text-xl font-bold text-[color:var(--color-text)]">
{isCreating ? 'Új' : 'Szerkesztés'} {' '} {isCreating ? 'Új' : 'Szerkesztés'}{' '}
{cardData.type === 'task' && 'Feladat kártya'} {cardData.type === 'task' && 'Feladat kártya'}
{cardData.type === 'joker' && 'Joker kártya'} {cardData.type === 'joker' && 'Joker kártya'}
{cardData.type === 'luck' && 'Szerencse kártya'} {cardData.type === 'luck' && 'Szerencse kártya'}
@@ -168,12 +174,15 @@ export default function CardEditor({ card, isCreating, cardType, onSave, onCance
</button> </button>
<button <button
onClick={onCancel} onClick={() => {
className="flex items-center gap-2 px-4 py-2 rounded-xl bg-[color:var(--color-background)] hover:bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] transition-all duration-200" notifyWarning('Kártya készítés megszakítva')
> onCancel()
<FaTimes /> }}
Mégse className="flex items-center gap-2 px-4 py-2 rounded-xl bg-[color:var(--color-background)] hover:bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] transition-all duration-200"
</button> >
<FaTimes />
Mégse
</button>
<button <button
onClick={handleSave} onClick={handleSave}
@@ -189,12 +198,10 @@ export default function CardEditor({ card, isCreating, cardType, onSave, onCance
{/* Content */} {/* Content */}
<div className="flex-1 overflow-hidden"> <div className="flex-1 overflow-hidden">
{showPreview ? ( {showPreview ? (
/* Preview Mode */
<div className="h-full bg-[color:var(--color-background)] flex items-center justify-center p-6"> <div className="h-full bg-[color:var(--color-background)] flex items-center justify-center p-6">
<CardPreview card={cardData} /> <CardPreview card={cardData} />
</div> </div>
) : ( ) : (
/* Edit Mode */
<div className="h-full overflow-y-auto p-6"> <div className="h-full overflow-y-auto p-6">
{cardData.type === 'task' && ( {cardData.type === 'task' && (
<TaskCardEditor <TaskCardEditor
@@ -1,8 +1,18 @@
// src/components/DeckCreator/CardsList.jsx // src/components/DeckCreator/CardsList.jsx
// Bal oldali kártyák listája és új kártya létrehozás // Bal oldali kártyák listája és új kártya létrehozás
import React from "react" import React, { useState } from "react"
import { FaPlus, FaEdit, FaTrash, FaQuestionCircle, FaCheck, FaTimes, FaDice, FaTheaterMasks } from "react-icons/fa" import {
FaPlus,
FaEdit,
FaTrash,
FaQuestionCircle,
FaCheck,
FaTimes,
FaDice,
FaTheaterMasks
} from "react-icons/fa"
import { notifySuccess, notifyError } from "../../components/Toastify/toastifyServices"
const cardTypeIcons = { const cardTypeIcons = {
task: { icon: FaQuestionCircle, color: "var(--color-question)" }, task: { icon: FaQuestionCircle, color: "var(--color-question)" },
@@ -26,38 +36,51 @@ export default function CardsList({
isCreatingCard, isCreatingCard,
newCardType newCardType
}) { }) {
const [confirmingDelete, setConfirmingDelete] = useState(null)
const getCardPreview = (card) => { const getCardPreview = (card) => {
if (card.type === 'task') { if (card.type === "task") {
return card.question || card.statement || 'Új feladat kártya' return card.question || card.statement || "Új feladat kártya"
} }
if (card.type === 'joker') { if (card.type === "joker") {
return card.text || 'Új joker kártya' return card.text || "Új joker kártya"
} }
if (card.type === 'luck') { if (card.type === "luck") {
return card.text || 'Új szerencse kártya' return card.text || "Új szerencse kártya"
} }
return 'Ismeretlen kártya' return "Ismeretlen kártya"
} }
const getCardTypeLabel = (card) => { const getCardTypeLabel = (card) => {
if (card.type === 'task') { if (card.type === "task") {
if (card.subType) { if (card.subType) {
return cardSubTypeLabels[card.subType] || 'Feladat' return cardSubTypeLabels[card.subType] || "Feladat"
} }
return 'Feladat' return "Feladat"
} }
if (card.type === 'joker') { if (card.type === "joker") {
return 'Joker' return "Joker"
} }
if (card.type === 'luck') { if (card.type === "luck") {
return 'Szerencse' return "Szerencse"
} }
return 'Ismeretlen' return "Ismeretlen"
}
const handleConfirmDelete = () => {
if (confirmingDelete) {
onDeleteCard(confirmingDelete)
notifySuccess("Kártya sikeresen törölve a pakliból!")
setConfirmingDelete(null)
}
}
const handleCancelDelete = () => {
setConfirmingDelete(null)
} }
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full relative">
{/* Header */} {/* Header */}
<div className="p-4 border-b border-[color:var(--color-surface-selected)]"> <div className="p-4 border-b border-[color:var(--color-surface-selected)]">
<h2 className="text-lg font-bold text-[color:var(--color-text)] mb-4 flex items-center gap-2"> <h2 className="text-lg font-bold text-[color:var(--color-text)] mb-4 flex items-center gap-2">
@@ -74,7 +97,7 @@ export default function CardsList({
{/* Dropdown Menu */} {/* Dropdown Menu */}
<div className="absolute top-full left-0 right-0 mt-2 bg-[color:var(--color-card)] rounded-xl shadow-lg border border-[color:var(--color-surface-selected)] opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-10"> <div className="absolute top-full left-0 right-0 mt-2 bg-[color:var(--color-card)] rounded-xl shadow-lg border border-[color:var(--color-surface-selected)] opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-10">
<button <button
onClick={() => onCreateCard('task')} onClick={() => onCreateCard("task")}
className="w-full flex items-center gap-3 px-4 py-3 hover:bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] transition-colors duration-200 rounded-t-xl" className="w-full flex items-center gap-3 px-4 py-3 hover:bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] transition-colors duration-200 rounded-t-xl"
> >
<FaQuestionCircle className="text-[color:var(--color-question)]" /> <FaQuestionCircle className="text-[color:var(--color-question)]" />
@@ -82,7 +105,7 @@ export default function CardsList({
</button> </button>
<button <button
onClick={() => onCreateCard('joker')} onClick={() => onCreateCard("joker")}
className="w-full flex items-center gap-3 px-4 py-3 hover:bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] transition-colors duration-200" className="w-full flex items-center gap-3 px-4 py-3 hover:bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] transition-colors duration-200"
> >
<FaTheaterMasks className="text-[color:var(--color-fun)]" /> <FaTheaterMasks className="text-[color:var(--color-fun)]" />
@@ -90,7 +113,7 @@ export default function CardsList({
</button> </button>
<button <button
onClick={() => onCreateCard('luck')} onClick={() => onCreateCard("luck")}
className="w-full flex items-center gap-3 px-4 py-3 hover:bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] transition-colors duration-200 rounded-b-xl" className="w-full flex items-center gap-3 px-4 py-3 hover:bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] transition-colors duration-200 rounded-b-xl"
> >
<FaDice className="text-[color:var(--color-luck)]" /> <FaDice className="text-[color:var(--color-luck)]" />
@@ -115,7 +138,7 @@ export default function CardsList({
)} )}
<div> <div>
<div className="text-[color:var(--color-text)] font-medium"> <div className="text-[color:var(--color-text)] font-medium">
Új {newCardType === 'task' ? 'feladat' : newCardType === 'joker' ? 'joker' : 'szerencse'} kártya Új {newCardType === "task" ? "feladat" : newCardType === "joker" ? "joker" : "szerencse"} kártya
</div> </div>
<div className="text-[color:var(--color-text-muted)] text-sm"> <div className="text-[color:var(--color-text-muted)] text-sm">
Szerkesztés folyamatban... Szerkesztés folyamatban...
@@ -136,16 +159,20 @@ export default function CardsList({
onClick={() => onSelectCard(card)} onClick={() => onSelectCard(card)}
className={` className={`
p-4 rounded-xl border cursor-pointer transition-all duration-200 hover:scale-105 group p-4 rounded-xl border cursor-pointer transition-all duration-200 hover:scale-105 group
${isSelected ${
? 'bg-[color:var(--color-success)]/10 border-[color:var(--color-success)] shadow-lg' isSelected
: 'bg-[color:var(--color-background)]/50 border-[color:var(--color-surface-selected)] hover:bg-[color:var(--color-background)]/80' ? "bg-[color:var(--color-success)]/10 border-[color:var(--color-success)] shadow-lg"
: "bg-[color:var(--color-background)]/50 border-[color:var(--color-surface-selected)] hover:bg-[color:var(--color-background)]/80"
} }
`} `}
> >
{/* Card Header */} {/* Card Header */}
<div className="flex items-start justify-between gap-2 mb-3"> <div className="flex items-start justify-between gap-2 mb-3">
<div className="flex items-center gap-3 flex-1 min-w-0"> <div className="flex items-center gap-3 flex-1 min-w-0">
<div className="flex items-center justify-center w-10 h-10 rounded-full border-2" style={{ borderColor: cardIcon.color }}> <div
className="flex items-center justify-center w-10 h-10 rounded-full border-2"
style={{ borderColor: cardIcon.color }}
>
{React.createElement(cardIcon.icon, { {React.createElement(cardIcon.icon, {
style: { color: cardIcon.color }, style: { color: cardIcon.color },
className: "text-lg" className: "text-lg"
@@ -169,7 +196,7 @@ export default function CardsList({
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
onDeleteCard(card.id) setConfirmingDelete(card.id)
}} }}
className="p-1.5 rounded-lg bg-[color:var(--color-error)]/10 hover:bg-[color:var(--color-error)]/20 text-[color:var(--color-error)] transition-colors duration-200" className="p-1.5 rounded-lg bg-[color:var(--color-error)]/10 hover:bg-[color:var(--color-error)]/20 text-[color:var(--color-error)] transition-colors duration-200"
> >
@@ -183,10 +210,10 @@ export default function CardsList({
<div <div
className="text-[color:var(--color-text)] text-sm leading-relaxed" className="text-[color:var(--color-text)] text-sm leading-relaxed"
style={{ style={{
display: '-webkit-box', display: "-webkit-box",
WebkitLineClamp: 2, WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical', WebkitBoxOrient: "vertical",
overflow: 'hidden' overflow: "hidden"
}} }}
> >
{getCardPreview(card)} {getCardPreview(card)}
@@ -209,6 +236,34 @@ export default function CardsList({
)} )}
</div> </div>
{/* Confirm Delete Popup */}
{confirmingDelete && (
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
<div className="bg-white rounded-xl shadow-xl p-6 w-80 text-center animate-fadeIn">
<h3 className="text-lg font-semibold mb-4 text-gray-800">
Biztosan törölni szeretnéd?
</h3>
<p className="text-sm text-gray-600 mb-6">
Ez a művelet nem visszavonható.
</p>
<div className="flex justify-center gap-4">
<button
onClick={handleConfirmDelete}
className="bg-[color:var(--color-error)] text-white px-4 py-2 rounded-lg hover:bg-red-600 transition"
>
Igen
</button>
<button
onClick={handleCancelDelete}
className="bg-gray-200 px-4 py-2 rounded-lg hover:bg-gray-300 transition"
>
Mégse
</button>
</div>
</div>
</div>
)}
{/* Footer Stats */} {/* Footer Stats */}
<div className="p-4 border-t border-[color:var(--color-surface-selected)] bg-[color:var(--color-background)]/30"> <div className="p-4 border-t border-[color:var(--color-surface-selected)] bg-[color:var(--color-background)]/30">
<div className="text-center"> <div className="text-center">
@@ -218,9 +273,9 @@ export default function CardsList({
{cards.length > 0 && ( {cards.length > 0 && (
<div className="flex justify-center gap-4 mt-2 text-xs text-[color:var(--color-text-muted)]"> <div className="flex justify-center gap-4 mt-2 text-xs text-[color:var(--color-text-muted)]">
<span>📋 {cards.filter(c => c.type === 'task').length}</span> <span>📋 {cards.filter((c) => c.type === "task").length}</span>
<span>🃏 {cards.filter(c => c.type === 'joker').length}</span> <span>🃏 {cards.filter((c) => c.type === "joker").length}</span>
<span>🎲 {cards.filter(c => c.type === 'luck').length}</span> <span>🎲 {cards.filter((c) => c.type === "luck").length}</span>
</div> </div>
)} )}
</div> </div>
@@ -1,4 +1,4 @@
import React, { useState } from "react" import React, { useState, useEffect } from "react"
import { useNavigate } from "react-router-dom" import { useNavigate } from "react-router-dom"
import { import {
FaPlus, FaPlus,
@@ -20,19 +20,7 @@ const deckTypes = [
{ label: "Fun", color: "var(--color-fun)" }, { label: "Fun", color: "var(--color-fun)" },
] ]
const mockDecks = [ // initial state will be fetched from backend
// Just for visual mockup
{ id: 1, name: "Party Luck", type: "Luck", created: "2025-07-01", origin: "Vállalati" },
{ id: 2, name: "Quiz Night", type: "Question", created: "2025-07-02", origin: "Saját" },
{ id: 3, name: "Fun Times", type: "Fun", created: "2025-07-03", origin: "Vállalati" },
{ id: 4, name: "Corporate Challenge", type: "Question", created: "2025-07-04", origin: "Vállalati" },
{ id: 5, name: "Randomizer", type: "Luck", created: "2025-07-05", origin: "Saját" },
{ id: 6, name: "Afterwork luck", type: "Luck", created: "2025-07-06", origin: "Saját" },
{ id: 7, name: "Serpent Quiz", type: "Question", created: "2025-07-07", origin: "Vállalati" },
{ id: 8, name: "Green Fortune", type: "Luck", created: "2025-07-08", origin: "Vállalati" },
{ id: 9, name: "Team Builder", type: "Fun", created: "2025-07-09", origin: "Saját" },
{ id: 10, name: "Knowledge Race", type: "Question", created: "2025-07-10", origin: "Saját" },
]
const origins = ["Mind", "Vállalati", "Saját"] const origins = ["Mind", "Vállalati", "Saját"]
@@ -82,9 +70,39 @@ const DeckManager = () => {
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
const [showSortHelp, setShowSortHelp] = useState(false) const [showSortHelp, setShowSortHelp] = useState(false)
const [selectedDeck, setSelectedDeck] = useState(null) const [selectedDeck, setSelectedDeck] = useState(null)
const [decks, setDecks] = useState([])
const [loading, setLoading] = useState(false)
// Filter logic (mock) useEffect(() => {
let filteredDecks = mockDecks.filter((deck) => { let mounted = true
const load = async () => {
setLoading(true)
try {
const result = await import('../../api/deckApi').then(m => m.getDecksPage(0, 49))
if (!mounted) return
// map backend deck shape to UI shape
const mapped = result.decks.map(d => ({
id: d.id,
name: d.name,
type: d.type === 2 ? 'Question' : d.type === 1 ? 'Joker' : 'Luck',
created: d.creationdate ? new Date(d.creationdate).toLocaleDateString() : '',
origin: d.ctype === 2 ? 'Vállalati' : d.ctype === 0 ? 'Mind' : 'Saját',
raw: d
}))
setDecks(mapped)
} catch (err) {
console.error('Failed to load decks', err)
} finally {
setLoading(false)
}
}
load()
return () => { mounted = false }
}, [])
// Filter logic
const sourceDecks = decks
let filteredDecks = sourceDecks.filter((deck) => {
const typeMatch = selectedType === "All" || deck.type === selectedType const typeMatch = selectedType === "All" || deck.type === selectedType
const originMatch = selectedOrigin === "Mind" || deck.origin === selectedOrigin const originMatch = selectedOrigin === "Mind" || deck.origin === selectedOrigin
const searchMatch = !search || deck.name.toLowerCase().includes(search.toLowerCase()) const searchMatch = !search || deck.name.toLowerCase().includes(search.toLowerCase())
@@ -255,8 +273,14 @@ const DeckManager = () => {
<FaPlus style={{ color: "var(--color-success)" }} className="text-5xl mb-2" /> <FaPlus style={{ color: "var(--color-success)" }} className="text-5xl mb-2" />
<span className="text-[color:var(--color-text)] font-semibold">Új pakli létrehozása</span> <span className="text-[color:var(--color-text)] font-semibold">Új pakli létrehozása</span>
</div> </div>
{/* Existing Decks (Mockup) */} {/* Existing Decks (from backend) */}
{filteredDecks.map((deck) => { {loading && (
<div className="col-span-full text-center text-[color:var(--color-text-muted)]">Betöltés...</div>
)}
{!loading && filteredDecks.length === 0 && (
<div className="col-span-full text-center text-[color:var(--color-text-muted)]">Nincsenek mentett paklik.</div>
)}
{!loading && filteredDecks.map((deck) => {
const deckType = deckTypes.find((t) => t.label === deck.type) const deckType = deckTypes.find((t) => t.label === deck.type)
const borderColor = deckType ? deckType.color : "var(--color-success)" const borderColor = deckType ? deckType.color : "var(--color-success)"
return ( return (
@@ -34,13 +34,13 @@ const Footer = () => {
return ( return (
<footer <footer
ref={footerRef} 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" }} style={{ transformOrigin: "bottom center" }}
> >
<div className="max-w-6xl mx-auto flex flex-wrap justify-between items-start gap-8 px-4"> <div className="max-w-6xl mx-auto flex flex-wrap justify-between items-start gap-8 px-4">
{/* Logó */} {/* Logó */}
<div className="flex flex-col items-center"> <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} /> <Logo size={100} />
</a> </a>
<span className="font-extrabold text-xl mt-2 tracking-wide">SerpentRace</span> <span className="font-extrabold text-xl mt-2 tracking-wide">SerpentRace</span>
@@ -48,30 +48,30 @@ const Footer = () => {
{/* Oldalak */} {/* Oldalak */}
<div className="flex flex-col gap-1"> <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 Oldalak
</span> </span>
<a href="/" className="hover:underline hover:text-green-400"> <a href="/" className="hover:underline hover:text-green-500">
Főoldal Főoldal
</a> </a>
<a href="/about" className="hover:underline hover:text-green-400"> <a href="/about" className="hover:underline hover:text-green-500">
Rólunk Rólunk
</a> </a>
<a href="/contact" className="hover:underline hover:text-green-400"> <a href="/contact" className="hover:underline hover:text-green-500">
Kapcsolat Kapcsolat
</a> </a>
</div> </div>
{/* Közösség */} {/* Közösség */}
<div className="flex flex-col gap-1"> <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 Közösség
</span> </span>
<a <a
href="https://discord.gg/" href="https://discord.gg/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="hover:underline hover:text-green-400" className="hover:underline hover:text-green-500"
> >
Discord Discord
</a> </a>
@@ -79,7 +79,7 @@ const Footer = () => {
href="https://github.com/" href="https://github.com/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="hover:underline hover:text-green-400" className="hover:underline hover:text-green-500"
> >
GitHub GitHub
</a> </a>
@@ -87,11 +87,11 @@ const Footer = () => {
{/* Elérhetőség */} {/* Elérhetőség */}
<div className="flex flex-col gap-1"> <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 Elérhetőség
</span> </span>
<span className="opacity-80">Email: info@serpentrace.hu</span> <span className="opacity-85">Email: info@serpentrace.hu</span>
<span className="opacity-80">Telefon: +36 30 123 4567</span> <span className="opacity-85">Telefon: +36 30 123 4567</span>
</div> </div>
</div> </div>
@@ -7,7 +7,7 @@ export default function InputBox({ type, placeholder, value, onChange, width })
return ( return (
<input <input
type={type} 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} placeholder={placeholder}
value={value} value={value}
onChange={onChange} onChange={onChange}
@@ -5,8 +5,13 @@ import logoImg from "../../assets/pictures/Logo.png"
import ButtonGreen from "../Buttons/ButtonGreen.jsx" import ButtonGreen from "../Buttons/ButtonGreen.jsx"
import { FaUsers, FaPaintBrush, FaHeadset } from "react-icons/fa" import { FaUsers, FaPaintBrush, FaHeadset } from "react-icons/fa"
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { isAuthenticated } from "../../hooks/useRequireAuth" // <-- added import
import { useNavigate } from "react-router-dom" // <-- NEW
const LandingPage = ({ onNavigateToPlay, onNavigateToAuth }) => { const LandingPage = ({ onNavigateToPlay, onNavigateToAuth }) => {
const auth = isAuthenticated() // <-- check without redirect
const navigate = useNavigate() // <-- NEW
return ( return (
<div className="w-full"> <div className="w-full">
{/* Hero Section */} {/* Hero Section */}
@@ -55,8 +60,15 @@ const LandingPage = ({ onNavigateToPlay, onNavigateToAuth }) => {
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 1 }} transition={{ duration: 0.7, delay: 1 }}
> >
<ButtonGreen text="Bejelntekezés" onClick={onNavigateToPlay} width="w-60" /> {/* If not authenticated show Login/Register; if authenticated show Home button */}
<ButtonGreen text="Regisztráció" onClick={onNavigateToAuth} width="w-60" /> {!auth ? (
<>
<ButtonGreen text="Bejelentkezés" onClick={onNavigateToPlay} width="w-60" />
<ButtonGreen text="Regisztráció" onClick={onNavigateToAuth} width="w-60" />
</>
) : (
<ButtonGreen text="Játék" onClick={() => navigate("/home")} width="w-60" />
)}
</motion.div> </motion.div>
</div> </div>
</motion.section> </motion.section>
@@ -4,9 +4,11 @@ import logoImg from "../../assets/pictures/Logo.png" // <-- EZT ADD HOZZÁ
import ButtonDark from "../Buttons/ButtonDark.jsx" import ButtonDark from "../Buttons/ButtonDark.jsx"
import InputBoxDark from "../Inputs/InputBoxDark.jsx" import InputBoxDark from "../Inputs/InputBoxDark.jsx"
const PlayMenu = ({ onJoinGame, onCreateGame, user }) => { const PlayMenu = ({ onJoinGame, onCreateGame, user, setUser }) => {
const [joinCode, setJoinCode] = useState("") const [joinCode, setJoinCode] = useState("")
const [error, setError] = 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) // gyors username kiolvasás (ha a parent objektum user={ { name: ... } } küldi)
const username = user?.name ?? null const username = user?.name ?? null
@@ -24,7 +26,7 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user }) => {
onCreateGame() onCreateGame()
} }
// egyszerű segéd az inicialishez // egyszerű segéd a kezdobetűk kinyerésére
const initials = username const initials = username
? username ? username
.split(" ") .split(" ")
@@ -64,32 +66,40 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user }) => {
style={{ background: "rgba(0,0,0,0.15)", backdropFilter: "blur(6px)" }} style={{ background: "rgba(0,0,0,0.15)", backdropFilter: "blur(6px)" }}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> {username ? (
{username ? ( <div className="flex items-center gap-3">
<div className="flex items-center gap-3"> <div
{/* opcionális kis info ikon helye, ha később kell */} className="w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold"
<div style={{ background: "rgba(34,197,94,0.12)", color: "var(--color-mint)" }}
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}
>
{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>
) : ( <div className="text-[32px]" style={{ color: "var(--color-muted, #cbd5e1)" }}>
<div className="text-sm text-gray-300">Nincs bejelentkezve</div> <span className="font-medium" style={{ color: "var(--color-text, #fff)" }}>
)} {username}
</div> </span>
{/* opcionális kis info ikon helye, ha később kell */} </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>
<div> <div>
<h2 className="text-xl font-semibold mb-3 text-text">Csatlakozás játékhoz</h2> <h2 className="font-semibold mb-3 text-text">Csatlakozás játékhoz</h2>
<div className={`${error ? "border border-error rounded-lg p-2" : ""}`}> <div className={`${error ? "border border-error rounded-lg p-2" : ""}`}>
<InputBoxDark <InputBoxDark
type="text" type="text"
@@ -104,15 +114,16 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user }) => {
<ButtonDark text="Csatlakozás" type="button" onClick={handleJoin} width="w-full" /> <ButtonDark text="Csatlakozás" type="button" onClick={handleJoin} width="w-full" />
</div> </div>
</div> </div>
{username ? (
<div className="border-t border-white/10 pt-4"> <div className="border-t border-white/10 pt-4">
{username && ( {username && (
<div> <div>
<h3 className="text-lg font-semibold mb-3 text-text">Új játék létrehozása</h3> <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" /> <ButtonDark text="Játék létrehozása" type="button" onClick={handleCreate} width="w-full" />
</div> </div>
)} )}
</div> </div>
) : null}
</div> </div>
</div> </div>
</section> </section>
@@ -3,6 +3,8 @@ import Logo from "../../assets/pictures/Logo"
import { Link, useNavigate } from "react-router-dom" 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 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 Navbar = () => {
const [menuOpen, setMenuOpen] = useState(false) const [menuOpen, setMenuOpen] = useState(false)
@@ -35,26 +37,31 @@ const Navbar = () => {
{isLoggedIn ? ( {isLoggedIn ? (
<> <>
<Link to="/home" className={navLinkClass}> <Link to="/home" className={navLinkClass}>
Home Főoldal
</Link> </Link>
<Link to="/decks" className={navLinkClass}> <Link to="/decks" className={navLinkClass}>
Decks Paklik
</Link> </Link>
<Link to="/report" className={navLinkClass}> <Link to="/report" className={navLinkClass}>
Stats Statisztika
</Link> </Link>
</> </>
) : ( ) : (
<Link to="/" className={navLinkClass}> <Link to="/" className={navLinkClass}>
Home Főoldal
</Link> </Link>
)} )}
<Link to="/about" className={navLinkClass}> <Link to="/about" className={navLinkClass}>
About Rólunk
</Link> </Link>
<Link to="/companies" className={navLinkClass}> <Link to="/companies" className={navLinkClass}>
Contact Kapcsolat
</Link> </Link>
{!isLoggedIn && (
<Link to="/home" className={navLinkClassPlay}>
Játék
</Link>
)}
{isLoggedIn && ( {isLoggedIn && (
<button <button
onClick={handleLogout} onClick={handleLogout}
@@ -113,22 +120,35 @@ const Navbar = () => {
<div className="md:hidden bg-emerald-600 px-2 pt-2 pb-3 space-y-1"> <div className="md:hidden bg-emerald-600 px-2 pt-2 pb-3 space-y-1">
{isLoggedIn ? ( {isLoggedIn ? (
<Link to="/home" className={navLinkClass}> <Link to="/home" className={navLinkClass}>
Home Főoldal
</Link> </Link>
) : ( ) : (
<Link to="/" className={navLinkClass}> <Link to="/" className={navLinkClass}>
Home Főoldal
</Link> </Link>
)} )}
<Link to="/leaderboard" className={navLinkClass}> <Link to="/leaderboard" className={navLinkClass}>
Leaderboard Ranglista
</Link> </Link>
<Link to="/about" className={navLinkClass}> <Link to="/about" className={navLinkClass}>
About Rólunk
</Link> </Link>
<Link to="/companies" className={navLinkClass}> <Link to="/companies" className={navLinkClass}>
Contact Kapcsolat
</Link> </Link>
{!isLoggedIn && (
<div className="px-2">
<button
onClick={() => {
setMenuOpen(false)
navigate("/home")
}}
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>
)}
{isLoggedIn && ( {isLoggedIn && (
<div className="flex justify-end px-2 pb-2"> <div className="flex justify-end px-2 pb-2">
<button <button
@@ -14,6 +14,10 @@ import {
export default function DeckInfoPopUp({ deck, onClose }) { export default function DeckInfoPopUp({ deck, onClose }) {
if (!deck) return null if (!deck) return null
// Debug: Log the deck structure to see what we're working with
console.log('Deck in popup:', deck)
console.log('Cards:', deck.cards)
// Scroll blokkolás amikor a popup nyitva van // Scroll blokkolás amikor a popup nyitva van
useEffect(() => { useEffect(() => {
// Scroll letiltása // Scroll letiltása
@@ -33,13 +37,25 @@ export default function DeckInfoPopUp({ deck, onClose }) {
const currentDeckType = deckTypes[deck.type] || { label: deck.type, color: "var(--color-success)" } const currentDeckType = deckTypes[deck.type] || { label: deck.type, color: "var(--color-success)" }
// Mock data - ezeket majd a backend adatokra cseréljük // Use real deck data with safe fallbacks
const creator = deck.creatorName || deck.creator || (deck.user && deck.user.name) || "Ismeretlen"
const privacy = deck.origin === "Vállalati" || deck.ctype === 1 || deck.privacy === 'public' ? "Publikus" : "Privát"
// Get data from raw if available
const rawData = deck.raw || deck
// Use played number from raw data for answers count
const questionsCount = rawData.cardCount || 0
const answersCount = rawData.playedNumber || 0
console.log('Calculated counts:', { questionsCount, answersCount, rawData })
const mockData = { const mockData = {
creator: "John Doe", ...deck,
privacy: deck.origin === "Vállalati" ? "Publikus" : "Privát", creator,
questionsCount: Math.floor(Math.random() * 50) + 10, privacy,
answersCount: Math.floor(Math.random() * 200) + 50, questionsCount,
...deck answersCount
} }
const formatDate = (dateString) => { const formatDate = (dateString) => {
@@ -0,0 +1,165 @@
import { toast, ToastContainer, Bounce } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
/**
* 🔧 Ezt csak egyszer kell betenni az App.jsx-be!
* <ToastConfig /> = az a komponens, ami kirendereli a toastokat
*/
export const ToastConfig = () => (
<ToastContainer
position="bottom-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick={false}
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="light"
transition={Bounce}
/>
);
/**
* 🦄 Alapértelmezett toast üzenet (semleges)
* notify("Üzenet szövege")
*/
export const notify = (message) => {
toast(message, {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
theme: "light",
transition: Bounce,
});
};
/**
* ✅ Sikeres művelethez
* notifySuccess("Sikeres mentés!")
*/
export const notifySuccess = (message) => {
toast.success(message, {
position: "bottom-right",
autoClose: 4000,
theme: "light",
transition: Bounce,
});
};
/**
* ❌ Hibás művelethez
* notifyError("Hiba történt a mentés során!")
*/
export const notifyError = (message) => {
toast.error(message, {
position: "bottom-right",
autoClose: 5000,
theme: "light",
transition: Bounce,
});
};
/**
* ️ Információs üzenethez
* notifyInfo("Friss adatok betöltve!")
*/
export const notifyInfo = (message) => {
toast.info(message, {
position: "bottom-right",
autoClose: 4000,
theme: "light",
transition: Bounce,
});
};
/**
* ⚠️ Figyelmeztetéshez
* notifyWarning("Figyelem! Nem mentett módosítások vannak!")
*/
export const notifyWarning = (message) => {
toast.warn(message, {
position: "bottom-right",
autoClose: 4000,
theme: "light",
transition: Bounce,
});
};
// import React, { useState } from "react";
// import { notifyWarning } from "../../components/Toastify/toastifyServices";
// function AuthLogin() {
// const [email, setEmail] = useState("");
// const [password, setPassword] = useState("");
// const handleLogin = async (e) => {
// e.preventDefault();
// // Példa jelszó ellenőrzés logikára:
// if (password !== "titkosjelszo123") {
// notifyWarning("⚠️ Hibás jelszó! Kérlek próbáld újra.");
// return;
// }
// // Ha jó a jelszó:
// notifySuccess("✅ Sikeres bejelentkezés!");
// };
// return (
// <form onSubmit={handleLogin} className="login-form">
// <input
// type="email"
// placeholder="Email cím"
// value={email}
// onChange={(e) => setEmail(e.target.value)}
// />
// <input
// type="password"
// placeholder="Jelszó"
// value={password}
// onChange={(e) => setPassword(e.target.value)}
// />
// <button type="submit">Bejelentkezés</button>
// </form>
// );
// }
// export default AuthLogin;
// meghivas
// import { toast } from "react-toastify";
// // 🔔 Alap toast
// export const notify = (msg) => toast(msg);
// // ✅ Sikeres üzenet
// export const notifySuccess = (msg) => toast.success(msg);
// // ⚠️ Figyelmeztetés
// export const notifyWarning = (msg) => toast.warning(msg);
// // ❌ Hiba
// export const notifyError = (msg) => toast.error(msg);
// // ️ Információ
// export const notifyInfo = (msg) => toast.info(msg);
// hasznalat
// import { notifyWarning } from "../../components/Toastify/toastifyServices";
// if (password !== "titkos") {
// notifyWarning("⚠️ Hibás jelszó!");
// }
@@ -11,8 +11,17 @@ export function requireAuthSync({ key = "username", redirectTo = "/login", repla
return true return true
} }
// Default hook: ad vissza egy [value, setValue] párt, szinkronizálja localStorage-t és átirányít, ha nincs érték // New: non-redirecting check for auth status
export default function useRequireAuth({ key = "username", redirectTo = "/login" } = {}) { 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 navigate = useNavigate()
const [value, setValue] = useState(() => { const [value, setValue] = useState(() => {
try { try {
@@ -22,12 +31,12 @@ export default function useRequireAuth({ key = "username", redirectTo = "/login"
} }
}) })
// Ha nincs érték, átirányítjuk (komponens mount-oláskor) // Ha nincs érték és redirect engedélyezve van, átirányítjuk (komponens mount-oláskor)
useEffect(() => { useEffect(() => {
if (!value) { if (!value && redirect) {
navigate(redirectTo) navigate(redirectTo)
} }
}, [navigate, value, redirectTo]) }, [navigate, value, redirectTo, redirect])
// Szinkronizáljuk a localStorage-t amikor a state változik // Szinkronizáljuk a localStorage-t amikor a state változik
useEffect(() => { useEffect(() => {
+4
View File
@@ -8,9 +8,13 @@
--color-eerie-black: #181d23; --color-eerie-black: #181d23;
--color-mint: #15803d; --color-mint: #15803d;
--color-mint-dark: #136636; --color-mint-dark: #136636;
--color-mint-darker: #11522b; --color-mint-darker: #11522b;
--color-mint-light: #16a34a;
--color-mint-lighter: #22c55e;
/* Gombok */ /* Gombok */
--color-button-primary: #16a34a; --color-button-primary: #16a34a;
--color-button-primary-hover: #15803d; --color-button-primary-hover: #15803d;
@@ -1,12 +1,11 @@
// src/pages/Auth/LoginForm.jsx // src/pages/Auth/LoginForm.jsx
// Bejelentkezési űrlap
import InputBox from "../../components/Inputs/InputBox" import InputBox from "../../components/Inputs/InputBox"
import Button from "../../components/Buttons/Button" import Button from "../../components/Buttons/Button"
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
import { useLocation, useNavigate } from "react-router-dom" import { useLocation, useNavigate } from "react-router-dom"
import { login } from "../../api/userApi" import { login } from "../../api/userApi"
import { FaArrowLeft } from "react-icons/fa"
export default function LoginForm() { export default function LoginForm() {
const [email, setEmail] = useState("") const [email, setEmail] = useState("")
@@ -32,23 +31,23 @@ export default function LoginForm() {
e.preventDefault() e.preventDefault()
setError("") setError("")
setShowErrorPopup(false) setShowErrorPopup(false)
if (!email || !password) { if (!email || !password) {
setError("Minden mező kitöltése kötelező.") setError("Minden mező kitöltése kötelező.")
setShowErrorPopup(true) setShowErrorPopup(true)
setTimeout(() => setShowErrorPopup(false), 2000) setTimeout(() => setShowErrorPopup(false), 2000)
return return
} }
if (!validateEmail(email)) { if (!validateEmail(email)) {
setError("Hibás email formátum.") setError("Hibás email formátum.")
setShowErrorPopup(true) setShowErrorPopup(true)
setTimeout(() => setShowErrorPopup(false), 2000) setTimeout(() => setShowErrorPopup(false), 2000)
return return
} }
// Backend API
login(email, password) login(email, password)
.then((response) => { .then((response) => {
console.log(response)
// Csak a response.status-t ellenőrizd!
if (response && response.status === 200) { if (response && response.status === 200) {
if (response.data && response.data.user) { if (response.data && response.data.user) {
localStorage.setItem("username", response.data.user.username) localStorage.setItem("username", response.data.user.username)
@@ -61,7 +60,7 @@ export default function LoginForm() {
setTimeout(() => setShowErrorPopup(false), 2000) setTimeout(() => setShowErrorPopup(false), 2000)
} }
}) })
.catch((error) => { .catch(() => {
setError("Hibás bejelentkezési adatok.") setError("Hibás bejelentkezési adatok.")
setShowErrorPopup(true) setShowErrorPopup(true)
setTimeout(() => setShowErrorPopup(false), 2000) setTimeout(() => setShowErrorPopup(false), 2000)
@@ -75,18 +74,35 @@ export default function LoginForm() {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
transition={{ duration: 0.25 }} transition={{ duration: 0.25 }}
className="relative flex flex-col items-center"
> >
<h2 className="text-4xl font-extrabold text-center mb-6 text-gray-800 tracking-wide">Bejelentkezés</h2> {/* 🔙 Vissza nyíl gomb — most pontosan a fehér box bal felső sarkában */}
<div
className="absolute -top-6 -left-6 flex items-center group cursor-pointer select-none"
onClick={() => navigate("/")}
>
<FaArrowLeft className="text-gray-700 text-xl transition-transform duration-300 group-hover:-translate-x-1" />
<span className="ml-2 text-gray-700 font-medium opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-300">
Vissza a főoldalra
</span>
</div>
<h2 className="text-4xl font-extrabold text-center mb-6 text-gray-800 tracking-wide">
Bejelentkezés
</h2>
{showSuccess && ( {showSuccess && (
<div className="fixed top-6 left-1/2 -translate-x-1/2 bg-green-500 text-white px-6 py-2 rounded shadow-lg z-50 text-center font-semibold transition-opacity duration-300"> <div className="fixed top-6 left-1/2 -translate-x-1/2 bg-green-500 text-white px-6 py-2 rounded shadow-lg z-50 text-center font-semibold transition-opacity duration-300">
Sikeres regisztráció! Az email ellenőrzése után be tudsz lépni. Sikeres regisztráció! Az email ellenőrzése után be tudsz lépni.
</div> </div>
)} )}
{showErrorPopup && error && ( {showErrorPopup && error && (
<div className="fixed top-6 left-1/2 -translate-x-1/2 bg-red-500 text-white px-6 py-2 rounded shadow-lg z-50 text-center font-semibold transition-opacity duration-300"> <div className="fixed top-6 left-1/2 -translate-x-1/2 bg-red-500 text-white px-6 py-2 rounded shadow-lg z-50 text-center font-semibold transition-opacity duration-300">
{error} {error}
</div> </div>
)} )}
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
<InputBox <InputBox
type="email" type="email"
@@ -1,12 +1,12 @@
// src/pages/Auth/RegisterForm.jsx // src/pages/Auth/RegisterForm.jsx
// Regisztrációs űrlap
import InputBox from "../../components/Inputs/InputBox" import InputBox from "../../components/Inputs/InputBox"
import Button from "../../components/Buttons/Button" import Button from "../../components/Buttons/Button"
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { useState } from "react" import { useState } from "react"
import { register } from "../../api/userApi" import { register } from "../../api/userApi"
import { useNavigate } from "react-router-dom" import { useNavigate, useLocation } from "react-router-dom"
import { ToastConfig } from "../../components/Toastify/toastifyServices"
import { FaArrowLeft } from "react-icons/fa"
export default function RegisterForm() { export default function RegisterForm() {
const [lastname, setLastname] = useState("") const [lastname, setLastname] = useState("")
@@ -19,6 +19,7 @@ export default function RegisterForm() {
const [error, setError] = useState("") const [error, setError] = useState("")
const [showErrorPopup, setShowErrorPopup] = useState(false) const [showErrorPopup, setShowErrorPopup] = useState(false)
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation()
function validateEmail(email) { function validateEmail(email) {
return /\S+@\S+\.\S+/.test(email) return /\S+@\S+\.\S+/.test(email)
@@ -45,28 +46,26 @@ export default function RegisterForm() {
setError("A jelszavak nem egyeznek.") setError("A jelszavak nem egyeznek.")
return return
} }
// Backend API
try { try {
const response = await register(username, email, password, firstname, lastname, phone) const response = await register(username, email, password, firstname, lastname, phone)
// Check for 201 Created status
if (response && response.status === 201) { if (response && response.status === 201) {
navigate("/login", { state: { success: true } }) ToastConfig("✅ Sikeres regisztráció!")
if (location.pathname === "/login") {
navigate("/login", { state: { success: true } })
window.location.reload()
} else {
navigate("/login", { state: { success: true } })
}
} else { } else {
let msg = "Sikertelen regisztráció." let msg = "Sikertelen regisztráció."
if (response && response.data && response.data.error) { if (response?.data?.error) msg = response.data.error
msg = response.data.error
}
setError(msg) setError(msg)
setShowErrorPopup(true) setShowErrorPopup(true)
setTimeout(() => setShowErrorPopup(false), 2000) setTimeout(() => setShowErrorPopup(false), 2000)
} }
} catch (err) { } catch (err) {
let msg = "Ismeretlen hiba történt." let msg = err?.response?.data?.error || err.message || "Ismeretlen hiba történt."
if (err.response && err.response.data && err.response.data.error) {
msg = err.response.data.error
} else if (err.message) {
msg = err.message
}
setError(msg) setError(msg)
setShowErrorPopup(true) setShowErrorPopup(true)
setTimeout(() => setShowErrorPopup(false), 2000) setTimeout(() => setShowErrorPopup(false), 2000)
@@ -80,56 +79,37 @@ export default function RegisterForm() {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
transition={{ duration: 0.25 }} transition={{ duration: 0.25 }}
className="relative flex flex-col items-center"
> >
<h2 className="text-4xl font-extrabold text-center mb-6 text-gray-800 tracking-wide">Regisztráció</h2> {/* 🔙 Vissza nyíl gomb ugyanott, mint a login oldalon */}
<div
className="absolute -top-2 -left-1 flex items-center group cursor-pointer select-none"
onClick={() => navigate("/")}
>
<FaArrowLeft className="text-gray-700 text-xl transition-transform duration-300 group-hover:-translate-x-1" />
<span className="ml-2 text-gray-700 font-medium opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-300">
Vissza a főoldalra
</span>
</div>
<h2 className="text-4xl font-extrabold text-center mb-6 text-gray-800 tracking-wide">
Regisztráció
</h2>
{showErrorPopup && error && ( {showErrorPopup && error && (
<div className="fixed top-6 left-1/2 -translate-x-1/2 bg-red-500 text-white px-6 py-2 rounded shadow-lg z-50 text-center font-semibold transition-opacity duration-300"> <div className="fixed top-6 left-1/2 -translate-x-1/2 bg-red-500 text-white px-6 py-2 rounded shadow-lg z-50 text-center font-semibold transition-opacity duration-300">
{error} {error}
</div> </div>
)} )}
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
<InputBox <InputBox type="text" placeholder="Vezetéknév" value={lastname} onChange={(e) => setLastname(e.target.value)} />
type="text" <InputBox type="text" placeholder="Keresztnév" value={firstname} onChange={(e) => setFirstname(e.target.value)} />
placeholder="Vezetéknév" <InputBox type="text" placeholder="Felhasználónév" value={username} onChange={(e) => setUsername(e.target.value)} />
value={lastname} <InputBox type="email" placeholder="Email cím" value={email} onChange={(e) => setEmail(e.target.value)} />
onChange={(e) => setLastname(e.target.value)} <InputBox type="phone" placeholder="Telefonszám" value={phone} onChange={(e) => setPhone(e.target.value)} />
/> <InputBox type="password" placeholder="Jelszó" value={password} onChange={(e) => setPassword(e.target.value)} />
<InputBox <InputBox type="password" placeholder="Jelszó megerősítése" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} />
type="text"
placeholder="Keresztnév"
value={firstname}
onChange={(e) => setFirstname(e.target.value)}
/>
<InputBox
type="text"
placeholder="Felhasználónév"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<InputBox
type="email"
placeholder="Email cím"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<InputBox
type="phone"
placeholder="Telefonszám"
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
<InputBox
type="password"
placeholder="Jelszó"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<InputBox
type="password"
placeholder="Jelszó megerősítése"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
<Button text="Regisztráció" type="submit" /> <Button text="Regisztráció" type="submit" />
</form> </form>
</motion.div> </motion.div>
@@ -8,6 +8,7 @@ import DeckHeader from "../../components/DeckCreator/DeckHeader.jsx"
import CardsList from "../../components/DeckCreator/CardsList.jsx" import CardsList from "../../components/DeckCreator/CardsList.jsx"
import CardEditor from "../../components/DeckCreator/CardEditor.jsx" import CardEditor from "../../components/DeckCreator/CardEditor.jsx"
import { createDeck } from '../../api/deckApi' import { createDeck } from '../../api/deckApi'
import { notifySuccess, notifyError } from "../../components/Toastify/toastifyServices"
export default function DeckCreator() { export default function DeckCreator() {
const { deckId } = useParams() // URL-ből deck ID (új deck esetén undefined) const { deckId } = useParams() // URL-ből deck ID (új deck esetén undefined)
@@ -31,10 +32,8 @@ export default function DeckCreator() {
// Betöltés (később API-ból) // Betöltés (később API-ból)
useEffect(() => { useEffect(() => {
if (deckId) { if (deckId) {
// TODO: Betöltés API-ból
loadDeck(deckId) loadDeck(deckId)
} else { } else {
// Új deck
setDeck({ setDeck({
id: null, id: null,
name: "Új Deck", name: "Új Deck",
@@ -98,19 +97,25 @@ export default function DeckCreator() {
} }
const saved = await createDeck(payload) const saved = await createDeck(payload)
setDeck(prev => ({ ...prev, id: saved.id ?? prev.id, creationdate: saved.creationdate ?? prev.creationdate, updatedate: saved.updatedate ?? prev.updatedate })) setDeck(prev => ({
...prev,
id: saved.id ?? prev.id,
creationdate: saved.creationdate ?? prev.creationdate,
updatedate: saved.updatedate ?? prev.updatedate
}))
console.log('Deck saved (backend):', saved) console.log('Deck saved (backend):', saved)
alert('Deck sikeresen mentve!') notifySuccess('Deck sikeresen mentve!')
} catch (error) { } catch (error) {
console.error('Mentési hiba:', error) console.error('Mentési hiba:', error)
alert('Hiba történt a mentés során: ' + (error?.response?.data?.error || error.message || String(error))) notifyError('Hiba történt a mentés során: ' + (error?.response?.data?.error || error.message || String(error)))
} }
} }
// 🔧 Itt korábban volt confirm(), de most eltávolítottuk
const handleBack = () => { const handleBack = () => {
if (confirm("Biztosan visszamész? A nem mentett változtatások elvesznek.")) { // Egyszerű visszalépés ha akarsz, később adhatunk hozzá saját modalt
navigate("/decks") navigate("/decks")
}
} }
const handleCreateCard = (cardType) => { const handleCreateCard = (cardType) => {
@@ -151,16 +156,15 @@ export default function DeckCreator() {
} }
} }
// 🔧 Itt is confirm() volt most a CardsList popupja kezeli a megerősítést
const handleDeleteCard = (cardId) => { const handleDeleteCard = (cardId) => {
if (confirm("Biztosan törlöd ezt a kártyát?")) { setDeck(prev => ({
setDeck(prev => ({ ...prev,
...prev, cards: prev.cards.filter(card => card.id !== cardId)
cards: prev.cards.filter(card => card.id !== cardId) }))
}))
if (selectedCard?.id === cardId) { if (selectedCard?.id === cardId) {
setSelectedCard(null) setSelectedCard(null)
}
} }
} }
@@ -66,12 +66,17 @@ const GameScreen = () => {
{ id: 3, name: "Fürtös", position: 68, score: 14, color: "bg-yellow-600", emoji: "😂" }, { 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 // Sort players by position in descending order
const sortedPlayers = [...players].sort((a, b) => b.position - a.position) const sortedPlayers = [...players].sort((a, b) => b.position - a.position)
// Handle dice roll // Handle dice roll completion
const handleDiceRoll = (value) => { const handleDiceRoll = (value) => {
console.log("Rolled:", 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 // You can add logic here to move the current player based on the dice value
} }
@@ -118,9 +123,6 @@ const GameScreen = () => {
{/* Háttér */} {/* Háttér */}
<div className="absolute w-full h-full opacity-10 pointer-events-none overflow-hidden"> <div className="absolute w-full h-full opacity-10 pointer-events-none overflow-hidden">
{[...Array(35)].map((_, i) => ( {[...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 <div
key={i} key={i}
className="absolute rounded-full bg-teal-600 animate-pulse8" className="absolute rounded-full bg-teal-600 animate-pulse8"
@@ -187,7 +189,7 @@ const GameScreen = () => {
{sortedPlayers.map((player, index) => ( {sortedPlayers.map((player, index) => (
<div <div
key={player.id} 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 <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`} 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 */} {/* Dice Container */}
<div className="bg-gray-800 rounded-xl p-4 shadow-lg border border-teal-700 text-center"> <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> <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> <p className="text-gray-300 text-sm mb-4">
<Dice onRoll={handleDiceRoll} /> 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> </div>
</div> </div>
@@ -10,7 +10,7 @@ import PlayMenu from "../../components/Landingpage/PlayMenu.jsx"
export default function Home() { export default function Home() {
// a hook inicializálja a user-t a localStorage-ból és visszaadja a state-et + settert // a hook inicializálja a user-t a localStorage-ból és visszaadja a state-et + settert
const [user, setUser] = useRequireAuth() const [user, setUser] = useRequireAuth({ redirect: false }) // no redirect on unauthenticated visitors
// Dummy callbackok és user példa // Dummy callbackok és user példa
const handleJoinGame = (code) => { const handleJoinGame = (code) => {
@@ -31,8 +31,13 @@ export default function Home() {
<div className="fixed top-0 left-0 right-0 z-30"> <div className="fixed top-0 left-0 right-0 z-30">
<Navbar /> <Navbar />
</div> </div>
<main className="flex-1 flex flex-col items-center justify-start py-15 min-h-0 mt-[64px]"> <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} /> <PlayMenu
onJoinGame={handleJoinGame}
onCreateGame={handleCreateGame}
user={userObj}
setUser={setUser}
/>
{/* Ide jöhetnek további szekciók, ha szeretnél még tartalmat */} {/* Ide jöhetnek további szekciók, ha szeretnél még tartalmat */}
</main> </main>
<Footer /> <Footer />
@@ -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
+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 Dice = ({ onRoll, selectedValue }) => {
const [diceValue, setDiceValue] = useState(1); const [diceValue, setDiceValue] = useState(1)
const [isRolling, setIsRolling] = useState(false); const [isRolling, setIsRolling] = useState(false)
const animationRef = useRef(null); const animationRef = useRef(null)
const rollTimeoutRef = useRef(null); const rollTimeoutRef = useRef(null)
const diceFaces = [ const diceFaces = [
[<div key="center" className="dice-dot"></div>], [<div key="center" className="dice-dot" style={dotPositions.center}></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" style={dotPositions.topLeft}></div>,
[<div key="top-left" className="dice-dot"></div>, <div key="top-right" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot" style={dotPositions.bottomRight}></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="top-left" className="dice-dot" style={dotPositions.topLeft}></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"></div>, <div key="top-right" className="dice-dot"></div>, <div key="bottom-right" className="dice-dot" style={dotPositions.bottomRight}></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="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(() => { useEffect(() => {
return () => { return () => {
if (animationRef.current) cancelAnimationFrame(animationRef.current); if (animationRef.current) cancelAnimationFrame(animationRef.current)
if (rollTimeoutRef.current) clearTimeout(rollTimeoutRef.current); if (rollTimeoutRef.current) clearTimeout(rollTimeoutRef.current)
}; }
}, []); }, [])
const rollDice = () => { // Helper that starts the rolling animation and finishes with targetValue if provided
if (isRolling) return; const startRoll = (targetValue = null) => {
if (isRolling) return
setIsRolling(true); setIsRolling(true)
let duration = 0; let duration = 0
const rollInterval = 80; // ms between dice face changes const rollInterval = 80 // ms between dice face changes
const maxDuration = 1500; // total animation time const maxDuration = 1500 // total animation time
const rollAnimation = () => { const rollAnimation = () => {
const randomValue = Math.floor(Math.random() * 6) + 1; const randomValue = Math.floor(Math.random() * 6) + 1
setDiceValue(randomValue); setDiceValue(randomValue)
duration += rollInterval; duration += rollInterval
if (duration < maxDuration) { if (duration < maxDuration) {
// Speed effect: slow down towards the end // Speed effect: slow down towards the end
const nextInterval = rollInterval * (1 + (duration / maxDuration) * 2); const nextInterval = rollInterval * (1 + (duration / maxDuration) * 2)
rollTimeoutRef.current = setTimeout(() => { rollTimeoutRef.current = setTimeout(() => {
animationRef.current = requestAnimationFrame(rollAnimation); animationRef.current = requestAnimationFrame(rollAnimation)
}, nextInterval); }, nextInterval)
} else { } else {
// Final roll // Final roll (use targetValue if provided)
const finalValue = Math.floor(Math.random() * 6) + 1; const finalValue = targetValue != null ? Number(targetValue) : Math.floor(Math.random() * 6) + 1
setDiceValue(finalValue); setDiceValue(finalValue)
setIsRolling(false); 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 ( return (
<div <div className={`dice-container ${isRolling ? "rolling" : ""}`} onClick={rollDice}>
className={`dice-container ${isRolling ? 'rolling' : ''}`} <div className="dice">{diceFaces[diceValue - 1]}</div>
onClick={rollDice}
>
<div className="dice">
{diceFaces[diceValue - 1]}
</div>
<style jsx>{` <style jsx>{`
.dice-container { .dice-container {
width: 80px; width: 80px;
@@ -98,7 +127,7 @@ const Dice = ({ onRoll }) => {
justify-content: center; justify-content: center;
background-color: #e63946; background-color: #e63946;
border-radius: 10px; 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; padding: 8px;
} }
@@ -107,11 +136,21 @@ const Dice = ({ onRoll }) => {
} }
@keyframes roll { @keyframes roll {
0% { transform: rotateX(0deg) rotateY(0deg); } 0% {
25% { transform: rotateX(90deg) rotateY(45deg); } transform: rotateX(0deg) rotateY(0deg);
50% { transform: rotateX(180deg) rotateY(90deg); } }
75% { transform: rotateX(270deg) rotateY(135deg); } 25% {
100% { transform: rotateX(360deg) rotateY(180deg); } 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 { .dice-dot {
@@ -120,20 +159,13 @@ const Dice = ({ onRoll }) => {
border-radius: 50%; border-radius: 50%;
background-color: white; background-color: white;
position: absolute; 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 */ /* removed :nth-child positioning — positions are provided inline from diceDotPositions.js */
.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 */
`}</style> `}</style>
</div> </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%)" },
}