10 Commits

13 changed files with 168 additions and 86 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
/* build-hook-start *//*00001*/try { require('c:\\Users\\magdo\\.vscode\\extensions\\wallabyjs.console-ninja-1.0.475\\out\\buildHook\\index.js').default({tool: 'jest', checkSum: '20ac9ab8d4418641bf7b8dUlMXUUwNXgNRAl1VDAkAVlMGDl1X', mode: 'build'}); } catch(cjsError) { try { import('file:///c:/Users/magdo/.vscode/extensions/wallabyjs.console-ninja-1.0.475/out/buildHook/index.js').then(m => m.default.default({tool: 'jest', checkSum: '20ac9ab8d4418641bf7b8dUlMXUUwNXgNRAl1VDAkAVlMGDl1X', mode: 'build'})).catch(esmError => {}) } catch(esmError) {}}/* build-hook-end */ /* build-hook-start *//*00001*/try { require('c:\\Users\\magdo\\.vscode\\extensions\\wallabyjs.console-ninja-1.0.483\\out\\buildHook\\index.js').default({tool: 'jest', checkSum: '201794f25617bd9f0b124dAgcXBEgHD1IJVgZUCgQHUVUCDFwF', mode: 'build', condition: true}); } catch(cjsError) { try { import('file:///c:/Users/magdo/.vscode/extensions/wallabyjs.console-ninja-1.0.483/out/buildHook/index.js').then(m => m.default.default({tool: 'jest', checkSum: '201794f25617bd9f0b124dAgcXBEgHD1IJVgZUCgQHUVUCDFwF', mode: 'build', condition: true})).catch(esmError => {}) } catch(esmError) {}}/* build-hook-end */
/*! /*!
* /** * /**
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
/* build-hook-start *//*00001*/try { require('c:\\Users\\magdo\\.vscode\\extensions\\wallabyjs.console-ninja-1.0.475\\out\\buildHook\\index.js').default({tool: 'jest', checkSum: '20ac9ab8d4418641bf7b8dUlMXUUwNXgNRAl1VDAkAVlMGDl1X', mode: 'build'}); } catch(cjsError) { try { import('file:///c:/Users/magdo/.vscode/extensions/wallabyjs.console-ninja-1.0.475/out/buildHook/index.js').then(m => m.default.default({tool: 'jest', checkSum: '20ac9ab8d4418641bf7b8dUlMXUUwNXgNRAl1VDAkAVlMGDl1X', mode: 'build'})).catch(esmError => {}) } catch(esmError) {}}/* build-hook-end */ /* build-hook-start *//*00001*/try { require('c:\\Users\\magdo\\.vscode\\extensions\\wallabyjs.console-ninja-1.0.483\\out\\buildHook\\index.js').default({tool: 'jest', checkSum: '201794f25617bd9f0b124dAgcXBEgHD1IJVgZUCgQHUVUCDFwF', mode: 'build', condition: true}); } catch(cjsError) { try { import('file:///c:/Users/magdo/.vscode/extensions/wallabyjs.console-ninja-1.0.483/out/buildHook/index.js').then(m => m.default.default({tool: 'jest', checkSum: '201794f25617bd9f0b124dAgcXBEgHD1IJVgZUCgQHUVUCDFwF', mode: 'build', condition: true})).catch(esmError => {}) } catch(esmError) {}}/* build-hook-end */
/** /**
@@ -23,9 +23,9 @@ async function isTokenBlacklisted(token: string): Promise<boolean> {
/** /**
* Extract token from request (cookie or Authorization header) * Extract token from request (cookie or Authorization header)
*/ */
function extractToken(req: Request): string | null { function extractToken(req: Request, type: 'auth' | 'refresh'): string | null {
// First try to get token from cookie // First try to get token from cookie
const cookieToken = req.cookies['auth_token']; const cookieToken = req.cookies[`${type}_token`];
if (cookieToken) { if (cookieToken) {
return cookieToken; return cookieToken;
} }
@@ -42,8 +42,9 @@ function extractToken(req: Request): string | null {
export async function authRequired(req: Request, res: Response, next: NextFunction) { export async function authRequired(req: Request, res: Response, next: NextFunction) {
try { try {
// Extract token from request // Extract token from request
const token = extractToken(req); const token = extractToken(req, "auth");
if (!token) { const refreshToken = extractToken(req, "refresh");
if (!token || !refreshToken) {
logAuth('Authentication failed - No token provided', undefined, { logAuth('Authentication failed - No token provided', undefined, {
ip: req.ip, ip: req.ip,
userAgent: req.get ? req.get('User-Agent') : 'unknown', userAgent: req.get ? req.get('User-Agent') : 'unknown',
@@ -95,8 +96,9 @@ export async function authRequired(req: Request, res: Response, next: NextFuncti
export async function adminRequired(req: Request, res: Response, next: NextFunction) { export async function adminRequired(req: Request, res: Response, next: NextFunction) {
try { try {
// Extract token from request // Extract token from request
const token = extractToken(req); const token = extractToken(req, "auth");
if (!token) { const refreshToken = extractToken(req, "refresh");
if (!token || !refreshToken) {
logWarning('Admin access denied - No token provided', { logWarning('Admin access denied - No token provided', {
ip: req.ip, ip: req.ip,
path: req.path path: req.path
@@ -281,9 +281,7 @@ export class JWTService {
} else { } else {
// For cookie auth, create token pair and set cookies // For cookie auth, create token pair and set cookies
const newTokenPair = this.create(freshPayload, res); const newTokenPair = this.create(freshPayload, res);
res.setHeader('X-New-Access-Token', newTokenPair.accessToken); this.setTokenCookies(res, newTokenPair);
res.setHeader('X-New-Refresh-Token', newTokenPair.refreshToken);
res.setHeader('X-Token-Refreshed', 'true');
} }
return true; return true;
+1 -1
View File
@@ -42,7 +42,7 @@ EMAIL_PORT=465
EMAIL_SECURE=true EMAIL_SECURE=true
EMAIL_USER=noreply@serpentrace.hu EMAIL_USER=noreply@serpentrace.hu
EMAIL_PASS=ZUx720ece&Cin&F{ EMAIL_PASS=ZUx720ece&Cin&F{
EMAIL_FROM=noreply@serpentrace.com EMAIL_FROM=noreply@serpentrace.hu
# CHAT SYSTEM CONFIGURATION # CHAT SYSTEM CONFIGURATION
CHAT_INACTIVITY_TIMEOUT_MINUTES=30 CHAT_INACTIVITY_TIMEOUT_MINUTES=30
+25
View File
@@ -0,0 +1,25 @@
import { apiClient } from './userApi'
// Create a new deck in the backend
export const createDeck = async (deck) => {
try {
const response = await apiClient.post('/decks', deck)
return response.data
} catch (err) {
throw err
}
}
// Get paginated decks (authenticated)
export const getDecksPage = async (from = 0, to = 49) => {
try {
const response = await apiClient.get(`/decks/page/${from}/${to}`)
return response.data
} catch (err) {
throw err
}
}
export default {
createDeck
}
+6 -34
View File
@@ -1,54 +1,26 @@
import axios from "axios" import axios from "axios"
export const API_CONFIG = { export const API_CONFIG = {
baseURL: import.meta.env.VITE_API_URL + "/api", baseURL: (import.meta.env.VITE_API_URL ? import.meta.env.VITE_API_URL : '') + "/api",
wsURL: "http://localhost:3000", wsURL: "http://localhost:3000",
timeout: 10000, timeout: 10000,
retryAttempts: 3, retryAttempts: 3,
} }
const apiClient = axios.create({ export const apiClient = axios.create({
baseURL: API_CONFIG.baseURL, baseURL: API_CONFIG.baseURL,
timeout: API_CONFIG.timeout, timeout: API_CONFIG.timeout,
withCredentials: true, // Important for cookie-based auth withCredentials: true, // Important for cookie-based auth
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json'
},
})
// Add request interceptor for debugging
apiClient.interceptors.request.use(
(config) => {
console.log("Request URL:", config.url)
console.log("Request headers:", config.headers)
console.log("Current cookies:", document.cookie)
return config
},
(error) => {
return Promise.reject(error)
} }
) });
// Add response interceptor for debugging cookies
apiClient.interceptors.response.use(
(response) => {
console.log("Response status:", response.status)
console.log("Response headers:", response.headers)
console.log("Set-Cookie headers:", response.headers["set-cookie"])
console.log("Cookies after response:", document.cookie)
return response
},
(error) => {
console.error("API Error:", error.response?.data || error.message)
return Promise.reject(error)
}
)
//login //login
export const login = async (username, password) => { export const login = async (username, password) => {
try { try {
const response = await apiClient.post("/users/login", { username, password }) const response = await apiClient.post("/users/login", { username, password })
return response.data return response
} catch (error) { } catch (error) {
throw error throw error
} }
@@ -58,7 +30,7 @@ export const login = async (username, password) => {
export const register = async (username, email, password, fname, lname, phone) => { export const register = async (username, email, password, fname, lname, phone) => {
try { try {
const response = await apiClient.post("/users/create", { username, email, password, fname, lname, phone }) const response = await apiClient.post("/users/create", { username, email, password, fname, lname, phone })
return { ...response.data, status: response.status } return response
} catch (error) { } catch (error) {
throw error throw error
} }
@@ -1,4 +1,4 @@
import React, { useState } from "react" import React, { useState, useEffect } from "react"
import { useNavigate } from "react-router-dom" import { useNavigate } from "react-router-dom"
import { import {
FaPlus, FaPlus,
@@ -20,19 +20,7 @@ const deckTypes = [
{ label: "Fun", color: "var(--color-fun)" }, { label: "Fun", color: "var(--color-fun)" },
] ]
const mockDecks = [ // initial state will be fetched from backend
// Just for visual mockup
{ id: 1, name: "Party Luck", type: "Luck", created: "2025-07-01", origin: "Vállalati" },
{ id: 2, name: "Quiz Night", type: "Question", created: "2025-07-02", origin: "Saját" },
{ id: 3, name: "Fun Times", type: "Fun", created: "2025-07-03", origin: "Vállalati" },
{ id: 4, name: "Corporate Challenge", type: "Question", created: "2025-07-04", origin: "Vállalati" },
{ id: 5, name: "Randomizer", type: "Luck", created: "2025-07-05", origin: "Saját" },
{ id: 6, name: "Afterwork luck", type: "Luck", created: "2025-07-06", origin: "Saját" },
{ id: 7, name: "Serpent Quiz", type: "Question", created: "2025-07-07", origin: "Vállalati" },
{ id: 8, name: "Green Fortune", type: "Luck", created: "2025-07-08", origin: "Vállalati" },
{ id: 9, name: "Team Builder", type: "Fun", created: "2025-07-09", origin: "Saját" },
{ id: 10, name: "Knowledge Race", type: "Question", created: "2025-07-10", origin: "Saját" },
]
const origins = ["Mind", "Vállalati", "Saját"] const origins = ["Mind", "Vállalati", "Saját"]
@@ -82,9 +70,39 @@ const DeckManager = () => {
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
const [showSortHelp, setShowSortHelp] = useState(false) const [showSortHelp, setShowSortHelp] = useState(false)
const [selectedDeck, setSelectedDeck] = useState(null) const [selectedDeck, setSelectedDeck] = useState(null)
const [decks, setDecks] = useState([])
const [loading, setLoading] = useState(false)
// Filter logic (mock) useEffect(() => {
let filteredDecks = mockDecks.filter((deck) => { let mounted = true
const load = async () => {
setLoading(true)
try {
const result = await import('../../api/deckApi').then(m => m.getDecksPage(0, 49))
if (!mounted) return
// map backend deck shape to UI shape
const mapped = result.decks.map(d => ({
id: d.id,
name: d.name,
type: d.type === 2 ? 'Question' : d.type === 1 ? 'Joker' : 'Luck',
created: d.creationdate ? new Date(d.creationdate).toLocaleDateString() : '',
origin: d.ctype === 2 ? 'Vállalati' : d.ctype === 0 ? 'Mind' : 'Saját',
raw: d
}))
setDecks(mapped)
} catch (err) {
console.error('Failed to load decks', err)
} finally {
setLoading(false)
}
}
load()
return () => { mounted = false }
}, [])
// Filter logic
const sourceDecks = decks
let filteredDecks = sourceDecks.filter((deck) => {
const typeMatch = selectedType === "All" || deck.type === selectedType const typeMatch = selectedType === "All" || deck.type === selectedType
const originMatch = selectedOrigin === "Mind" || deck.origin === selectedOrigin const originMatch = selectedOrigin === "Mind" || deck.origin === selectedOrigin
const searchMatch = !search || deck.name.toLowerCase().includes(search.toLowerCase()) const searchMatch = !search || deck.name.toLowerCase().includes(search.toLowerCase())
@@ -255,8 +273,14 @@ const DeckManager = () => {
<FaPlus style={{ color: "var(--color-success)" }} className="text-5xl mb-2" /> <FaPlus style={{ color: "var(--color-success)" }} className="text-5xl mb-2" />
<span className="text-[color:var(--color-text)] font-semibold">Új pakli létrehozása</span> <span className="text-[color:var(--color-text)] font-semibold">Új pakli létrehozása</span>
</div> </div>
{/* Existing Decks (Mockup) */} {/* Existing Decks (from backend) */}
{filteredDecks.map((deck) => { {loading && (
<div className="col-span-full text-center text-[color:var(--color-text-muted)]">Betöltés...</div>
)}
{!loading && filteredDecks.length === 0 && (
<div className="col-span-full text-center text-[color:var(--color-text-muted)]">Nincsenek mentett paklik.</div>
)}
{!loading && filteredDecks.map((deck) => {
const deckType = deckTypes.find((t) => t.label === deck.type) const deckType = deckTypes.find((t) => t.label === deck.type)
const borderColor = deckType ? deckType.color : "var(--color-success)" const borderColor = deckType ? deckType.color : "var(--color-success)"
return ( return (
@@ -14,6 +14,10 @@ import {
export default function DeckInfoPopUp({ deck, onClose }) { export default function DeckInfoPopUp({ deck, onClose }) {
if (!deck) return null if (!deck) return null
// Debug: Log the deck structure to see what we're working with
console.log('Deck in popup:', deck)
console.log('Cards:', deck.cards)
// Scroll blokkolás amikor a popup nyitva van // Scroll blokkolás amikor a popup nyitva van
useEffect(() => { useEffect(() => {
// Scroll letiltása // Scroll letiltása
@@ -33,13 +37,25 @@ export default function DeckInfoPopUp({ deck, onClose }) {
const currentDeckType = deckTypes[deck.type] || { label: deck.type, color: "var(--color-success)" } const currentDeckType = deckTypes[deck.type] || { label: deck.type, color: "var(--color-success)" }
// Mock data - ezeket majd a backend adatokra cseréljük // Use real deck data with safe fallbacks
const creator = deck.creatorName || deck.creator || (deck.user && deck.user.name) || "Ismeretlen"
const privacy = deck.origin === "Vállalati" || deck.ctype === 1 || deck.privacy === 'public' ? "Publikus" : "Privát"
// Get data from raw if available
const rawData = deck.raw || deck
// Use played number from raw data for answers count
const questionsCount = rawData.cardCount || 0
const answersCount = rawData.playedNumber || 0
console.log('Calculated counts:', { questionsCount, answersCount, rawData })
const mockData = { const mockData = {
creator: "John Doe", ...deck,
privacy: deck.origin === "Vállalati" ? "Publikus" : "Privát", creator,
questionsCount: Math.floor(Math.random() * 50) + 10, privacy,
answersCount: Math.floor(Math.random() * 200) + 50, questionsCount,
...deck answersCount
} }
const formatDate = (dateString) => { const formatDate = (dateString) => {
@@ -5,7 +5,7 @@ import InputBox from "../../components/Inputs/InputBox"
import Button from "../../components/Buttons/Button" import Button from "../../components/Buttons/Button"
import { motion } from "framer-motion" import { motion } from "framer-motion"
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
import { useLocation } from "react-router-dom" import { useLocation, useNavigate } from "react-router-dom"
import { login } from "../../api/userApi" import { login } from "../../api/userApi"
export default function LoginForm() { export default function LoginForm() {
@@ -13,7 +13,9 @@ export default function LoginForm() {
const [password, setPassword] = useState("") const [password, setPassword] = useState("")
const [error, setError] = useState("") const [error, setError] = useState("")
const location = useLocation() const location = useLocation()
const navigate = useNavigate()
const [showSuccess, setShowSuccess] = useState(false) const [showSuccess, setShowSuccess] = useState(false)
const [showErrorPopup, setShowErrorPopup] = useState(false)
useEffect(() => { useEffect(() => {
if (location.state && location.state.success) { if (location.state && location.state.success) {
@@ -29,22 +31,40 @@ export default function LoginForm() {
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault() e.preventDefault()
setError("") setError("")
setShowErrorPopup(false)
if (!email || !password) { if (!email || !password) {
setError("Minden mező kitöltése kötelező.") setError("Minden mező kitöltése kötelező.")
setShowErrorPopup(true)
setTimeout(() => setShowErrorPopup(false), 2000)
return return
} }
if (!validateEmail(email)) { if (!validateEmail(email)) {
setError("Hibás email formátum.") setError("Hibás email formátum.")
setShowErrorPopup(true)
setTimeout(() => setShowErrorPopup(false), 2000)
return return
} }
// Backend API // Backend API
login(email, password) login(email, password)
.then((data) => { .then((response) => {
console.log(data) console.log(response)
console.log("Bejelentkezés:", { email, password }) // Csak a response.status-t ellenőrizd!
if (response && response.status === 200) {
if (response.data && response.data.user) {
localStorage.setItem("username", response.data.user.username)
localStorage.setItem("authLevel", response.data.user.authLevel)
}
navigate("/home")
} else {
setError("Hibás bejelentkezési adatok.")
setShowErrorPopup(true)
setTimeout(() => setShowErrorPopup(false), 2000)
}
}) })
.catch((error) => { .catch((error) => {
setError("Hibás bejelentkezési adatok.") setError("Hibás bejelentkezési adatok.")
setShowErrorPopup(true)
setTimeout(() => setShowErrorPopup(false), 2000)
}) })
} }
@@ -62,7 +82,11 @@ export default function LoginForm() {
Sikeres regisztráció! Az email ellenőrzése után be tudsz lépni. Sikeres regisztráció! Az email ellenőrzése után be tudsz lépni.
</div> </div>
)} )}
{error && <div className="mb-4 text-red-600 text-center font-semibold">{error}</div>} {showErrorPopup && error && (
<div className="fixed top-6 left-1/2 -translate-x-1/2 bg-red-500 text-white px-6 py-2 rounded shadow-lg z-50 text-center font-semibold transition-opacity duration-300">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
<InputBox <InputBox
type="email" type="email"
@@ -51,12 +51,10 @@ export default function RegisterForm() {
// Check for 201 Created status // Check for 201 Created status
if (response && response.status === 201) { if (response && response.status === 201) {
navigate("/login", { state: { success: true } }) navigate("/login", { state: { success: true } })
console.log(response)
console.log("Regisztráció:", { username, email, password, firstname, lastname, phone })
} else { } else {
let msg = "Sikertelen regisztráció." let msg = "Sikertelen regisztráció."
if (response && response.error) { if (response && response.data && response.data.error) {
msg = response.error msg = response.data.error
} }
setError(msg) setError(msg)
setShowErrorPopup(true) setShowErrorPopup(true)
@@ -7,6 +7,7 @@ import Navbar from "../../components/Navbar/Navbar.jsx"
import DeckHeader from "../../components/DeckCreator/DeckHeader.jsx" import DeckHeader from "../../components/DeckCreator/DeckHeader.jsx"
import CardsList from "../../components/DeckCreator/CardsList.jsx" import CardsList from "../../components/DeckCreator/CardsList.jsx"
import CardEditor from "../../components/DeckCreator/CardEditor.jsx" import CardEditor from "../../components/DeckCreator/CardEditor.jsx"
import { createDeck } from '../../api/deckApi'
export default function DeckCreator() { export default function DeckCreator() {
const { deckId } = useParams() // URL-ből deck ID (új deck esetén undefined) const { deckId } = useParams() // URL-ből deck ID (új deck esetén undefined)
@@ -86,12 +87,23 @@ export default function DeckCreator() {
const handleSaveDeck = async () => { const handleSaveDeck = async () => {
try { try {
// TODO: API mentés
console.log("Deck mentése:", deck) console.log("Deck mentése:", deck)
alert("✅ Deck sikeresen mentve!")
const payload = {
name: deck.name,
type: (deck.type || 'Question').toString().toUpperCase(),
ctype: deck.privacy === 'public' ? 'PUBLIC' : 'PRIVATE',
description: deck.description || '',
cards: deck.cards.map(c => ({ ...c, text: c.question || c.statement || c.text || '' }))
}
const saved = await createDeck(payload)
setDeck(prev => ({ ...prev, id: saved.id ?? prev.id, creationdate: saved.creationdate ?? prev.creationdate, updatedate: saved.updatedate ?? prev.updatedate }))
console.log('Deck saved (backend):', saved)
alert('✅ Deck sikeresen mentve!')
} catch (error) { } catch (error) {
console.error("Mentési hiba:", error) console.error('Mentési hiba:', error)
alert("❌ Hiba történt a mentés során!") alert('❌ Hiba történt a mentés során: ' + (error?.response?.data?.error || error.message || String(error)))
} }
} }
+13 -2
View File
@@ -1,13 +1,24 @@
// src/pages/Home/Home.jsx // src/pages/Home/Home.jsx
// Régi PlayMenu-s oldal, "Home" néven // Régi PlayMenu-s oldal, "Home" néven
import { useState } from "react" import { useState, useEffect } from "react"
import { useNavigate } from "react-router-dom"
import Navbar from "../../components/Navbar/Navbar" import Navbar from "../../components/Navbar/Navbar"
import Footer from "../../components/Footer/Footer.jsx" import Footer from "../../components/Footer/Footer.jsx"
import Background from "../../assets/backgrounds/Background.jsx" import Background from "../../assets/backgrounds/Background.jsx"
import PlayMenu from "../../components/Landingpage/PlayMenu.jsx" import PlayMenu from "../../components/Landingpage/PlayMenu.jsx"
export default function Home() { export default function Home() {
const navigate = useNavigate()
useEffect(() => {
const username = localStorage.getItem("username")
const authLevel = localStorage.getItem("authLevel")
if (!username || !authLevel) {
navigate("/login")
}
}, [navigate])
// Dummy callbackok és user példa // Dummy callbackok és user példa
const handleJoinGame = (code) => { const handleJoinGame = (code) => {
alert(`Csatlakozás játékhoz: ${code}`) alert(`Csatlakozás játékhoz: ${code}`)
@@ -15,7 +26,7 @@ export default function Home() {
const handleCreateGame = () => { const handleCreateGame = () => {
alert("Új játék létrehozása") alert("Új játék létrehozása")
} }
const user = { name: "Teszt Elek" } const user = { name: localStorage.getItem("username") }
return ( return (
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden"> <div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">