Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 322059ace0 | |||
| 0ac5ead63a | |||
| 63533c0313 |
@@ -0,0 +1,96 @@
|
|||||||
|
# ⚡ Gyors Összefoglaló - Felesleges Adatok Tisztítás
|
||||||
|
|
||||||
|
## 🎯 Mi a probléma?
|
||||||
|
|
||||||
|
A frontend **10 felesleges mezőt** küld a backendnek minden kártya mentésekor.
|
||||||
|
|
||||||
|
## 📊 Számok
|
||||||
|
|
||||||
|
- **Felesleges deck mezők:** 1 db (`description`)
|
||||||
|
- **Felesleges kártya mezők:** 9 db
|
||||||
|
- **Payload csökkenés:** ~32-60%
|
||||||
|
- **Implementációs idő:** ~3-4 óra
|
||||||
|
|
||||||
|
## ✅ Használt mezők (BACKEND)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
name: "Pakli neve",
|
||||||
|
type: 2, // 0=LUCK, 1=JOKER, 2=QUESTION
|
||||||
|
ctype: 1, // 0=PUBLIC, 1=PRIVATE, 2=ORGANIZATION
|
||||||
|
cards: [
|
||||||
|
{
|
||||||
|
text: "Kérdés szövege",
|
||||||
|
type: 0, // CardType enum (0-4)
|
||||||
|
answer: "..." // TÍPUS-SPECIFIKUS formátum!
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ❌ Felesleges mezők (TÖRLENDŐ)
|
||||||
|
|
||||||
|
### Deck:
|
||||||
|
- `description` - nincs a backend sémában
|
||||||
|
|
||||||
|
### Kártya:
|
||||||
|
- `id` (frontend generált) - backend UUID-t használ
|
||||||
|
- `question` - duplikáció (`text` használandó)
|
||||||
|
- `statement` - duplikáció (`text` használandó)
|
||||||
|
- `options` - `answer` array-ben kell lennie
|
||||||
|
- `correctAnswer` - `answer` array-ben kell lennie
|
||||||
|
- `leftItems`, `rightItems`, `correctPairs` - `answer` array-ben kell lennie
|
||||||
|
- `acceptedAnswers` - `answer` array-ként kell lennie
|
||||||
|
- `hint` - nincs implementálva
|
||||||
|
|
||||||
|
## 🔄 Helyes answer formátumok
|
||||||
|
|
||||||
|
| Típus | answer formátum |
|
||||||
|
|-------|----------------|
|
||||||
|
| QUIZ (0) | `[{answer: "A", text: "...", correct: true}, ...]` |
|
||||||
|
| PAIRING (1) | `[{left: "...", right: "..."}, ...]` |
|
||||||
|
| OWN_ANSWER (2) | `["answer1", "answer2", ...]` |
|
||||||
|
| TRUE_FALSE (3) | `true` vagy `false` |
|
||||||
|
| CLOSER (4) | `{correct: 123, percent: 10}` |
|
||||||
|
|
||||||
|
## 🛠️ Következő lépések
|
||||||
|
|
||||||
|
1. ✅ Olvasd el: `FRONTEND_TO_BACKEND_DATA_CLEANUP.md`
|
||||||
|
2. 🔧 Implementáld: `cardBackendConverter.js` utility
|
||||||
|
3. 🔄 Módosítsd: `DeckCreator.jsx` mentés logikát
|
||||||
|
4. ✅ Teszteld: minden kártyatípust
|
||||||
|
|
||||||
|
## 📁 Kapcsolódó fájlok
|
||||||
|
|
||||||
|
- **Részletes dokumentáció:** `FRONTEND_TO_BACKEND_DATA_CLEANUP.md`
|
||||||
|
- **Módosítandó frontend:** `src/pages/DeckCreator/DeckCreator.jsx`
|
||||||
|
- **Backend referencia:** `SerpentRace_Backend/src/Application/Services/CardProcessingService.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Gyors példa:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ❌ ROSSZ (jelenleg)
|
||||||
|
{
|
||||||
|
text: "Kérdés",
|
||||||
|
question: "Kérdés", // Duplikáció
|
||||||
|
options: ["A", "B", "C"], // Felesleges
|
||||||
|
correctAnswer: 0 // Felesleges
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ JÓ (célállapot)
|
||||||
|
{
|
||||||
|
text: "Kérdés",
|
||||||
|
type: 0,
|
||||||
|
answer: [
|
||||||
|
{answer: "A", text: "A", correct: true},
|
||||||
|
{answer: "B", text: "B", correct: false},
|
||||||
|
{answer: "C", text: "C", correct: false}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
📖 **Teljes dokumentáció:** Lásd `FRONTEND_TO_BACKEND_DATA_CLEANUP.md`
|
||||||
@@ -0,0 +1,750 @@
|
|||||||
|
# Frontend → Backend Felesleges Adatok Dokumentáció
|
||||||
|
|
||||||
|
## 📋 Összefoglaló
|
||||||
|
|
||||||
|
Ez a dokumentum tartalmazza azokat a mezőket és adatokat, amiket a frontend küld a backendnek, de **nem szükségesek** vagy **nem használtak** a backend oldalon.
|
||||||
|
|
||||||
|
**🎯 Fő probléma:** A frontend sok felesleges mezőt küld, ahelyett hogy egyetlen `answer` mezőt használna típus-specifikus formátumban.
|
||||||
|
|
||||||
|
**💾 Adatmegtakarítás:** ~40-60% payload csökkentés várható a tisztítás után!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Gyors Összefoglaló Táblázat
|
||||||
|
|
||||||
|
| Mező | Használat | Cselekvés |
|
||||||
|
|------|-----------|-----------|
|
||||||
|
| `name` | ✅ Használt | Megtartani |
|
||||||
|
| `type` | ✅ Használt | Megtartani |
|
||||||
|
| `ctype` | ✅ Használt | Megtartani |
|
||||||
|
| `cards` | ✅ Használt | Megtartani |
|
||||||
|
| `description` | ❌ **Nincs a DB-ben** | **TÖRÖLNI** |
|
||||||
|
| | | |
|
||||||
|
| **Kártya mezők:** | | |
|
||||||
|
| `card.text` | ✅ Használt | Megtartani |
|
||||||
|
| `card.type` | ✅ Használt | Megtartani |
|
||||||
|
| `card.answer` | ✅ Használt | Megtartani (típus-specifikus!) |
|
||||||
|
| `card.consequence` | ✅ Használt (LUCK) | Megtartani |
|
||||||
|
| | | |
|
||||||
|
| `card.id` (frontend) | ❌ Nem releváns | **NE KÜLDJÜK** |
|
||||||
|
| `card.question` | ❌ Duplikáció | **TÖRÖLNI** (text-be) |
|
||||||
|
| `card.statement` | ❌ Duplikáció | **TÖRÖLNI** (text-be) |
|
||||||
|
| `card.options` | ❌ Felesleges | **KONVERTÁLNI** (answer-be) |
|
||||||
|
| `card.correctAnswer` | ❌ Felesleges | **KONVERTÁLNI** (answer-be) |
|
||||||
|
| `card.leftItems` | ❌ Felesleges | **KONVERTÁLNI** (answer-be) |
|
||||||
|
| `card.rightItems` | ❌ Felesleges | **KONVERTÁLNI** (answer-be) |
|
||||||
|
| `card.correctPairs` | ❌ Felesleges | **KONVERTÁLNI** (answer-be) |
|
||||||
|
| `card.acceptedAnswers` | ❌ Felesleges | **KONVERTÁLNI** (answer-be) |
|
||||||
|
| `card.hint` | ❌ Nincs implementálva | **TÖRÖLNI** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Deck Létrehozás/Frissítés (createDeck / updateDeck)
|
||||||
|
|
||||||
|
### Backend által HASZNÁLT mezők:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// CreateDeckCommand / UpdateDeckCommand
|
||||||
|
{
|
||||||
|
name: string, // ✅ HASZNÁLT - Pakli neve
|
||||||
|
type: number, // ✅ HASZNÁLT - 0=LUCK, 1=JOKER, 2=QUESTION
|
||||||
|
userid: string, // ✅ HASZNÁLT - Automatikusan hozzáadódik az authRequired middleware-ből
|
||||||
|
cards: any[], // ✅ HASZNÁLT - Kártyák tömbje
|
||||||
|
ctype?: number, // ✅ HASZNÁLT - 0=PUBLIC, 1=PRIVATE, 2=ORGANIZATION
|
||||||
|
state?: number, // ✅ HASZNÁLT - De csak admin állíthatja (0=ACTIVE, 1=SOFT_DELETE)
|
||||||
|
authLevel: number // ✅ HASZNÁLT - Automatikusan jön az auth middleware-ből
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend által KÜLDÖTT de FELESLEGES mezők:
|
||||||
|
|
||||||
|
#### 1. **`description` mező** - ❌ NEM HASZNÁLT
|
||||||
|
**Helyek:** `DeckCreator.jsx` (line ~100-110, ~170)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// FELESLEGES - Backend nem tárolja, nem használja
|
||||||
|
const payload = {
|
||||||
|
name: deck.name?.trim() || "Névtelen pakli",
|
||||||
|
type: typeMapping[deck.type] ?? 2,
|
||||||
|
ctype: ctypeMapping[deck.privacy] ?? 1,
|
||||||
|
cards: cleanedCards
|
||||||
|
// description: deck.description // ❌ Ez NINCS a backend sémában!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Megjegyzés a kódban (line ~171):**
|
||||||
|
```javascript
|
||||||
|
// Note: description field is not sent to backend as it's not supported yet
|
||||||
|
```
|
||||||
|
|
||||||
|
**Javaslat:**
|
||||||
|
- Ha a `description` soha nem lesz használva → töröljük a frontend state-ből
|
||||||
|
- Ha később implementálni fogjuk → adjuk hozzá a backend DeckAggregate entitáshoz először
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📇 Kártya Mezők (cards array)
|
||||||
|
|
||||||
|
### Backend Card Interface:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface Card {
|
||||||
|
text: string; // ✅ KÖTELEZŐ
|
||||||
|
type?: CardType; // ✅ OPCIONÁLIS - 0=QUIZ, 1=PAIRING, 2=OWN_ANSWER, 3=TRUE_FALSE, 4=CLOSER
|
||||||
|
answer?: string | null; // ✅ OPCIONÁLIS
|
||||||
|
consequence?: Consequence | null; // ✅ OPCIONÁLIS (csak LUCK kártyáknál)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend által KÜLDÖTT de ESETLEG FELESLEGES kártya mezők:
|
||||||
|
|
||||||
|
#### A. **Duplikált mezők** (ugyanaz az adat több néven):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// DeckCreator.jsx - cleanedCards mapping (line ~130-165)
|
||||||
|
|
||||||
|
// 1. TEXT mező duplikáció - ⚠️ REDUNDÁNS
|
||||||
|
cleanedCard.text = card.text || card.question || card.statement || ""
|
||||||
|
if (card.question !== undefined) cleanedCard.question = card.question // ❌ Felesleges?
|
||||||
|
if (card.statement !== undefined) cleanedCard.statement = card.statement // ❌ Felesleges?
|
||||||
|
|
||||||
|
// Backend csak a `text` mezőt használja!
|
||||||
|
// A `question` és `statement` valószínűleg NEM SZÜKSÉGESEK
|
||||||
|
```
|
||||||
|
|
||||||
|
**Megjegyzés:** A backend `Card` interfészben **nincs** `question` vagy `statement` mező, csak `text`.
|
||||||
|
|
||||||
|
#### B. **QUESTION típusú kártyák extra mezői** - ⚠️ ELLENŐRIZENDŐ
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Ezek a mezők a DeckCreator.jsx-ben kerülnek hozzáadásra (line ~145-155)
|
||||||
|
if (card.question !== undefined) cleanedCard.question = card.question
|
||||||
|
if (card.statement !== undefined) cleanedCard.statement = card.statement
|
||||||
|
if (card.options !== undefined) cleanedCard.options = card.options
|
||||||
|
if (card.correctAnswer !== undefined) cleanedCard.correctAnswer = card.correctAnswer
|
||||||
|
if (card.leftItems !== undefined) cleanedCard.leftItems = card.leftItems
|
||||||
|
if (card.rightItems !== undefined) cleanedCard.rightItems = card.rightItems
|
||||||
|
if (card.correctPairs !== undefined) cleanedCard.correctPairs = card.correctPairs
|
||||||
|
if (card.acceptedAnswers !== undefined) cleanedCard.acceptedAnswers = card.acceptedAnswers
|
||||||
|
if (card.hint !== undefined) cleanedCard.hint = card.hint
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backend Card interfész ezeket NEM tartalmazza:**
|
||||||
|
- ❌ `question` - Nincs a Card interface-ben
|
||||||
|
- ❌ `statement` - Nincs a Card interface-ben
|
||||||
|
- ❌ `options` - Nincs a Card interface-ben
|
||||||
|
- ❌ `correctAnswer` - Nincs a Card interface-ben
|
||||||
|
- ❌ `leftItems` - Nincs a Card interface-ben
|
||||||
|
- ❌ `rightItems` - Nincs a Card interface-ben
|
||||||
|
- ❌ `correctPairs` - Nincs a Card interface-ben
|
||||||
|
- ❌ `acceptedAnswers` - Nincs a Card interface-ben
|
||||||
|
- ❌ `hint` - Nincs a Card interface-ben
|
||||||
|
|
||||||
|
**KRITIKUS KÉRDÉS:**
|
||||||
|
- Ezek a mezők **JSON-ként tárolódnak** a `cards` mezőben?
|
||||||
|
- A backend TypeORM `@Column({ type: 'json' })` deklaráció miatt bármit el tud tárolni
|
||||||
|
- De a **Card interface** szerint csak `text`, `type`, `answer`, `consequence` mezőket használ
|
||||||
|
|
||||||
|
**Két lehetséges eset:**
|
||||||
|
|
||||||
|
1. **Ha a backend JSON mezőként tárolja de nem használja ezeket:**
|
||||||
|
- ❌ FELESLEGESEK - Adatbázis helyet pazarolnak
|
||||||
|
- Javaslat: Tisztítsuk meg a frontend-et, ne küldje őket
|
||||||
|
|
||||||
|
2. **Ha a backend valahol mégis használja (pl. game logic-ban):**
|
||||||
|
- ✅ SZÜKSÉGESEK - De akkor frissíteni kell a Card interface-t
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 Consequence mező - ✅ RENDBEN (de típus ellenőrzés szükséges)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// DeckCreator.jsx (line ~160-162)
|
||||||
|
if (deck.type === 'LUCK' && card.consequence) {
|
||||||
|
cleanedCard.consequence = card.consequence
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backend Consequence interface:**
|
||||||
|
```typescript
|
||||||
|
export interface Consequence {
|
||||||
|
type: ConsequenceType; // 0-5 közötti szám
|
||||||
|
value?: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Javaslat:** Ellenőrizni kell hogy a frontend mindig valid `ConsequenceType` enum értéket küld-e (0-5).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Részletes Backend vs Frontend Mapping
|
||||||
|
|
||||||
|
### Deck Level
|
||||||
|
|
||||||
|
| Frontend Mező | Backend Mező | Használat | Megjegyzés |
|
||||||
|
|--------------|-------------|----------|-----------|
|
||||||
|
| `deck.id` | `id` | ✅ Használt | UUID |
|
||||||
|
| `deck.name` | `name` | ✅ Használt | string (max 255) |
|
||||||
|
| `deck.type` | `type` | ✅ Használt | 0/1/2 (enum) |
|
||||||
|
| `deck.privacy` | `ctype` | ✅ Használt | 0/1/2 (enum) |
|
||||||
|
| `deck.description` | - | ❌ **NEM LÉTEZIK** | **FELESLEGES** |
|
||||||
|
| `deck.cards` | `cards` | ✅ Használt | JSON array |
|
||||||
|
| `deck.creationdate` | `creationdate` | ✅ Használt | Date (readonly) |
|
||||||
|
| `deck.updatedate` | `updateDate` | ✅ Használt | Date (readonly) |
|
||||||
|
|
||||||
|
### Card Level (QUESTION típusú kártyák)
|
||||||
|
|
||||||
|
| Frontend Mező | Backend Card Interface | Használat | Megjegyzés |
|
||||||
|
|--------------|----------------------|----------|-----------|
|
||||||
|
| `card.id` | - | ❌ **Lokális azonosító** | Csak frontend-en, backenden nem releváns |
|
||||||
|
| `card.text` | `text` | ✅ Használt | Fő szöveg |
|
||||||
|
| `card.question` | - | ❓ **Ellenőrizendő** | Lehet felesleges (text duplikáció?) |
|
||||||
|
| `card.statement` | - | ❓ **Ellenőrizendő** | Lehet felesleges (text duplikáció?) |
|
||||||
|
| `card.type` / `card.subType` | `type` | ✅ Használt | CardType enum (0-4) |
|
||||||
|
| `card.answer` | `answer` | ✅ Használt | String vagy null |
|
||||||
|
| `card.options` | - | ❓ **Ellenőrizendő** | Nincs a Card interface-ben |
|
||||||
|
| `card.correctAnswer` | - | ❓ **Ellenőrizendő** | Nincs a Card interface-ben |
|
||||||
|
| `card.leftItems` | - | ❓ **Ellenőrizendő** | Nincs a Card interface-ben |
|
||||||
|
| `card.rightItems` | - | ❓ **Ellenőrizendő** | Nincs a Card interface-ben |
|
||||||
|
| `card.correctPairs` | - | ❓ **Ellenőrizendő** | Nincs a Card interface-ben |
|
||||||
|
| `card.acceptedAnswers` | - | ❓ **Ellenőrizendő** | Nincs a Card interface-ben |
|
||||||
|
| `card.hint` | - | ❓ **Ellenőrizendő** | Nincs a Card interface-ben |
|
||||||
|
| `card.consequence` | `consequence` | ✅ Használt | Csak LUCK típusnál |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ BACKEND GAME LOGIC VIZSGÁLAT - ✅ KÉSZ
|
||||||
|
|
||||||
|
### 1. Kártya mezők tényleges használata - ELLENŐRIZVE ✅
|
||||||
|
|
||||||
|
**Ellenőrzött fájlok:**
|
||||||
|
- ✅ `SerpentRace_Backend/src/Application/Services/CardProcessingService.ts`
|
||||||
|
- ✅ `SerpentRace_Backend/src/Application/Services/CardDrawingService.ts`
|
||||||
|
|
||||||
|
**EREDMÉNY: A backend CSAK az `answer` mezőt használja!**
|
||||||
|
|
||||||
|
**Backend Card használat:**
|
||||||
|
```typescript
|
||||||
|
export interface Card {
|
||||||
|
text: string; // ✅ Kérdés szövege
|
||||||
|
type?: CardType; // ✅ Kártya típus (0-4)
|
||||||
|
answer?: string | null; // ✅ EGYETLEN valid mező a válaszokhoz!
|
||||||
|
consequence?: Consequence | null; // ✅ Csak LUCK kártyákhoz
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fontos:** A backend `answer` mező **típus-specifikus formátumú**:
|
||||||
|
|
||||||
|
1. **QUIZ (type: 0)** → `answer` = `QuizOption[]` array:
|
||||||
|
```typescript
|
||||||
|
answer: [
|
||||||
|
{ answer: "A", text: "First option", correct: false },
|
||||||
|
{ answer: "B", text: "Second option", correct: true },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **SENTENCE_PAIRING (type: 1)** → `answer` = Párosítás array:
|
||||||
|
```typescript
|
||||||
|
answer: [
|
||||||
|
{ left: "Apple", right: "Red" },
|
||||||
|
{ left: "Banana", right: "Yellow" }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **OWN_ANSWER (type: 2)** → `answer` = String vagy String array:
|
||||||
|
```typescript
|
||||||
|
answer: ["correct answer 1", "correct answer 2"]
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **TRUE_FALSE (type: 3)** → `answer` = Boolean:
|
||||||
|
```typescript
|
||||||
|
answer: true // vagy false
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **CLOSER (type: 4)** → `answer` = Object:
|
||||||
|
```typescript
|
||||||
|
answer: { correct: 42, percent: 10 }
|
||||||
|
```
|
||||||
|
|
||||||
|
**KÖVETKEZTETÉS:**
|
||||||
|
- ❌ **A frontend által küldött `options`, `correctAnswer`, `acceptedAnswers`, `leftItems`, `rightItems`, `correctPairs` mezők MIND FELESLEGESEK!**
|
||||||
|
- ✅ **Csak az `answer` mezőt kellene küldeni, megfelelő formátumban!**
|
||||||
|
|
||||||
|
### 2. Card Type Mapping
|
||||||
|
**Frontend:**
|
||||||
|
```javascript
|
||||||
|
const cardTypeMapping = {
|
||||||
|
'quiz': 0, // QUIZ
|
||||||
|
'pairing': 1, // SENTENCE_PAIRING
|
||||||
|
'text': 2, // OWN_ANSWER
|
||||||
|
'truefalse': 3, // TRUE_FALSE
|
||||||
|
'closer': 4 // CLOSER
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backend CardType enum:**
|
||||||
|
```typescript
|
||||||
|
export enum CardType {
|
||||||
|
QUIZ = 0,
|
||||||
|
SENTENCE_PAIRING = 1,
|
||||||
|
OWN_ANSWER = 2,
|
||||||
|
TRUE_FALSE = 3,
|
||||||
|
CLOSER = 4
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Ez HELYES** - A mapping megfelelő
|
||||||
|
|
||||||
|
### 3. Frontend kártya ID kezelés
|
||||||
|
```javascript
|
||||||
|
// DeckCreator.jsx (line ~242)
|
||||||
|
const updatedCard = {
|
||||||
|
...cardData,
|
||||||
|
id: isCreatingCard ? Date.now() : cardData.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line ~129
|
||||||
|
if (card.id) {
|
||||||
|
cleanedCard.id = card.id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Probléma:** A frontend `Date.now()` timestamp ID-kat generál, de a backend UUID-kat használ.
|
||||||
|
|
||||||
|
**Javaslat:**
|
||||||
|
- ❌ NE küldjük a frontend-generált `id`-t a backendnek
|
||||||
|
- A backend a create során generál UUID-t
|
||||||
|
- Update-nél a backend már ismeri az ID-t (URL parameter-ből jön)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 JAVASOLT TISZTÍTÁSOK
|
||||||
|
|
||||||
|
### Prioritás 1: BIZTOS FELESLEGESEK
|
||||||
|
|
||||||
|
1. **`description` mező törlése**
|
||||||
|
- Fájl: `DeckCreator.jsx`
|
||||||
|
- Sorok: ~20, ~40-45, ~100-105
|
||||||
|
- Töröljük a state-ből és ne küldjük a backendnek
|
||||||
|
|
||||||
|
2. **Frontend-generált kártya `id` ne menjen a backendre**
|
||||||
|
- Fájl: `DeckCreator.jsx`
|
||||||
|
- Sor: ~129
|
||||||
|
- Kommenteljük ki vagy töröljük: `if (card.id) cleanedCard.id = card.id`
|
||||||
|
|
||||||
|
### Prioritás 2: BIZONYÍTOTTAN FELESLEGESEK ✅
|
||||||
|
|
||||||
|
3. **Duplikált text mezők (`question`, `statement`)** - ❌ FELESLEGES
|
||||||
|
- A backend **csak `text`-et használ**
|
||||||
|
- Töröljük: `question` és `statement` mezők küldését
|
||||||
|
|
||||||
|
4. **QUESTION kártya részletes mezők - MIND FELESLEGESEK ❌**
|
||||||
|
- A backend GameService **NEM használja** ezeket:
|
||||||
|
- ❌ `options` - Felesleges (backend: `answer` array használ)
|
||||||
|
- ❌ `correctAnswer` - Felesleges (backend: `answer` array-ben `correct: true`)
|
||||||
|
- ❌ `leftItems` / `rightItems` / `correctPairs` - Felesleges (backend: `answer` array-ben `{left, right}` párok)
|
||||||
|
- ❌ `acceptedAnswers` - Felesleges (backend: `answer` string array)
|
||||||
|
- ❌ `hint` - Nincs implementálva a backenden
|
||||||
|
|
||||||
|
**HELYETTE:** Konvertáljuk ezeket megfelelő `answer` formátumra!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 HELYES KONVERZIÓ - Példák
|
||||||
|
|
||||||
|
### Jelenlegi (FELESLEGES mezőkkel):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ❌ ROSSZ - Felesleges mezők küldése
|
||||||
|
const cleanedCard = {
|
||||||
|
text: "Mi a főváros?",
|
||||||
|
type: 0, // QUIZ
|
||||||
|
question: "Mi a főváros?", // ❌ DUPLIKÁCIÓ
|
||||||
|
options: ["Budapest", "Berlin", "Prága"], // ❌ FELESLEGES
|
||||||
|
correctAnswer: 0 // ❌ FELESLEGES
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Helyes (Optimalizált):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ✅ JÓ - Csak szükséges mezők
|
||||||
|
const cleanedCard = {
|
||||||
|
text: "Mi a főváros?",
|
||||||
|
type: 0, // QUIZ
|
||||||
|
answer: [
|
||||||
|
{ answer: "A", text: "Budapest", correct: true },
|
||||||
|
{ answer: "B", text: "Berlin", correct: false },
|
||||||
|
{ answer: "C", text: "Prága", correct: false }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Konverziós Példák Típusonként:
|
||||||
|
|
||||||
|
#### 1. QUIZ (type: 0) - Feleletválasztós
|
||||||
|
|
||||||
|
**Frontend állapot:**
|
||||||
|
```javascript
|
||||||
|
card = {
|
||||||
|
subType: 'multiplechoice',
|
||||||
|
question: "Melyik a helyes?",
|
||||||
|
options: ["A válasz", "B válasz", "C válasz"],
|
||||||
|
correctAnswer: 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Helyes backend formátum:**
|
||||||
|
```javascript
|
||||||
|
cleanedCard = {
|
||||||
|
text: "Melyik a helyes?",
|
||||||
|
type: 0,
|
||||||
|
answer: [
|
||||||
|
{ answer: "A", text: "A válasz", correct: false },
|
||||||
|
{ answer: "B", text: "B válasz", correct: true }, // correctAnswer: 1
|
||||||
|
{ answer: "C", text: "C válasz", correct: false }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. SENTENCE_PAIRING (type: 1) - Párosítás
|
||||||
|
|
||||||
|
**Frontend állapot:**
|
||||||
|
```javascript
|
||||||
|
card = {
|
||||||
|
subType: 'matching',
|
||||||
|
question: "Párosítsd össze!",
|
||||||
|
leftItems: ["Alma", "Banán"],
|
||||||
|
rightItems: ["Piros", "Sárga"],
|
||||||
|
correctPairs: { 0: 0, 1: 1 } // leftItems[0] -> rightItems[0]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Helyes backend formátum:**
|
||||||
|
```javascript
|
||||||
|
cleanedCard = {
|
||||||
|
text: "Párosítsd össze!",
|
||||||
|
type: 1,
|
||||||
|
answer: [
|
||||||
|
{ left: "Alma", right: "Piros" },
|
||||||
|
{ left: "Banán", right: "Sárga" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. OWN_ANSWER (type: 2) - Szöveges válasz
|
||||||
|
|
||||||
|
**Frontend állapot:**
|
||||||
|
```javascript
|
||||||
|
card = {
|
||||||
|
subType: 'text',
|
||||||
|
question: "Mi a főváros?",
|
||||||
|
acceptedAnswers: ["Budapest", "budapest", "Bp"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Helyes backend formátum:**
|
||||||
|
```javascript
|
||||||
|
cleanedCard = {
|
||||||
|
text: "Mi a főváros?",
|
||||||
|
type: 2,
|
||||||
|
answer: ["Budapest", "budapest", "Bp"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. TRUE_FALSE (type: 3) - Igaz/Hamis
|
||||||
|
|
||||||
|
**Frontend állapot:**
|
||||||
|
```javascript
|
||||||
|
card = {
|
||||||
|
subType: 'truefalse',
|
||||||
|
statement: "A Föld lapos.",
|
||||||
|
correctAnswer: 1, // 0=Igaz, 1=Hamis
|
||||||
|
isTrue: false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Helyes backend formátum:**
|
||||||
|
```javascript
|
||||||
|
cleanedCard = {
|
||||||
|
text: "A Föld lapos.",
|
||||||
|
type: 3,
|
||||||
|
answer: false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. CLOSER (type: 4) - Tippelés
|
||||||
|
|
||||||
|
**Frontend állapot:**
|
||||||
|
```javascript
|
||||||
|
card = {
|
||||||
|
subType: 'closer',
|
||||||
|
question: "Hány lakosa van Budapestnek?",
|
||||||
|
correctAnswer: 1750000,
|
||||||
|
tolerance: 10 // ±10%
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Helyes backend formátum:**
|
||||||
|
```javascript
|
||||||
|
cleanedCard = {
|
||||||
|
text: "Hány lakosa van Budapestnek?",
|
||||||
|
type: 4,
|
||||||
|
answer: {
|
||||||
|
correct: 1750000,
|
||||||
|
percent: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 TESZTELÉSI TERV
|
||||||
|
|
||||||
|
1. **Logolás hozzáadása a backenden:**
|
||||||
|
```typescript
|
||||||
|
// CreateDeckCommandHandler.ts, UpdateDeckCommandHandler.ts
|
||||||
|
console.log('Received card data:', cmd.cards)
|
||||||
|
console.log('Card keys:', Object.keys(cmd.cards[0]))
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Frontendről küldött payload ellenőrzése:**
|
||||||
|
```javascript
|
||||||
|
// DeckCreator.jsx - handleSaveDeck
|
||||||
|
console.log('Payload before send:', JSON.stringify(payload, null, 2))
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Adatbázisban tárolt JSON ellenőrzése:**
|
||||||
|
```sql
|
||||||
|
SELECT id, name, cards FROM Decks WHERE id = 'xyz' LIMIT 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ KÖVETKEZŐ LÉPÉSEK
|
||||||
|
|
||||||
|
1. ✅ **Dokumentáció elkészült** - Ez a fájl
|
||||||
|
2. ✅ **Backend game logic ellenőrzés** - KÉSZ! Csak `answer` mezőt használ
|
||||||
|
3. ⏳ **Frontend konverzió implementálás** - Következő feladat:
|
||||||
|
- Új függvény: `convertCardToBackendFormat(card, deckType)`
|
||||||
|
- Minden kártyatípushoz megfelelő `answer` formátum generálása
|
||||||
|
- Felesleges mezők eltávolítása
|
||||||
|
4. ⏳ **Tesztelés** - Minden működik-e a változások után?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ IMPLEMENTÁCIÓS TERV
|
||||||
|
|
||||||
|
### 1. Létrehozandó segédfüggvény: `cardBackendConverter.js`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/utils/cardBackendConverter.js
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konvertálja a frontend kártya formátumot backend-kompatibilis formátumra
|
||||||
|
* @param {Object} card - Frontend kártya objektum
|
||||||
|
* @param {string} deckType - Pakli típusa ('LUCK', 'JOKER', 'QUESTION')
|
||||||
|
* @returns {Object} Backend-kompatibilis kártya objektum
|
||||||
|
*/
|
||||||
|
export function convertCardToBackendFormat(card, deckType) {
|
||||||
|
const baseCard = {
|
||||||
|
text: card.text || card.question || card.statement || "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// CardType mapping
|
||||||
|
const cardTypeMapping = {
|
||||||
|
'quiz': 0,
|
||||||
|
'multiplechoice': 0, // Alias
|
||||||
|
'pairing': 1,
|
||||||
|
'matching': 1, // Alias
|
||||||
|
'text': 2,
|
||||||
|
'truefalse': 3,
|
||||||
|
'closer': 4
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardType = cardTypeMapping[card.subType] ?? cardTypeMapping[card.subType?.toLowerCase()]
|
||||||
|
if (cardType !== undefined) {
|
||||||
|
baseCard.type = cardType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Típus-specifikus answer konverzió
|
||||||
|
switch (cardType) {
|
||||||
|
case 0: // QUIZ
|
||||||
|
if (card.options && Array.isArray(card.options)) {
|
||||||
|
baseCard.answer = card.options.map((opt, idx) => ({
|
||||||
|
answer: String.fromCharCode(65 + idx), // A, B, C, D...
|
||||||
|
text: opt,
|
||||||
|
correct: idx === card.correctAnswer
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 1: // SENTENCE_PAIRING
|
||||||
|
if (card.leftItems && card.rightItems && card.correctPairs) {
|
||||||
|
baseCard.answer = Object.entries(card.correctPairs).map(([leftIdx, rightIdx]) => ({
|
||||||
|
left: card.leftItems[parseInt(leftIdx)],
|
||||||
|
right: card.rightItems[parseInt(rightIdx)]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 2: // OWN_ANSWER
|
||||||
|
if (card.acceptedAnswers && Array.isArray(card.acceptedAnswers)) {
|
||||||
|
baseCard.answer = card.acceptedAnswers.filter(a => a && a.trim())
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 3: // TRUE_FALSE
|
||||||
|
if (card.correctAnswer !== undefined) {
|
||||||
|
baseCard.answer = card.correctAnswer === 0 // 0=Igaz, 1=Hamis
|
||||||
|
} else if (card.isTrue !== undefined) {
|
||||||
|
baseCard.answer = card.isTrue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 4: // CLOSER
|
||||||
|
if (card.correctAnswer !== undefined && card.tolerance !== undefined) {
|
||||||
|
baseCard.answer = {
|
||||||
|
correct: card.correctAnswer,
|
||||||
|
percent: card.tolerance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// LUCK típusú kártyákhoz consequence
|
||||||
|
if (deckType === 'LUCK' && card.consequence) {
|
||||||
|
baseCard.consequence = card.consequence
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseCard
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Módosítandó fájl: `DeckCreator.jsx`
|
||||||
|
|
||||||
|
**Jelenlegi kód (line ~120-165):**
|
||||||
|
```javascript
|
||||||
|
// ❌ RÉGI - Felesleges mezők küldése
|
||||||
|
const cleanedCards = validCards.map(card => {
|
||||||
|
const cleanedCard = {}
|
||||||
|
if (card.id) cleanedCard.id = card.id
|
||||||
|
if (card.subType && cardTypeMapping[card.subType] !== undefined) {
|
||||||
|
cleanedCard.type = cardTypeMapping[card.subType]
|
||||||
|
}
|
||||||
|
cleanedCard.text = card.text || card.question || card.statement || ""
|
||||||
|
if (card.question !== undefined) cleanedCard.question = card.question // FELESLEGES
|
||||||
|
if (card.statement !== undefined) cleanedCard.statement = card.statement // FELESLEGES
|
||||||
|
if (card.options !== undefined) cleanedCard.options = card.options // FELESLEGES
|
||||||
|
// ... stb
|
||||||
|
return cleanedCard
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Új kód:**
|
||||||
|
```javascript
|
||||||
|
// ✅ ÚJ - Csak szükséges mezők
|
||||||
|
import { convertCardToBackendFormat } from '../../utils/cardBackendConverter'
|
||||||
|
|
||||||
|
const cleanedCards = validCards.map(card =>
|
||||||
|
convertCardToBackendFormat(card, deck.type)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Tesztelési checklist
|
||||||
|
|
||||||
|
- [ ] QUIZ kártyák helyes answer formátummal mentődnek
|
||||||
|
- [ ] SENTENCE_PAIRING kártyák helyes left-right párokkal mentődnek
|
||||||
|
- [ ] OWN_ANSWER kártyák acceptedAnswers array-ként mentődnek
|
||||||
|
- [ ] TRUE_FALSE kártyák boolean answer-rel mentődnek
|
||||||
|
- [ ] CLOSER kártyák {correct, percent} formátummal mentődnek
|
||||||
|
- [ ] LUCK kártyák consequence mezője megmarad
|
||||||
|
- [ ] Mentett paklik betöltése és szerkesztése működik
|
||||||
|
- [ ] Játék során kártyák feldolgozása helyes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Utolsó frissítés:** 2025-11-03
|
||||||
|
**Készítette:** GitHub Copilot
|
||||||
|
**Cél:** Adatoptimalizálás és felesleges payload csökkentés
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 VÁRHATÓ EREDMÉNYEK
|
||||||
|
|
||||||
|
### Payload méret csökkenés példa:
|
||||||
|
|
||||||
|
**ELŐTTE (jelenleg):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Teszt Pakli",
|
||||||
|
"type": 2,
|
||||||
|
"ctype": 1,
|
||||||
|
"description": "Ez egy leírás", // ❌ FELESLEGES
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"id": 1730123456789, // ❌ FELESLEGES
|
||||||
|
"text": "Mi a főváros?",
|
||||||
|
"question": "Mi a főváros?", // ❌ DUPLIKÁCIÓ
|
||||||
|
"type": 0,
|
||||||
|
"options": ["Budapest", "Berlin", "Prága"], // ❌ FELESLEGES
|
||||||
|
"correctAnswer": 0 // ❌ FELESLEGES
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// Méret: ~280 byte
|
||||||
|
```
|
||||||
|
|
||||||
|
**UTÁNA (optimalizált):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Teszt Pakli",
|
||||||
|
"type": 2,
|
||||||
|
"ctype": 1,
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"text": "Mi a főváros?",
|
||||||
|
"type": 0,
|
||||||
|
"answer": [
|
||||||
|
{"answer": "A", "text": "Budapest", "correct": true},
|
||||||
|
{"answer": "B", "text": "Berlin", "correct": false},
|
||||||
|
{"answer": "C", "text": "Prága", "correct": false}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// Méret: ~190 byte
|
||||||
|
```
|
||||||
|
|
||||||
|
**💾 Megtakarítás: ~32% ebben a példában!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 VÉGSŐ ÖSSZEFOGLALÁS
|
||||||
|
|
||||||
|
### Felesleges mezők száma:
|
||||||
|
- **Deck level:** 1 mező (`description`)
|
||||||
|
- **Card level:** 9 mező (`id`, `question`, `statement`, `options`, `correctAnswer`, `leftItems`, `rightItems`, `correctPairs`, `acceptedAnswers`, `hint`)
|
||||||
|
|
||||||
|
### Összes felesleges mező: **10 db**
|
||||||
|
|
||||||
|
### Ajánlott lépések:
|
||||||
|
1. ✅ Dokumentáció áttekintése
|
||||||
|
2. 🔄 `cardBackendConverter.js` implementálása
|
||||||
|
3. 🔧 `DeckCreator.jsx` módosítása
|
||||||
|
4. ✅ Tesztelés minden kártyatípussal
|
||||||
|
5. 🚀 Deploy
|
||||||
|
|
||||||
|
**Becsült munkaidő:** 2-3 óra implementálás + 1 óra tesztelés
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Kérdések / Problémák esetén
|
||||||
|
|
||||||
|
Ha bármilyen kérdés merül fel az implementálás során:
|
||||||
|
1. Ellenőrizd a backend `CardProcessingService.ts` fájlt
|
||||||
|
2. Nézd meg a példákat ebben a dokumentációban
|
||||||
|
3. Teszteld lokálisan először egy kis paklival
|
||||||
|
|
||||||
|
**Fontos:** A backend JSON mezőként tárolja a `cards` array-t, ezért bármit elfogad - de csak a dokumentált mezőket használja!
|
||||||
@@ -15,12 +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/Game/Lobby"
|
import Lobby from "./pages/Lobby/Lobby"
|
||||||
import ProfileCard from "./components/Userdetails/Userdetails"
|
import ProfileCard from "./components/Userdetails/Userdetails"
|
||||||
import { ToastConfig } from "./components/Toastify/toastifyServices" // ✅ fontos: named import, nem default!
|
import { ToastConfig } from "./components/Toastify/toastifyServices" // ✅ fontos: named import, nem default!
|
||||||
import VerifyEmailPage from "./pages/Auth/VerifyEmailPage"
|
import VerifyEmailPage from "./pages/Auth/VerifyEmailPage"
|
||||||
import ChooseDeck from "./pages/Game/ChooseDeck"
|
|
||||||
import PlayerSetup from "./pages/Game/PlayerSetup"
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isMobile, setIsMobile] = useState(false)
|
const [isMobile, setIsMobile] = useState(false)
|
||||||
@@ -70,8 +68,6 @@ function App() {
|
|||||||
<Route path="/game" element={<GameScreen />} />
|
<Route path="/game" element={<GameScreen />} />
|
||||||
{/* <Route path="/contacts" element={<CompanyHub />} /> */}
|
{/* <Route path="/contacts" element={<CompanyHub />} /> */}
|
||||||
<Route path="/report" element={<Reports />} />
|
<Route path="/report" element={<Reports />} />
|
||||||
<Route path="/choosedeck" element={<ChooseDeck />} />
|
|
||||||
<Route path="/playersetup" element={<PlayerSetup />} />
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ const Animation = ({ sizePercentage = 100 }) => {
|
|||||||
const pathRefs = Array.from({ length: 11 }, () => useRef(null));
|
const pathRefs = Array.from({ length: 11 }, () => useRef(null));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="w-full flex justify-center">
|
||||||
{/* prettier-ignore */}
|
{/* prettier-ignore */}
|
||||||
<svg className={styles.animation} width={width} height={height} viewBox="0 0 1319 198" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg className={styles.animation} width="100%" height="auto" viewBox="0 0 1319 198" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" style={{ maxWidth: `${width}px`, maxHeight: `${height}px` }}>
|
||||||
<path ref={pathRefs[0]} className={styles.path0} d="M1261.64 32.9C1272.02 32.9 1281.15 34.9576 1289.1 39.0094L1289.86 39.4078C1297.97 43.7136 1304.29 49.9037 1308.86 58.026L1308.86 58.0328L1308.87 58.0406C1313.41 65.9983 1315.74 75.4878 1315.74 86.6002C1315.74 88.8329 1315.63 91.0662 1315.41 93.3004H1240.77L1240.94 95.9625C1241.36 102.425 1243.14 107.682 1246.63 111.328L1246.67 111.368L1246.71 111.407C1250.29 114.831 1254.8 116.5 1260.04 116.5C1263.69 116.5 1266.97 115.677 1269.77 113.917C1272.15 112.419 1274.06 110.315 1275.55 107.7H1312.61C1310.88 113.608 1308.06 118.989 1304.16 123.859L1303.71 124.408L1303.71 124.413C1299.18 129.919 1293.45 134.322 1286.48 137.611L1285.8 137.925C1278.56 141.229 1270.51 142.9 1261.64 142.9C1250.94 142.9 1241.49 140.648 1233.23 136.205C1225.37 131.905 1219.12 125.83 1214.46 117.933L1214.01 117.164C1209.46 108.936 1207.14 99.1765 1207.14 87.8004C1207.14 76.4113 1209.46 66.7169 1214.01 58.6256L1214.02 58.6187L1214.02 58.6109C1218.45 50.6085 1224.53 44.4249 1232.28 40.0143L1233.04 39.5934C1241.29 35.1536 1250.8 32.9 1261.64 32.9ZM1261.44 58.9C1256.17 58.9 1251.64 60.3691 1248.04 63.4723C1244.4 66.4788 1242.18 70.8761 1241.18 76.3473L1240.63 79.3004H1280.74V76.8004C1280.74 71.5541 1279.01 67.178 1275.39 63.985L1275.04 63.6793C1271.33 60.4557 1266.74 58.9 1261.44 58.9Z" stroke="white" strokeWidth="5"/>
|
<path ref={pathRefs[0]} className={styles.path0} d="M1261.64 32.9C1272.02 32.9 1281.15 34.9576 1289.1 39.0094L1289.86 39.4078C1297.97 43.7136 1304.29 49.9037 1308.86 58.026L1308.86 58.0328L1308.87 58.0406C1313.41 65.9983 1315.74 75.4878 1315.74 86.6002C1315.74 88.8329 1315.63 91.0662 1315.41 93.3004H1240.77L1240.94 95.9625C1241.36 102.425 1243.14 107.682 1246.63 111.328L1246.67 111.368L1246.71 111.407C1250.29 114.831 1254.8 116.5 1260.04 116.5C1263.69 116.5 1266.97 115.677 1269.77 113.917C1272.15 112.419 1274.06 110.315 1275.55 107.7H1312.61C1310.88 113.608 1308.06 118.989 1304.16 123.859L1303.71 124.408L1303.71 124.413C1299.18 129.919 1293.45 134.322 1286.48 137.611L1285.8 137.925C1278.56 141.229 1270.51 142.9 1261.64 142.9C1250.94 142.9 1241.49 140.648 1233.23 136.205C1225.37 131.905 1219.12 125.83 1214.46 117.933L1214.01 117.164C1209.46 108.936 1207.14 99.1765 1207.14 87.8004C1207.14 76.4113 1209.46 66.7169 1214.01 58.6256L1214.02 58.6187L1214.02 58.6109C1218.45 50.6085 1224.53 44.4249 1232.28 40.0143L1233.04 39.5934C1241.29 35.1536 1250.8 32.9 1261.64 32.9ZM1261.44 58.9C1256.17 58.9 1251.64 60.3691 1248.04 63.4723C1244.4 66.4788 1242.18 70.8761 1241.18 76.3473L1240.63 79.3004H1280.74V76.8004C1280.74 71.5541 1279.01 67.178 1275.39 63.985L1275.04 63.6793C1271.33 60.4557 1266.74 58.9 1261.44 58.9Z" stroke="white" strokeWidth="5"/>
|
||||||
<path ref={pathRefs[1]} className={styles.path1} d="M1139.95 32.9C1153.73 32.9 1165.15 36.6867 1174.38 44.1441L1174.39 44.151L1174.4 44.1578C1182.91 50.9203 1188.68 60.2478 1191.63 72.3004H1154.9C1153.61 69.0944 1151.8 66.4744 1149.4 64.5846C1146.55 62.349 1143.08 61.3004 1139.15 61.3004C1133.38 61.3004 1128.7 63.7808 1125.31 68.5533L1125.31 68.5602L1125.3 68.566C1122.08 73.1723 1120.65 79.708 1120.65 87.8004C1120.65 95.9013 1122.08 102.479 1125.28 107.202L1125.31 107.247C1128.7 112.019 1133.38 114.5 1139.15 114.5C1143.13 114.5 1146.64 113.458 1149.5 111.215C1151.9 109.324 1153.68 106.702 1154.93 103.5H1191.63C1188.77 115.027 1183.29 124.135 1175.24 130.949L1174.38 131.656C1165.15 139.113 1153.73 142.9 1139.95 142.9C1129.25 142.9 1119.8 140.648 1111.55 136.205C1103.69 131.908 1097.51 125.841 1092.97 117.958L1092.54 117.189C1087.98 108.956 1085.65 99.188 1085.65 87.8004C1085.65 76.9027 1087.83 67.4559 1092.12 59.3873L1092.54 58.6109C1096.97 50.6085 1103.05 44.4249 1110.8 40.0143L1111.55 39.5934C1119.81 35.1513 1129.25 32.9 1139.95 32.9Z" stroke="white" strokeWidth="5"/>
|
<path ref={pathRefs[1]} className={styles.path1} d="M1139.95 32.9C1153.73 32.9 1165.15 36.6867 1174.38 44.1441L1174.39 44.151L1174.4 44.1578C1182.91 50.9203 1188.68 60.2478 1191.63 72.3004H1154.9C1153.61 69.0944 1151.8 66.4744 1149.4 64.5846C1146.55 62.349 1143.08 61.3004 1139.15 61.3004C1133.38 61.3004 1128.7 63.7808 1125.31 68.5533L1125.31 68.5602L1125.3 68.566C1122.08 73.1723 1120.65 79.708 1120.65 87.8004C1120.65 95.9013 1122.08 102.479 1125.28 107.202L1125.31 107.247C1128.7 112.019 1133.38 114.5 1139.15 114.5C1143.13 114.5 1146.64 113.458 1149.5 111.215C1151.9 109.324 1153.68 106.702 1154.93 103.5H1191.63C1188.77 115.027 1183.29 124.135 1175.24 130.949L1174.38 131.656C1165.15 139.113 1153.73 142.9 1139.95 142.9C1129.25 142.9 1119.8 140.648 1111.55 136.205C1103.69 131.908 1097.51 125.841 1092.97 117.958L1092.54 117.189C1087.98 108.956 1085.65 99.188 1085.65 87.8004C1085.65 76.9027 1087.83 67.4559 1092.12 59.3873L1092.54 58.6109C1096.97 50.6085 1103.05 44.4249 1110.8 40.0143L1111.55 39.5934C1119.81 35.1513 1129.25 32.9 1139.95 32.9Z" stroke="white" strokeWidth="5"/>
|
||||||
<path ref={pathRefs[2]} className={styles.path2} d="M995.014 32.9C1002.18 32.9 1008.26 34.2763 1013.33 36.9322L1013.81 37.193C1019.04 40.0563 1023.04 43.8802 1025.86 48.6695L1030.51 56.5602V34.3004H1064.71V141.5H1030.51V119.24L1025.86 127.13C1023.04 131.905 1019 135.728 1013.63 138.595L1013.61 138.607C1008.45 141.437 1002.27 142.9 995.014 142.9C986.807 142.9 979.357 140.83 972.608 136.697L971.956 136.291C965.401 132.037 960.089 125.994 956.045 118.069L955.657 117.296C951.72 108.895 949.714 99.0842 949.714 87.8004C949.714 76.5091 951.722 66.7655 955.656 58.5035L955.657 58.5045C959.747 50.1977 965.189 43.9003 971.956 39.5094C978.877 35.1054 986.542 32.9 995.014 32.9ZM1007.61 62.1002C1001.29 62.1002 995.894 64.2893 991.601 68.6617L991.217 69.0621C986.771 73.6617 984.714 80.0315 984.714 87.8004C984.714 95.4589 986.781 101.845 991.161 106.678L991.175 106.694L991.189 106.708C995.547 111.367 1001.08 113.7 1007.61 113.7C1014.02 113.7 1019.47 111.363 1023.81 106.738L1023.81 106.739C1028.38 102.021 1030.51 95.5962 1030.51 87.8004C1030.51 80.1231 1028.37 73.771 1023.81 69.0611H1023.81C1019.47 64.436 1014.01 62.1003 1007.61 62.1002Z" stroke="white" strokeWidth="5"/>
|
<path ref={pathRefs[2]} className={styles.path2} d="M995.014 32.9C1002.18 32.9 1008.26 34.2763 1013.33 36.9322L1013.81 37.193C1019.04 40.0563 1023.04 43.8802 1025.86 48.6695L1030.51 56.5602V34.3004H1064.71V141.5H1030.51V119.24L1025.86 127.13C1023.04 131.905 1019 135.728 1013.63 138.595L1013.61 138.607C1008.45 141.437 1002.27 142.9 995.014 142.9C986.807 142.9 979.357 140.83 972.608 136.697L971.956 136.291C965.401 132.037 960.089 125.994 956.045 118.069L955.657 117.296C951.72 108.895 949.714 99.0842 949.714 87.8004C949.714 76.5091 951.722 66.7655 955.656 58.5035L955.657 58.5045C959.747 50.1977 965.189 43.9003 971.956 39.5094C978.877 35.1054 986.542 32.9 995.014 32.9ZM1007.61 62.1002C1001.29 62.1002 995.894 64.2893 991.601 68.6617L991.217 69.0621C986.771 73.6617 984.714 80.0315 984.714 87.8004C984.714 95.4589 986.781 101.845 991.161 106.678L991.175 106.694L991.189 106.708C995.547 111.367 1001.08 113.7 1007.61 113.7C1014.02 113.7 1019.47 111.363 1023.81 106.738L1023.81 106.739C1028.38 102.021 1030.51 95.5962 1030.51 87.8004C1030.51 80.1231 1028.37 73.771 1023.81 69.0611H1023.81C1019.47 64.436 1014.01 62.1003 1007.61 62.1002Z" stroke="white" strokeWidth="5"/>
|
||||||
|
|||||||
@@ -33,10 +33,84 @@ const Footer = () => {
|
|||||||
return (
|
return (
|
||||||
<footer
|
<footer
|
||||||
ref={footerRef}
|
ref={footerRef}
|
||||||
className="relative bg-zinc-900 text-zinc-400 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-6 md: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 px-4">
|
||||||
|
{/* Mobile: Logo középen, majd grid alatta */}
|
||||||
|
<div className="flex flex-col items-center md:hidden gap-6 mb-6">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<button
|
||||||
|
onClick={goLanding}
|
||||||
|
className="hover:scale-105 hover:brightness-110 transition-transform"
|
||||||
|
>
|
||||||
|
<Logo size={80} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={goLanding}
|
||||||
|
className="font-extrabold text-lg mt-2 tracking-wide text-white hover:text-green-500 transition-colors"
|
||||||
|
>
|
||||||
|
SerpentRace
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile: 2 oszlopos grid */}
|
||||||
|
<div className="grid grid-cols-2 gap-6 md:hidden mb-6">
|
||||||
|
{/* Oldalak */}
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="text-base font-semibold text-green-600 underline underline-offset-4 mb-2 drop-shadow-sm">
|
||||||
|
Oldalak
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={goLanding}
|
||||||
|
className="text-left text-sm hover:underline hover:text-green-500 transition-colors"
|
||||||
|
>
|
||||||
|
Főoldal
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={goAbout}
|
||||||
|
className="text-left text-sm hover:underline hover:text-green-500 transition-colors"
|
||||||
|
>
|
||||||
|
Rólunk
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Közösség */}
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="text-base 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="text-sm hover:underline hover:text-green-500"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-sm hover:underline hover:text-green-500"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile: Elérhetőség teljes széles */}
|
||||||
|
<div className="flex flex-col gap-1 md:hidden mb-6">
|
||||||
|
<span className="text-base font-semibold text-green-600 underline underline-offset-4 mb-2 drop-shadow-sm">
|
||||||
|
Elérhetőség
|
||||||
|
</span>
|
||||||
|
<span className="text-sm opacity-85">Email: info@serpentrace.hu</span>
|
||||||
|
<span className="text-sm opacity-85">Telefon: +36 30 123 4567</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop: Original flex layout */}
|
||||||
|
<div className="hidden md:flex flex-wrap justify-between items-start gap-8">
|
||||||
{/* Logó */}
|
{/* Logó */}
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<button
|
<button
|
||||||
@@ -104,6 +178,7 @@ const Footer = () => {
|
|||||||
<span className="opacity-85">Telefon: +36 30 123 4567</span>
|
<span className="opacity-85">Telefon: +36 30 123 4567</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="text-center mt-8 text-sm opacity-70">
|
<div className="text-center mt-8 text-sm opacity-70">
|
||||||
© {new Date().getFullYear()} SerpentRace. Minden jog fenntartva.
|
© {new Date().getFullYear()} SerpentRace. Minden jog fenntartva.
|
||||||
|
|||||||
@@ -18,19 +18,21 @@ const LandingPage = () => {
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<motion.section
|
<motion.section
|
||||||
className="min-h-[80vh] flex flex-col items-center justify-center text-center px-4 py-20"
|
className="min-h-[80vh] flex flex-col items-center justify-center text-center px-4 sm:px-6 py-12 sm:py-16 md:py-20"
|
||||||
initial={{ opacity: 0, y: 40 }}
|
initial={{ opacity: 0, y: 40 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.8 }}
|
transition={{ duration: 0.8 }}
|
||||||
>
|
>
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto w-full">
|
||||||
{/* Animált logo és cím */}
|
{/* Animált logo és cím */}
|
||||||
<div className="mb-8">
|
<div className="mb-6 sm:mb-8 flex justify-center">
|
||||||
|
<div className="w-full max-w-[90%] sm:max-w-[70%] md:max-w-full">
|
||||||
<SerpentRaceAnimation sizePercentage={70} />
|
<SerpentRaceAnimation sizePercentage={70} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<motion.h1
|
<motion.h1
|
||||||
className="text-3xl md:text-5xl font-bold text-white mb-4 leading-tight"
|
className="text-2xl sm:text-3xl md:text-5xl font-bold text-white mb-3 sm:mb-4 leading-tight px-2"
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.7, delay: 0.4 }}
|
transition={{ duration: 0.7, delay: 0.4 }}
|
||||||
@@ -39,7 +41,7 @@ const LandingPage = () => {
|
|||||||
</motion.h1>
|
</motion.h1>
|
||||||
|
|
||||||
<motion.p
|
<motion.p
|
||||||
className="text-lg md:text-xl text-gray-300 mb-4 max-w-3xl mx-auto leading-relaxed"
|
className="text-base sm:text-lg md:text-xl text-gray-300 mb-3 sm:mb-4 max-w-3xl mx-auto leading-relaxed px-2"
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.7, delay: 0.6 }}
|
transition={{ duration: 0.7, delay: 0.6 }}
|
||||||
@@ -49,7 +51,7 @@ const LandingPage = () => {
|
|||||||
</motion.p>
|
</motion.p>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="text-xl md:text-2xl font-bold text-emerald-400 mb-10"
|
className="text-lg sm:text-xl md:text-2xl font-bold text-emerald-400 mb-6 sm:mb-8 md:mb-10 px-2"
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.7, delay: 0.8 }}
|
transition={{ duration: 0.7, delay: 0.8 }}
|
||||||
@@ -58,7 +60,7 @@ const LandingPage = () => {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex flex-col sm:flex-row gap-4 justify-center items-center"
|
className="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center items-center px-2"
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.7, delay: 1 }}
|
transition={{ duration: 0.7, delay: 1 }}
|
||||||
@@ -66,12 +68,12 @@ const LandingPage = () => {
|
|||||||
{/* If not authenticated show Login/Register; if authenticated show Home button */}
|
{/* If not authenticated show Login/Register; if authenticated show Home button */}
|
||||||
{!auth ? (
|
{!auth ? (
|
||||||
<>
|
<>
|
||||||
<ButtonGreen text="Bejelentkezés" onClick={goLogin} width="w-60" />
|
<ButtonGreen text="Bejelentkezés" onClick={goLogin} width="w-full sm:w-60" />
|
||||||
<ButtonGreen text="Regisztráció" onClick={goAuth} width="w-60" />
|
<ButtonGreen text="Regisztráció" onClick={goAuth} width="w-full sm:w-60" />
|
||||||
<ButtonGreen text="Játék" onClick={goHome} width="w-60" />
|
<ButtonGreen text="Játék" onClick={goHome} width="w-full sm:w-60" />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ButtonGreen text="Játék" onClick={goHome} width="w-60" />
|
<ButtonGreen text="Játék" onClick={goHome} width="w-full sm:w-60" />
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,7 +81,7 @@ const LandingPage = () => {
|
|||||||
|
|
||||||
{/* Features Section */}
|
{/* Features Section */}
|
||||||
<motion.section
|
<motion.section
|
||||||
className="py-20 px-4"
|
className="py-12 sm:py-16 md:py-20 px-4 sm:px-6"
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
whileInView={{ opacity: 1 }}
|
whileInView={{ opacity: 1 }}
|
||||||
viewport={{ once: true, amount: 0.2 }}
|
viewport={{ once: true, amount: 0.2 }}
|
||||||
@@ -87,7 +89,7 @@ const LandingPage = () => {
|
|||||||
>
|
>
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
<motion.h2
|
<motion.h2
|
||||||
className="text-2xl md:text-3xl font-bold text-white text-center mb-12"
|
className="text-xl sm:text-2xl md:text-3xl font-bold text-white text-center mb-8 sm:mb-10 md:mb-12 px-2"
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
@@ -96,19 +98,19 @@ const LandingPage = () => {
|
|||||||
Miért a SerpentRace a legjobb választás?
|
Miért a SerpentRace a legjobb választás?
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 sm:gap-8">
|
||||||
{/* Feature 1 */}
|
{/* Feature 1 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 text-center"
|
className="bg-white/10 backdrop-blur-lg rounded-xl sm:rounded-2xl p-6 sm:p-8 text-center"
|
||||||
initial={{ opacity: 0, y: 40 }}
|
initial={{ opacity: 0, y: 40 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.7, delay: 0.3 }}
|
transition={{ duration: 0.7, delay: 0.3 }}
|
||||||
>
|
>
|
||||||
<div className="w-16 h-16 mx-auto mb-6 bg-emerald-500 rounded-full flex items-center justify-center">
|
<div className="w-12 h-12 sm:w-16 sm:h-16 mx-auto mb-4 sm:mb-6 bg-emerald-500 rounded-full flex items-center justify-center">
|
||||||
<FaUsers className="w-8 h-8 text-white" />
|
<FaUsers className="w-6 h-6 sm:w-8 sm:h-8 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold text-white mb-2">Közösségi élmény</h3>
|
<h3 className="text-base sm:text-lg font-semibold text-white mb-2">Közösségi élmény</h3>
|
||||||
<p className="text-gray-300 text-sm">
|
<p className="text-gray-300 text-sm">
|
||||||
Ismerkedj, nevess, tanulj! A SerpentRace összehozza a társaságot, legyen szó baráti
|
Ismerkedj, nevess, tanulj! A SerpentRace összehozza a társaságot, legyen szó baráti
|
||||||
összejövetelről vagy csapatépítésről.
|
összejövetelről vagy csapatépítésről.
|
||||||
@@ -117,16 +119,16 @@ const LandingPage = () => {
|
|||||||
|
|
||||||
{/* Feature 2 */}
|
{/* Feature 2 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 text-center"
|
className="bg-white/10 backdrop-blur-lg rounded-xl sm:rounded-2xl p-6 sm:p-8 text-center"
|
||||||
initial={{ opacity: 0, y: 40 }}
|
initial={{ opacity: 0, y: 40 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.7, delay: 0.5 }}
|
transition={{ duration: 0.7, delay: 0.5 }}
|
||||||
>
|
>
|
||||||
<div className="w-16 h-16 mx-auto mb-6 bg-emerald-500 rounded-full flex items-center justify-center">
|
<div className="w-12 h-12 sm:w-16 sm:h-16 mx-auto mb-4 sm:mb-6 bg-emerald-500 rounded-full flex items-center justify-center">
|
||||||
<FaPaintBrush className="w-8 h-8 text-white" />
|
<FaPaintBrush className="w-6 h-6 sm:w-8 sm:h-8 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold text-white mb-2">Személyre szabható</h3>
|
<h3 className="text-base sm:text-lg font-semibold text-white mb-2">Személyre szabható</h3>
|
||||||
<p className="text-gray-300 text-sm">
|
<p className="text-gray-300 text-sm">
|
||||||
Kérdéskártyák, szabályok, design – minden a te igényeidhez igazítható, akár céges brandinggel
|
Kérdéskártyák, szabályok, design – minden a te igényeidhez igazítható, akár céges brandinggel
|
||||||
is!
|
is!
|
||||||
@@ -135,16 +137,16 @@ const LandingPage = () => {
|
|||||||
|
|
||||||
{/* Feature 3 */}
|
{/* Feature 3 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 text-center"
|
className="bg-white/10 backdrop-blur-lg rounded-xl sm:rounded-2xl p-6 sm:p-8 text-center"
|
||||||
initial={{ opacity: 0, y: 40 }}
|
initial={{ opacity: 0, y: 40 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.7, delay: 0.7 }}
|
transition={{ duration: 0.7, delay: 0.7 }}
|
||||||
>
|
>
|
||||||
<div className="w-16 h-16 mx-auto mb-6 bg-emerald-500 rounded-full flex items-center justify-center">
|
<div className="w-12 h-12 sm:w-16 sm:h-16 mx-auto mb-4 sm:mb-6 bg-emerald-500 rounded-full flex items-center justify-center">
|
||||||
<FaHeadset className="w-8 h-8 text-white" />
|
<FaHeadset className="w-6 h-6 sm:w-8 sm:h-8 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold text-white mb-2">Folyamatos támogatás</h3>
|
<h3 className="text-base sm:text-lg font-semibold text-white mb-2">Folyamatos támogatás</h3>
|
||||||
<p className="text-gray-300 text-sm">
|
<p className="text-gray-300 text-sm">
|
||||||
Gyors, segítőkész ügyfélszolgálat – ha bármilyen kérdésed vagy problémád van, mindig
|
Gyors, segítőkész ügyfélszolgálat – ha bármilyen kérdésed vagy problémád van, mindig
|
||||||
számíthatsz ránk!
|
számíthatsz ránk!
|
||||||
@@ -156,7 +158,7 @@ const LandingPage = () => {
|
|||||||
|
|
||||||
{/* Call to Action Section */}
|
{/* Call to Action Section */}
|
||||||
<motion.section
|
<motion.section
|
||||||
className="py-20 px-4"
|
className="py-12 sm:py-16 md:py-20 px-4 sm:px-6"
|
||||||
initial={{ opacity: 0, y: 40 }}
|
initial={{ opacity: 0, y: 40 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true, amount: 0.2 }}
|
viewport={{ once: true, amount: 0.2 }}
|
||||||
@@ -164,17 +166,17 @@ const LandingPage = () => {
|
|||||||
>
|
>
|
||||||
<div className="max-w-4xl mx-auto text-center">
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
<motion.div
|
<motion.div
|
||||||
className="bg-gradient-to-r from-emerald-500/20 to-green-500/20 backdrop-blur-lg rounded-3xl p-12"
|
className="bg-gradient-to-r from-emerald-500/20 to-green-500/20 backdrop-blur-lg rounded-2xl sm:rounded-3xl p-6 sm:p-8 md:p-12"
|
||||||
initial={{ opacity: 0, scale: 0.95 }}
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
whileInView={{ opacity: 1, scale: 1 }}
|
whileInView={{ opacity: 1, scale: 1 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.7, delay: 0.3 }}
|
transition={{ duration: 0.7, delay: 0.3 }}
|
||||||
>
|
>
|
||||||
<h2 className="text-2xl md:text-3xl font-bold text-white mb-4">
|
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold text-white mb-3 sm:mb-4 px-2">
|
||||||
Próbáld ki te is a SerpentRace-t!
|
Próbáld ki te is a SerpentRace-t!
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p className="text-lg text-gray-300 mb-6">
|
<p className="text-base sm:text-lg text-gray-300 mb-4 sm:mb-6 px-2">
|
||||||
Legyél részese egy új közösségi élménynek, vagy rendeld meg saját, személyre szabott
|
Legyél részese egy új közösségi élménynek, vagy rendeld meg saját, személyre szabott
|
||||||
társasjátékodat – mi mindenben segítünk!
|
társasjátékodat – mi mindenben segítünk!
|
||||||
</p>
|
</p>
|
||||||
@@ -182,7 +184,8 @@ const LandingPage = () => {
|
|||||||
<ButtonGreen
|
<ButtonGreen
|
||||||
text="Kapcsolatfelvétel"
|
text="Kapcsolatfelvétel"
|
||||||
onClick={goAbout}
|
onClick={goAbout}
|
||||||
className="px-12 py-4 text-xl font-bold"
|
className="px-8 sm:px-12 py-3 sm:py-4 text-lg sm:text-xl font-bold"
|
||||||
|
width="w-full sm:w-auto"
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
import { useNavigate } from "react-router-dom"
|
|
||||||
import LogoCard from "../../assets/pictures/LogoCard.jsx"
|
import LogoCard from "../../assets/pictures/LogoCard.jsx"
|
||||||
import logoImg from "../../assets/pictures/Logo.png" // <-- EZT ADD HOZZÁ
|
import logoImg from "../../assets/pictures/Logo.png" // <-- EZT ADD HOZZÁ
|
||||||
import ButtonDark from "../Buttons/ButtonDark.jsx"
|
import ButtonDark from "../Buttons/ButtonDark.jsx"
|
||||||
@@ -13,7 +12,6 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user, setUser }) => {
|
|||||||
|
|
||||||
// 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
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const handleJoin = () => {
|
const handleJoin = () => {
|
||||||
if (!joinCode.trim()) {
|
if (!joinCode.trim()) {
|
||||||
@@ -25,22 +23,7 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user, setUser }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
// determine the name we will pass: logged in username or guestName
|
onCreateGame()
|
||||||
const nameToSend = username ?? guestName?.trim()
|
|
||||||
|
|
||||||
if (!nameToSend) {
|
|
||||||
setGuestError("Adj meg egy nevet, vagy jelentkezz be!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if parent provided a setter, set guest as current user (optional)
|
|
||||||
if (!username && setUser) {
|
|
||||||
setUser({ name: nameToSend })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do NOT call onCreateGame here to avoid any alert side-effects from parent.
|
|
||||||
// Just navigate to choose deck and pass username via location.state
|
|
||||||
navigate("/choosedeck", { state: { username: nameToSend } })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// egyszerű segéd a kezdobetűk kinyerésére
|
// egyszerű segéd a kezdobetűk kinyerésére
|
||||||
@@ -55,19 +38,20 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user, setUser }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<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 overflow-hidden"
|
className="w-[95%] max-w-6xl mx-auto my-8 md:my-16 flex flex-col md:flex-row items-center justify-center rounded-2xl md:rounded-3xl shadow-2xl overflow-hidden"
|
||||||
style={{
|
style={{
|
||||||
background: "linear-gradient(90deg, var(--color-surface) 30%, var(--color-mint) 100%)",
|
background: "linear-gradient(90deg, var(--color-surface) 30%, var(--color-mint) 100%)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Bal oldali animáció/kép */}
|
{/* Bal oldali animáció/kép */}
|
||||||
<div className="flex-1 flex items-center justify-center w-full h-full py-10 md:py-0 md:pl-10">
|
<div className="flex-1 flex items-center justify-center w-full h-full py-6 md:py-10 md:pl-10">
|
||||||
|
<div className="w-[200px] h-[200px] sm:w-[300px] sm:h-[300px] md:w-[420px] md:h-[420px]">
|
||||||
<LogoCard
|
<LogoCard
|
||||||
imageSrc={logoImg}
|
imageSrc={logoImg}
|
||||||
containerHeight="420px"
|
containerHeight="100%"
|
||||||
containerWidth="420px"
|
containerWidth="100%"
|
||||||
imageHeight="420px"
|
imageHeight="100%"
|
||||||
imageWidth="420px"
|
imageWidth="100%"
|
||||||
rotateAmplitude={7}
|
rotateAmplitude={7}
|
||||||
scaleOnHover={1.03}
|
scaleOnHover={1.03}
|
||||||
showMobileWarning={false}
|
showMobileWarning={false}
|
||||||
@@ -75,23 +59,24 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user, setUser }) => {
|
|||||||
displayOverlayContent={false}
|
displayOverlayContent={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Jobb oldali panel */}
|
{/* Jobb oldali panel */}
|
||||||
<div className="flex-1 w-full flex items-center justify-center px-6 md:px-12 py-8">
|
<div className="flex-1 w-full flex items-center justify-center px-4 sm:px-6 md:px-12 py-6 md:py-8">
|
||||||
<div
|
<div
|
||||||
className="w-full max-w-md rounded-2xl p-6 md:p-8 flex flex-col gap-6"
|
className="w-full max-w-md rounded-xl md:rounded-2xl p-4 sm:p-6 md:p-8 flex flex-col gap-4 md:gap-6"
|
||||||
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">
|
||||||
{username ? (
|
{username ? (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-2 md:gap-3">
|
||||||
<div
|
<div
|
||||||
className="w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold"
|
className="w-8 h-8 md:w-10 md:h-10 rounded-full flex items-center justify-center text-xs md:text-sm font-semibold"
|
||||||
style={{ background: "rgba(34,197,94,0.12)", color: "var(--color-mint)" }}
|
style={{ background: "rgba(34,197,94,0.12)", color: "var(--color-mint)" }}
|
||||||
>
|
>
|
||||||
{initials}
|
{initials}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[32px]" style={{ color: "var(--color-muted, #cbd5e1)" }}>
|
<div className="text-xl sm:text-2xl md:text-[32px]" style={{ color: "var(--color-muted, #cbd5e1)" }}>
|
||||||
<span className="font-medium" style={{ color: "var(--color-text, #fff)" }}>
|
<span className="font-medium" style={{ color: "var(--color-text, #fff)" }}>
|
||||||
{username}
|
{username}
|
||||||
</span>
|
</span>
|
||||||
@@ -99,7 +84,7 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user, setUser }) => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="font-semibold mb-3 text-text">Nincs bejelentkezve — játssz vendégként:</div>
|
<div className="font-semibold mb-2 md:mb-3 text-sm md:text-base text-text">Nincs bejelentkezve — játssz vendégként:</div>
|
||||||
<InputBoxDark
|
<InputBoxDark
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Nickname..."
|
placeholder="Nickname..."
|
||||||
@@ -116,7 +101,7 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user, setUser }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 className="font-semibold mb-3 text-text">Csatlakozás játékhoz</h2>
|
<h2 className="font-semibold mb-2 md:mb-3 text-sm md:text-base 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"
|
||||||
@@ -127,15 +112,15 @@ const PlayMenu = ({ onJoinGame, onCreateGame, user, setUser }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{error && <div className="text-xs mt-2 text-error">{error}</div>}
|
{error && <div className="text-xs mt-2 text-error">{error}</div>}
|
||||||
<div className="mt-4">
|
<div className="mt-3 md:mt-4">
|
||||||
<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 ? (
|
{username ? (
|
||||||
<div className="border-t border-white/10 pt-4">
|
<div className="border-t border-white/10 pt-3 md:pt-4">
|
||||||
{username && (
|
{username && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold mb-3 text-text">Új játék létrehozása</h3>
|
<h3 className="font-semibold mb-2 md:mb-3 text-sm md:text-base 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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -233,18 +233,17 @@ const Navbar = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-end px-2 pb-2">
|
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleLogout()
|
handleLogout()
|
||||||
setMenuOpen(false)
|
setMenuOpen(false)
|
||||||
}}
|
}}
|
||||||
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 flex items-center gap-2"
|
className="flex items-center gap-2 px-3 py-2 rounded-lg bg-red-600 hover:bg-red-700 text-white transition-all"
|
||||||
title="Kijelentkezés"
|
title="Kijelentkezés"
|
||||||
>
|
>
|
||||||
<FaSignOutAlt className="h-6 w-6" />
|
<FaSignOutAlt className="h-4 w-4" />
|
||||||
|
<span>Kijelentkezés</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ const Card_display = () => {
|
|||||||
"QUESTION": "Kérdés",
|
"QUESTION": "Kérdés",
|
||||||
"LUCK": "Szerencse",
|
"LUCK": "Szerencse",
|
||||||
"JOKER": "Joker",
|
"JOKER": "Joker",
|
||||||
|
"joker": "Joker",
|
||||||
|
"luck": "Szerencse",
|
||||||
// If backend converts to different numbers, map them:
|
// If backend converts to different numbers, map them:
|
||||||
"0": "Igaz/Hamis", // truefalse = 0
|
"0": "Igaz/Hamis", // truefalse = 0
|
||||||
"1": "Feleletválasztós", // multiplechoice = 1
|
"1": "Feleletválasztós", // multiplechoice = 1
|
||||||
@@ -352,7 +354,7 @@ const Card_display = () => {
|
|||||||
)}
|
)}
|
||||||
{paginatedCards.map((card, idx) => {
|
{paginatedCards.map((card, idx) => {
|
||||||
const cardIndex = startIndex + idx + 1
|
const cardIndex = startIndex + idx + 1
|
||||||
const questionText = card.question || card.statement || 'Kérdés hiányzik'
|
const questionText = card.text || card.question || card.statement || 'Kérdés hiányzik'
|
||||||
|
|
||||||
// Get answers based on card type
|
// Get answers based on card type
|
||||||
let answerOptions = []
|
let answerOptions = []
|
||||||
@@ -364,13 +366,30 @@ const Card_display = () => {
|
|||||||
// Detect card type by fields if subType is missing
|
// Detect card type by fields if subType is missing
|
||||||
let detectedType = subType
|
let detectedType = subType
|
||||||
if (subType === 'undefined' || subType === 'null') {
|
if (subType === 'undefined' || subType === 'null') {
|
||||||
// Check by numeric type field first
|
// First check deck type - if deck is JOKER or LUCK type, cards inherit that
|
||||||
if (card.type === 3) {
|
if (deck.type === 1) {
|
||||||
|
// Deck type 1 = Joker deck
|
||||||
|
detectedType = 'joker'
|
||||||
|
} else if (deck.type === 0) {
|
||||||
|
// Deck type 0 = Luck deck
|
||||||
|
detectedType = 'luck'
|
||||||
|
} else if (card.type !== undefined) {
|
||||||
|
// Check by card.type field (string or numeric)
|
||||||
|
const cardType = typeof card.type === 'string' ? card.type.toLowerCase() : card.type
|
||||||
|
|
||||||
|
if (cardType === 'joker' || card.type === 'JOKER') {
|
||||||
|
// Joker card
|
||||||
|
detectedType = 'joker'
|
||||||
|
} else if (cardType === 'luck' || card.type === 'LUCK') {
|
||||||
|
// Luck card
|
||||||
|
detectedType = 'luck'
|
||||||
|
} else if (card.type === 3) {
|
||||||
// type 3 = True/False
|
// type 3 = True/False
|
||||||
detectedType = 'truefalse'
|
detectedType = 'truefalse'
|
||||||
} else if (card.type === 2) {
|
} else if (card.type === 2) {
|
||||||
// type 2 = Text answer
|
// type 2 = Text answer
|
||||||
detectedType = 'text'
|
detectedType = 'text'
|
||||||
|
}
|
||||||
} else if (card.leftItems && card.rightItems && card.correctPairs) {
|
} else if (card.leftItems && card.rightItems && card.correctPairs) {
|
||||||
// Has leftItems, rightItems AND correctPairs = matching
|
// Has leftItems, rightItems AND correctPairs = matching
|
||||||
detectedType = 'matching'
|
detectedType = 'matching'
|
||||||
@@ -385,6 +404,28 @@ const Card_display = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract consequence info for JOKER and LUCK cards
|
||||||
|
let consequenceText = null
|
||||||
|
if ((detectedType === 'joker' || detectedType === 'luck') && card.consequence) {
|
||||||
|
const consequenceLabels = {
|
||||||
|
0: 'Lépj előre',
|
||||||
|
1: 'Lépj hátra',
|
||||||
|
2: 'Kör kihagyás',
|
||||||
|
3: 'Extra kör',
|
||||||
|
5: 'Vissza a starthoz'
|
||||||
|
}
|
||||||
|
const consequenceType = consequenceLabels[card.consequence.type] || 'Ismeretlen hatás'
|
||||||
|
const consequenceValue = card.consequence.value
|
||||||
|
|
||||||
|
if (consequenceValue && [0, 1].includes(card.consequence.type)) {
|
||||||
|
consequenceText = `${consequenceType} ${consequenceValue} mezőt`
|
||||||
|
} else if (consequenceValue && [2, 3].includes(card.consequence.type)) {
|
||||||
|
consequenceText = `${consequenceType} (${consequenceValue} kör)`
|
||||||
|
} else {
|
||||||
|
consequenceText = consequenceType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (detectedType === 'truefalse' || detectedType === '0') {
|
if (detectedType === 'truefalse' || detectedType === '0') {
|
||||||
// True/False cards
|
// True/False cards
|
||||||
answerOptions = ['Igaz', 'Hamis']
|
answerOptions = ['Igaz', 'Hamis']
|
||||||
@@ -432,16 +473,92 @@ const Card_display = () => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={cardId}
|
key={cardId}
|
||||||
className="relative h-80 cursor-pointer"
|
className="relative h-80"
|
||||||
style={{ perspective: "1000px" }}
|
style={{ perspective: "1000px" }}
|
||||||
onClick={() => toggleCardFlip(cardId)}
|
|
||||||
>
|
>
|
||||||
|
{detectedType === 'joker' ? (
|
||||||
|
// Joker card - no flip, just show the task
|
||||||
<div
|
<div
|
||||||
className={`relative w-full h-full transition-transform duration-500`}
|
className="w-full h-full bg-[color:var(--color-card)] rounded-2xl p-6 shadow-lg border-l-4 flex flex-col"
|
||||||
|
style={{
|
||||||
|
borderLeftColor: "var(--color-fun)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<span className="text-[color:var(--color-text-muted)] text-sm font-medium">
|
||||||
|
Kártya #{cardIndex}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="inline-block px-2 py-1 rounded-full text-xs font-bold"
|
||||||
|
style={{
|
||||||
|
background: "var(--color-fun)",
|
||||||
|
color: "var(--color-text-inverse)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🃏 JOKER
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 flex flex-col items-center justify-center">
|
||||||
|
<div className="text-6xl mb-4">🃏</div>
|
||||||
|
<div className="text-[color:var(--color-text)] text-center text-lg font-medium bg-[color:var(--color-fun)]/20 rounded-lg px-6 py-4 border-2 border-[color:var(--color-fun)]">
|
||||||
|
{questionText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-3 border-t border-[color:var(--color-surface-selected)] text-xs text-[color:var(--color-text-muted)] text-center">
|
||||||
|
<div>Típus: <span className="font-semibold">Joker</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : detectedType === 'luck' ? (
|
||||||
|
// Luck card - no flip, show text and consequence
|
||||||
|
<div
|
||||||
|
className="w-full h-full bg-[color:var(--color-card)] rounded-2xl p-6 shadow-lg border-l-4 flex flex-col"
|
||||||
|
style={{
|
||||||
|
borderLeftColor: "var(--color-luck)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<span className="text-[color:var(--color-text-muted)] text-sm font-medium">
|
||||||
|
Kártya #{cardIndex}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="inline-block px-2 py-1 rounded-full text-xs font-bold"
|
||||||
|
style={{
|
||||||
|
background: "var(--color-luck)",
|
||||||
|
color: "var(--color-text-inverse)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🎲 SZERENCSE
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 flex flex-col items-center justify-center">
|
||||||
|
<div className="text-6xl mb-4">🎲</div>
|
||||||
|
<div className="text-[color:var(--color-text)] text-center text-lg font-medium bg-[color:var(--color-luck)]/20 rounded-lg px-6 py-4 border-2 border-[color:var(--color-luck)] mb-4">
|
||||||
|
{questionText}
|
||||||
|
</div>
|
||||||
|
{consequenceText && (
|
||||||
|
<div className="text-[color:var(--color-text)] text-center">
|
||||||
|
<div className="text-xl font-bold bg-[color:var(--color-luck)]/30 rounded-lg px-6 py-3 border-2 border-[color:var(--color-luck)]">
|
||||||
|
{consequenceText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-3 border-t border-[color:var(--color-surface-selected)] text-xs text-[color:var(--color-text-muted)] text-center">
|
||||||
|
<div>Típus: <span className="font-semibold">Szerencse</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={`relative w-full h-full transition-transform duration-500 ${detectedType !== 'joker' && detectedType !== 'luck' ? 'cursor-pointer' : ''}`}
|
||||||
style={{
|
style={{
|
||||||
transformStyle: "preserve-3d",
|
transformStyle: "preserve-3d",
|
||||||
transform: isFlipped ? "rotateY(180deg)" : "rotateY(0deg)"
|
transform: isFlipped ? "rotateY(180deg)" : "rotateY(0deg)"
|
||||||
}}
|
}}
|
||||||
|
onClick={detectedType !== 'joker' && detectedType !== 'luck' ? () => toggleCardFlip(cardId) : undefined}
|
||||||
>
|
>
|
||||||
{/* Front side - Question */}
|
{/* Front side - Question */}
|
||||||
<div
|
<div
|
||||||
@@ -455,6 +572,7 @@ const Card_display = () => {
|
|||||||
<span className="text-[color:var(--color-text-muted)] text-sm font-medium">
|
<span className="text-[color:var(--color-text-muted)] text-sm font-medium">
|
||||||
Kártya #{cardIndex}
|
Kártya #{cardIndex}
|
||||||
</span>
|
</span>
|
||||||
|
{detectedType !== 'joker' && detectedType !== 'luck' && (
|
||||||
<span
|
<span
|
||||||
className="inline-block px-2 py-1 rounded-full text-xs font-bold"
|
className="inline-block px-2 py-1 rounded-full text-xs font-bold"
|
||||||
style={{
|
style={{
|
||||||
@@ -464,6 +582,29 @@ const Card_display = () => {
|
|||||||
>
|
>
|
||||||
{answerCount} válasz
|
{answerCount} válasz
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
|
{detectedType === 'joker' && (
|
||||||
|
<span
|
||||||
|
className="inline-block px-2 py-1 rounded-full text-xs font-bold"
|
||||||
|
style={{
|
||||||
|
background: "var(--color-fun)",
|
||||||
|
color: "var(--color-text-inverse)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🃏 JOKER
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{detectedType === 'luck' && (
|
||||||
|
<span
|
||||||
|
className="inline-block px-2 py-1 rounded-full text-xs font-bold"
|
||||||
|
style={{
|
||||||
|
background: "var(--color-luck)",
|
||||||
|
color: "var(--color-text-inverse)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🎲 SZERENCSE
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-lg font-bold text-[color:var(--color-text)] mb-3">
|
<h3 className="text-lg font-bold text-[color:var(--color-text)] mb-3">
|
||||||
@@ -492,7 +633,7 @@ const Card_display = () => {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="text-[color:var(--color-text-muted)] text-sm font-medium">
|
<span className="text-[color:var(--color-text-muted)] text-sm font-medium">
|
||||||
Megoldás
|
{detectedType === 'joker' || detectedType === 'luck' ? 'Kártya hatás' : 'Megoldás'}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="inline-block px-2 py-1 rounded-full text-xs font-bold"
|
className="inline-block px-2 py-1 rounded-full text-xs font-bold"
|
||||||
@@ -501,11 +642,37 @@ const Card_display = () => {
|
|||||||
color: "var(--color-text-inverse)",
|
color: "var(--color-text-inverse)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{answerCount} válasz
|
{detectedType === 'joker' || detectedType === 'luck' ? (detectedType === 'joker' ? '🃏 JOKER' : '🎲 SZERENCSE') : `${answerCount} válasz`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{answerCount > 0 ? (
|
{detectedType === 'joker' ? (
|
||||||
|
// Joker card - just show the task/challenge
|
||||||
|
<div className="flex flex-col items-center justify-center h-full py-8">
|
||||||
|
<div className="text-6xl mb-4">🃏</div>
|
||||||
|
<div className="text-[color:var(--color-text)] text-center text-lg font-medium bg-[color:var(--color-fun)]/20 rounded-lg px-6 py-4 border-2 border-[color:var(--color-fun)]">
|
||||||
|
{questionText}
|
||||||
|
</div>
|
||||||
|
<div className="text-[color:var(--color-text-muted)] text-sm mt-4 text-center italic">
|
||||||
|
A játékmester dönti el a teljesítést
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : detectedType === 'luck' ? (
|
||||||
|
// Luck card - show consequence
|
||||||
|
<div className="flex flex-col items-center justify-center h-full py-8">
|
||||||
|
<div className="text-6xl mb-4">🎲</div>
|
||||||
|
{consequenceText && (
|
||||||
|
<div className="text-[color:var(--color-text)] text-center">
|
||||||
|
<div className="text-2xl font-bold mb-4 bg-[color:var(--color-luck)]/20 rounded-lg px-6 py-3 border-2 border-[color:var(--color-luck)]">
|
||||||
|
{consequenceText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="text-[color:var(--color-text-muted)] text-sm mt-2 text-center italic">
|
||||||
|
Azonnal végrehajt
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : answerCount > 0 ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
|
<div className="text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
|
||||||
Helyes válasz:
|
Helyes válasz:
|
||||||
@@ -563,6 +730,7 @@ const Card_display = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,362 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react"
|
|
||||||
import { useNavigate, useLocation } from "react-router-dom"
|
|
||||||
import Navbar from "../../components/Navbar/Navbar.jsx"
|
|
||||||
import Background from "../../assets/backgrounds/Background.jsx"
|
|
||||||
import Footer from "../../components/Footer/Footer.jsx"
|
|
||||||
import useRequireAuth from "../../hooks/useRequireAuth.jsx"
|
|
||||||
import ButtonGreen from "../../components/Buttons/ButtonGreen.jsx"
|
|
||||||
import {
|
|
||||||
FaFilter,
|
|
||||||
FaCalendarAlt,
|
|
||||||
FaArrowUp,
|
|
||||||
FaArrowDown,
|
|
||||||
FaSortAlphaDown,
|
|
||||||
FaSortAlphaUp,
|
|
||||||
FaQuestionCircle,
|
|
||||||
FaCheckCircle,
|
|
||||||
FaCircle,
|
|
||||||
} from "react-icons/fa"
|
|
||||||
import SearchBox from "../../components/Search/SearchBox.jsx"
|
|
||||||
import PopUp from "../../components/PopUp/PopUp.jsx"
|
|
||||||
import { motion } from "framer-motion"
|
|
||||||
|
|
||||||
const deckTypes = [
|
|
||||||
{ label: "Luck", color: "var(--color-luck)" },
|
|
||||||
{ label: "Question", color: "var(--color-question)" },
|
|
||||||
{ label: "Joker", color: "var(--color-fun)" },
|
|
||||||
]
|
|
||||||
|
|
||||||
const origins = ["Mind", "Vállalati", "Saját"]
|
|
||||||
|
|
||||||
const sortOptions = [
|
|
||||||
{ value: "date-asc", label: "📅↑" },
|
|
||||||
{ value: "date-desc", label: "📅↓" },
|
|
||||||
{ value: "abc-asc", label: "A→Z" },
|
|
||||||
{ value: "abc-desc", label: "Z→A" },
|
|
||||||
]
|
|
||||||
|
|
||||||
const ChooseDeck = () => {
|
|
||||||
const location = useLocation()
|
|
||||||
const locationUsername = location.state?.username ?? null
|
|
||||||
|
|
||||||
// always call hook (hooks must be called unconditionally) and use as fallback
|
|
||||||
const [authUsername] = useRequireAuth({ key: "username", redirectTo: "/" })
|
|
||||||
|
|
||||||
// prefer passed username (from navigate state) over authenticated username
|
|
||||||
const username = locationUsername ?? authUsername
|
|
||||||
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const [selectedType, setSelectedType] = useState("All")
|
|
||||||
const [selectedOrigin, setSelectedOrigin] = useState("Mind")
|
|
||||||
const [sortBy, setSortBy] = useState("date-desc")
|
|
||||||
const [search, setSearch] = useState("")
|
|
||||||
const [showSortHelp, setShowSortHelp] = useState(false)
|
|
||||||
const [allDecks, setAllDecks] = useState([])
|
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const [selectedDeckIds, setSelectedDeckIds] = useState([])
|
|
||||||
|
|
||||||
// Load all decks once
|
|
||||||
useEffect(() => {
|
|
||||||
let mounted = true
|
|
||||||
const load = async () => {
|
|
||||||
setLoading(true)
|
|
||||||
try {
|
|
||||||
const result = await import("../../api/deckApi.js").then((m) => m.getDecksPage(0, 99))
|
|
||||||
if (!mounted) return
|
|
||||||
|
|
||||||
console.log("Loaded decks:", result)
|
|
||||||
|
|
||||||
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,
|
|
||||||
}))
|
|
||||||
|
|
||||||
console.log("Mapped decks:", mapped)
|
|
||||||
setAllDecks(mapped)
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to load decks", err)
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
load()
|
|
||||||
return () => {
|
|
||||||
mounted = false
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Filter logic
|
|
||||||
let filteredDecks = allDecks.filter((deck) => {
|
|
||||||
const typeMatch = selectedType === "All" || deck.type === selectedType
|
|
||||||
const originMatch = selectedOrigin === "Mind" || deck.origin === selectedOrigin
|
|
||||||
const searchMatch = !search || deck.name.toLowerCase().includes(search.toLowerCase())
|
|
||||||
return typeMatch && originMatch && searchMatch
|
|
||||||
})
|
|
||||||
|
|
||||||
// Sort logic
|
|
||||||
filteredDecks = [...filteredDecks].sort((a, b) => {
|
|
||||||
if (sortBy === "date-asc") {
|
|
||||||
return a.created.localeCompare(b.created)
|
|
||||||
} else if (sortBy === "date-desc") {
|
|
||||||
return b.created.localeCompare(a.created)
|
|
||||||
} else if (sortBy === "abc-asc") {
|
|
||||||
return a.name.localeCompare(b.name)
|
|
||||||
} else if (sortBy === "abc-desc") {
|
|
||||||
return b.name.localeCompare(a.name)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// Toggle deck selection
|
|
||||||
const toggleDeckSelection = (deckId) => {
|
|
||||||
setSelectedDeckIds((prev) => {
|
|
||||||
if (prev.includes(deckId)) {
|
|
||||||
return prev.filter((id) => id !== deckId)
|
|
||||||
} else {
|
|
||||||
return [...prev, deckId]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle continue button
|
|
||||||
const handleContinue = () => {
|
|
||||||
if (selectedDeckIds.length === 0) {
|
|
||||||
alert("Kérlek válassz ki legalább egy paklit!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.log("Kiválasztott pakli ID-k:", selectedDeckIds)
|
|
||||||
navigate("/playersetup", { state: { deckIds: selectedDeckIds } })
|
|
||||||
}
|
|
||||||
|
|
||||||
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-24 pb-20">
|
|
||||||
<motion.section
|
|
||||||
className="max-w-6xl mx-auto"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.7 }}
|
|
||||||
>
|
|
||||||
{/* Title */}
|
|
||||||
<motion.h1
|
|
||||||
className="text-5xl font-extrabold text-green-300 mb-6 text-center tracking-wide drop-shadow-lg"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.8, delay: 0.1 }}
|
|
||||||
>
|
|
||||||
Válassz Paklikat a Játékhoz
|
|
||||||
</motion.h1>
|
|
||||||
|
|
||||||
<motion.p
|
|
||||||
className="text-lg leading-relaxed text-zinc-200 mb-10 text-center max-w-3xl mx-auto"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.8, delay: 0.2 }}
|
|
||||||
>
|
|
||||||
Válaszd ki azokat a paklikat, amelyekkel játszani szeretnél. Több paklit is kiválaszthatsz
|
|
||||||
egyszerre.
|
|
||||||
</motion.p>
|
|
||||||
|
|
||||||
{/* Filters */}
|
|
||||||
<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 flex-wrap">
|
|
||||||
<SearchBox
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
|
||||||
width={240}
|
|
||||||
placeholder="Keresés..."
|
|
||||||
className="mr-4"
|
|
||||||
/>
|
|
||||||
<FaFilter style={{ color: "var(--color-success)" }} className="mr-2" />
|
|
||||||
<span className="text-[color:var(--color-text)] font-semibold mr-2">Típus:</span>
|
|
||||||
<button
|
|
||||||
className={`px-3 py-1 rounded-lg font-medium transition-all duration-200 ${
|
|
||||||
selectedType === "All"
|
|
||||||
? "bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] border border-[color:var(--color-surface)]"
|
|
||||||
: "text-[color:var(--color-text)] bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30"
|
|
||||||
}`}
|
|
||||||
onClick={() => setSelectedType("All")}
|
|
||||||
>
|
|
||||||
Mind
|
|
||||||
</button>
|
|
||||||
{deckTypes.map((type) => (
|
|
||||||
<button
|
|
||||||
key={type.label}
|
|
||||||
className={`px-3 py-1 rounded-lg font-medium transition-all duration-200 ml-1 ${
|
|
||||||
selectedType === type.label
|
|
||||||
? "text-[color:var(--color-text-inverse)] border border-[color:var(--color-surface)]"
|
|
||||||
: "text-[color:var(--color-text)] bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30"
|
|
||||||
}`}
|
|
||||||
style={selectedType === type.label ? { background: type.color } : undefined}
|
|
||||||
onClick={() => setSelectedType(type.label)}
|
|
||||||
>
|
|
||||||
{type.label === "Luck" ? "Szerencse" : type.label === "Question" ? "Kérdés" : "Joker"}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
<span className="text-[color:var(--color-text)] font-semibold mr-2 ml-2">Eredet:</span>
|
|
||||||
<select
|
|
||||||
className="px-3 py-1 rounded-lg bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30 text-[color:var(--color-text)] border-none focus:ring-2 focus:ring-[color:var(--color-success)] outline-none"
|
|
||||||
value={selectedOrigin}
|
|
||||||
onChange={(e) => setSelectedOrigin(e.target.value)}
|
|
||||||
>
|
|
||||||
{origins.map((origin) => (
|
|
||||||
<option
|
|
||||||
key={origin}
|
|
||||||
value={origin}
|
|
||||||
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
|
|
||||||
>
|
|
||||||
{origin}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<span className="text-[color:var(--color-text)] font-semibold mr-2 ml-2 flex items-center gap-1">
|
|
||||||
Rendezés:
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="ml-1 text-[color:var(--color-success)] hover:text-[color:var(--color-text)] focus:outline-none"
|
|
||||||
onClick={() => setShowSortHelp(true)}
|
|
||||||
aria-label="Rendezési magyarázat"
|
|
||||||
style={{ fontSize: 18, lineHeight: 1 }}
|
|
||||||
>
|
|
||||||
<FaQuestionCircle />
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
<select
|
|
||||||
className="px-3 py-1 rounded-lg bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30 text-[color:var(--color-text)] border-none focus:ring-2 focus:ring-[color:var(--color-success)] outline-none"
|
|
||||||
value={sortBy}
|
|
||||||
onChange={(e) => setSortBy(e.target.value)}
|
|
||||||
>
|
|
||||||
{sortOptions.map((opt) => (
|
|
||||||
<option
|
|
||||||
key={opt.value}
|
|
||||||
value={opt.value}
|
|
||||||
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
|
|
||||||
>
|
|
||||||
{opt.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showSortHelp && (
|
|
||||||
<PopUp onClose={() => setShowSortHelp(false)}>
|
|
||||||
<h2 className="text-lg font-bold mb-4">Rendezési lehetőségek</h2>
|
|
||||||
<ul className="space-y-2 text-[color:var(--color-night)]">
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">📅↑</span> – Dátum szerint növekvő
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">📅↓</span> – Dátum szerint csökkenő
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">A→Z</span> – Név szerint növekvő
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span className="font-bold">Z→A</span> – Név szerint csökkenő
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<button
|
|
||||||
className="mt-6 px-4 py-2 rounded-lg bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)] font-semibold hover:bg-[color:var(--color-success)]/80"
|
|
||||||
onClick={() => setShowSortHelp(false)}
|
|
||||||
>
|
|
||||||
Bezárás
|
|
||||||
</button>
|
|
||||||
</PopUp>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Selection Info */}
|
|
||||||
<div className="mb-6 text-center">
|
|
||||||
<span className="text-[color:var(--color-text)] text-lg font-semibold">
|
|
||||||
Kiválasztva: {selectedDeckIds.length} pakli
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Decks Grid */}
|
|
||||||
<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">
|
|
||||||
{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 elérhető paklik.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!loading &&
|
|
||||||
filteredDecks.map((deck) => {
|
|
||||||
const deckType = deckTypes.find((t) => t.label === deck.type)
|
|
||||||
const borderColor = deckType ? deckType.color : "var(--color-success)"
|
|
||||||
const isSelected = selectedDeckIds.includes(deck.id)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={deck.id}
|
|
||||||
className={`relative flex flex-col justify-between h-48 bg-[color:var(--color-card)] rounded-2xl p-6 shadow-lg border-t-4 hover:scale-105 transition-transform duration-200 cursor-pointer ${
|
|
||||||
isSelected ? "ring-4 ring-[color:var(--color-success)]" : ""
|
|
||||||
}`}
|
|
||||||
style={{ borderTopColor: borderColor }}
|
|
||||||
onClick={() => toggleDeckSelection(deck.id)}
|
|
||||||
>
|
|
||||||
{/* Selection Indicator */}
|
|
||||||
<div className="absolute top-3 right-3">
|
|
||||||
{isSelected ? (
|
|
||||||
<FaCheckCircle className="text-3xl text-[color:var(--color-success)]" />
|
|
||||||
) : (
|
|
||||||
<FaCircle className="text-3xl text-[color:var(--color-text-muted)] opacity-30" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
className="inline-block px-3 py-1 rounded-full text-xs font-bold mb-2"
|
|
||||||
style={{
|
|
||||||
background: deckType?.color,
|
|
||||||
color: "var(--color-text-inverse)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{deck.type === "Luck" ? "Szerencse" : deck.type === "Question" ? "Kérdés" : "Joker"}
|
|
||||||
</span>
|
|
||||||
<h2 className="text-xl font-bold text-[color:var(--color-text)] mb-1 truncate">
|
|
||||||
{deck.name}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div className="text-[color:var(--color-text-muted)] text-sm mt-2">
|
|
||||||
Létrehozva: {deck.created}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Continue Button */}
|
|
||||||
<div className="flex justify-center mt-12">
|
|
||||||
<ButtonGreen
|
|
||||||
text={`Tovább (${selectedDeckIds.length} pakli kiválasztva)`}
|
|
||||||
onClick={handleContinue}
|
|
||||||
width="w-auto px-8"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</motion.section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer className="mt-auto">
|
|
||||||
<Footer />
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ChooseDeck
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import React, { useState } from "react"
|
|
||||||
import { useNavigate, useLocation } from "react-router-dom"
|
|
||||||
import Navbar from "../../components/Navbar/Navbar.jsx"
|
|
||||||
import Background from "../../assets/backgrounds/Background.jsx"
|
|
||||||
import Footer from "../../components/Footer/Footer.jsx"
|
|
||||||
import useRequireAuth from "../../hooks/useRequireAuth.jsx"
|
|
||||||
import ButtonGreen from "../../components/Buttons/ButtonGreen.jsx"
|
|
||||||
import { motion } from "framer-motion"
|
|
||||||
|
|
||||||
const GameLobbySetup = () => {
|
|
||||||
const [username] = useRequireAuth({ key: "username", redirectTo: "/login" })
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const location = useLocation()
|
|
||||||
|
|
||||||
const deckIds = location.state?.deckIds || []
|
|
||||||
|
|
||||||
const [maxPlayers, setMaxPlayers] = useState(4)
|
|
||||||
const [isPublic, setIsPublic] = useState(true)
|
|
||||||
|
|
||||||
const handleCreateLobby = () => {
|
|
||||||
console.log({
|
|
||||||
deckIds,
|
|
||||||
maxPlayers,
|
|
||||||
isPublic,
|
|
||||||
})
|
|
||||||
// Itt küldd el az API-nak a lobby létrehozását
|
|
||||||
// navigate("/game-lobby", { state: { lobbyId: response.lobbyId } })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deckIds.length === 0) {
|
|
||||||
navigate("/choose-deck")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
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-24 pb-20">
|
|
||||||
<motion.section
|
|
||||||
className="max-w-2xl mx-auto"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.7 }}
|
|
||||||
>
|
|
||||||
<motion.h1
|
|
||||||
className="text-5xl font-extrabold text-green-300 mb-6 text-center tracking-wide drop-shadow-lg"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.8, delay: 0.1 }}
|
|
||||||
>
|
|
||||||
Lobby Beállítások
|
|
||||||
</motion.h1>
|
|
||||||
|
|
||||||
<motion.p
|
|
||||||
className="text-lg leading-relaxed text-zinc-200 mb-10 text-center"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.8, delay: 0.2 }}
|
|
||||||
>
|
|
||||||
{deckIds.length} pakli kiválasztva. Add meg a játék részleteit.
|
|
||||||
</motion.p>
|
|
||||||
|
|
||||||
<div className="bg-[color:var(--color-surface)]/80 backdrop-blur-lg rounded-2xl p-8 shadow-lg space-y-6">
|
|
||||||
{/* Max Players */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-[color:var(--color-text)] font-semibold mb-2">
|
|
||||||
Maximális játékosszám:
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min="2"
|
|
||||||
max="10"
|
|
||||||
value={maxPlayers}
|
|
||||||
onChange={(e) => setMaxPlayers(parseInt(e.target.value) || 2)}
|
|
||||||
className="w-full px-4 py-2 rounded-lg bg-[color:var(--color-card)] text-[color:var(--color-text)] border border-[color:var(--color-surface)] focus:ring-2 focus:ring-[color:var(--color-success)] outline-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Public/Private */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-[color:var(--color-text)] font-semibold mb-2">Játék típusa:</label>
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<button
|
|
||||||
className={`flex-1 px-4 py-3 rounded-lg font-medium transition-all duration-200 ${
|
|
||||||
isPublic
|
|
||||||
? "bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)]"
|
|
||||||
: "bg-[color:var(--color-card)] text-[color:var(--color-text)] hover:bg-[color:var(--color-success)]/30"
|
|
||||||
}`}
|
|
||||||
onClick={() => setIsPublic(true)}
|
|
||||||
>
|
|
||||||
🌐 Publikus
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`flex-1 px-4 py-3 rounded-lg font-medium transition-all duration-200 ${
|
|
||||||
!isPublic
|
|
||||||
? "bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)]"
|
|
||||||
: "bg-[color:var(--color-card)] text-[color:var(--color-text)] hover:bg-[color:var(--color-success)]/30"
|
|
||||||
}`}
|
|
||||||
onClick={() => setIsPublic(false)}
|
|
||||||
>
|
|
||||||
🔒 Privát
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Action Buttons */}
|
|
||||||
<div className="flex justify-center gap-4 mt-8">
|
|
||||||
<ButtonGreen
|
|
||||||
text="Vissza"
|
|
||||||
onClick={() => navigate("/choose-deck")}
|
|
||||||
width="w-auto px-8"
|
|
||||||
className="bg-gray-600 hover:bg-gray-700"
|
|
||||||
/>
|
|
||||||
<ButtonGreen text="Lobby Létrehozása" onClick={handleCreateLobby} width="w-auto px-8" />
|
|
||||||
</div>
|
|
||||||
</motion.section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer className="mt-auto">
|
|
||||||
<Footer />
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GameLobbySetup
|
|
||||||
@@ -31,7 +31,7 @@ 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 min-h-[calc(100vh-64px)] flex mt-[64px] flex-col items-center justify-center">
|
<main className="flex-1 min-h-[calc(100vh-64px)] flex mt-[64px] flex-col items-center justify-center px-2 sm:px-4">
|
||||||
<PlayMenu
|
<PlayMenu
|
||||||
onJoinGame={handleJoinGame}
|
onJoinGame={handleJoinGame}
|
||||||
onCreateGame={handleCreateGame}
|
onCreateGame={handleCreateGame}
|
||||||
|
|||||||
+3
-2
@@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react"
|
import React, { useEffect, useRef, useState } from "react"
|
||||||
import { useNavigate, useLocation } from "react-router-dom"
|
import { useNavigate, useLocation } from "react-router-dom"
|
||||||
import Navbar from "../../components/Navbar/Navbar.jsx"
|
import Navbar from "../../components/Navbar/Navbar"
|
||||||
import Background from "../../assets/backgrounds/Background.jsx"
|
import Background from "../../assets/backgrounds/Background.jsx"
|
||||||
import useRequireAuth from "../../hooks/useRequireAuth.jsx"
|
import useRequireAuth from "../../hooks/useRequireAuth"
|
||||||
|
|
||||||
const Lobby = () => {
|
const Lobby = () => {
|
||||||
const [visible, setVisible] = useState(false)
|
const [visible, setVisible] = useState(false)
|
||||||
@@ -10,6 +10,7 @@ const Lobby = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
|
|
||||||
const [user, setUser] = useRequireAuth()
|
const [user, setUser] = useRequireAuth()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Reference in New Issue
Block a user