diff --git a/README.md b/README.md index 970ba8e..0767ad9 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,4 @@ The **docker folder** contains a couple of helper scripts: Afterwards, continue to start your project in the backend directory by running: - `yarn start` - admin commands tools for admin@flatlogic.com - admin_pass for admin@flatlogic.com - password for admin@flatlogic.com PASSWORD fac6a658 \ No newline at end of file + `yarn start` \ No newline at end of file diff --git a/frontend/public/assets/vm-shot-2026-08-07T19-27-16-851Z.jpg b/frontend/public/assets/vm-shot-2026-08-07T19-27-16-851Z.jpg deleted file mode 100644 index 3edc0d3..0000000 Binary files a/frontend/public/assets/vm-shot-2026-08-07T19-27-16-851Z.jpg and /dev/null differ diff --git a/frontend/public/assets/vm-shot-2026-08-07T19-27-26-780Z.jpg b/frontend/public/assets/vm-shot-2026-08-07T19-27-26-780Z.jpg deleted file mode 100644 index 3edc0d3..0000000 Binary files a/frontend/public/assets/vm-shot-2026-08-07T19-27-26-780Z.jpg and /dev/null differ diff --git a/frontend/public/assets/vm-shot-2026-08-07T19-28-03-715Z.jpg b/frontend/public/assets/vm-shot-2026-08-07T19-28-03-715Z.jpg deleted file mode 100644 index 3edc0d3..0000000 Binary files a/frontend/public/assets/vm-shot-2026-08-07T19-28-03-715Z.jpg and /dev/null differ diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 2ef67e9..6548433 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,5 +1,6 @@ -import React, { useEffect, useRef, useState } from 'react' +import React, {useEffect, useRef} from 'react' import Link from 'next/link' +import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 73d8391..1b9907d 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,4 +1,5 @@ -import React, { ReactNode, useEffect, useState } from 'react' +import React, { ReactNode, useEffect } from 'react' +import { useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index cdc330c..5cb10e8 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -7,13 +7,6 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiViewDashboardOutline, label: 'Dashboard', }, - { - href: '/play', - label: 'Play Game', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiGamepadVariant' in icon ? icon['mdiGamepadVariant' as keyof typeof icon] : icon.mdiTable, - }, { href: '/users/users-list', diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index af5d53e..ae1bc95 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,166 @@ -import React from 'react'; + +import React, { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; import Link from 'next/link'; +import BaseButton from '../components/BaseButton'; +import CardBox from '../components/CardBox'; +import SectionFullScreen from '../components/SectionFullScreen'; import LayoutGuest from '../layouts/Guest'; +import BaseDivider from '../components/BaseDivider'; +import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; +import { useAppSelector } from '../stores/hooks'; +import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; +import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; -const colorBursts = [ - 'bg-[#35F2C1]', - 'bg-[#FF4FD8]', - 'bg-[#FFD166]', - 'bg-[#7C5CFF]', - 'bg-[#38BDF8]', - 'bg-[#FF7A59]', -]; - -const arenaStats = [ - ['49', 'color blocks'], - ['4', 'rivals'], - ['45s', 'rounds'], -]; - -const floatingBlocks = [ - 'left-[6%] top-[18%] h-14 w-14 rotate-12 bg-[#FF4FD8] shadow-[#B8228D]', - 'right-[8%] top-[17%] h-16 w-16 -rotate-12 bg-[#FFD166] shadow-[#B88711]', - 'left-[12%] bottom-[14%] h-12 w-12 -rotate-6 bg-[#38BDF8] shadow-[#117DAA]', - 'right-[18%] bottom-[18%] h-10 w-10 rotate-6 bg-[#7C5CFF] shadow-[#4931B8]', -]; export default function Starter() { + const [illustrationImage, setIllustrationImage] = useState({ + src: undefined, + photographer: undefined, + photographer_url: undefined, + }) + const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) + const [contentType, setContentType] = useState('image'); + const [contentPosition, setContentPosition] = useState('left'); + const textColor = useAppSelector((state) => state.style.linkColor); + + const title = 'Steal a Color Game' + + // Fetch Pexels image/video + useEffect(() => { + async function fetchData() { + const image = await getPexelsImage(); + const video = await getPexelsVideo(); + setIllustrationImage(image); + setIllustrationVideo(video); + } + fetchData(); + }, []); + + const imageBlock = (image) => ( +
+
+ + Photo by {image?.photographer} on Pexels + +
+
+ ); + + const videoBlock = (video) => { + if (video?.video_files?.length > 0) { + return ( +
+ +
+ + Video by {video.user.name} on Pexels + +
+
) + } + }; + return ( - <> +
- {getPageTitle('Steal a Color')} - + {getPageTitle('Starter Page')} -
-
-
-
-
-
-
- - {floatingBlocks.map((block) => ( -
-
- +
); } Starter.getLayout = function getLayout(page: ReactElement) { return {page}; }; + diff --git a/frontend/src/pages/play.tsx b/frontend/src/pages/play.tsx deleted file mode 100644 index 9e86147..0000000 --- a/frontend/src/pages/play.tsx +++ /dev/null @@ -1,471 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import type { ReactElement } from 'react'; -import Head from 'next/head'; -import Link from 'next/link'; -import LayoutGuest from '../layouts/Guest'; -import { getPageTitle } from '../config'; - -type Owner = 0 | 1 | 2 | 3; -type Phase = 'setup' | 'playing' | 'finished'; - -type Player = { - id: Owner; - name: string; - color: string; - textColor: string; - glow: string; - shade: string; -}; - -type MatchRecord = { - id: string; - playerName: string; - score: number; - winner: string; - won: boolean; - captured: number; - createdAt: string; - board: Owner[]; -}; - -const boardSize = 49; -const matchSeconds = 45; -const historyKey = 'steal-a-color-history'; - -const players: Player[] = [ - { id: 0, name: 'You', color: '#35F2C1', textColor: 'text-[#087560]', glow: 'shadow-[#159875]/35', shade: '#159875' }, - { id: 1, name: 'Pixel', color: '#FF4FD8', textColor: 'text-[#B8228D]', glow: 'shadow-[#B8228D]/30', shade: '#B8228D' }, - { id: 2, name: 'Volt', color: '#FFD166', textColor: 'text-[#9B6A00]', glow: 'shadow-[#B88711]/30', shade: '#B88711' }, - { id: 3, name: 'Nova', color: '#7C5CFF', textColor: 'text-[#4931B8]', glow: 'shadow-[#4931B8]/30', shade: '#4931B8' }, -]; - -const createBoard = () => - Array.from({ length: boardSize }, (_, index) => { - if (index === 0 || index === 1 || index === 7) { - return 0; - } - - if (index === 5 || index === 6 || index === 13) { - return 1; - } - - if (index === 35 || index === 42 || index === 43) { - return 2; - } - - if (index === 41 || index === 47 || index === 48) { - return 3; - } - - return ((index + Math.floor(index / 7)) % 4) as Owner; - }); - -const getCounts = (board: Owner[]) => - players.map((player) => ({ - ...player, - tiles: board.filter((owner) => owner === player.id).length, - })); - -const loadHistory = (): MatchRecord[] => { - if (typeof window === 'undefined') { - return []; - } - - try { - const stored = window.localStorage.getItem(historyKey); - if (!stored) { - return []; - } - - const parsed = JSON.parse(stored); - return Array.isArray(parsed) ? parsed : []; - } catch (error) { - console.error('Failed to load Steal a Color match history', error); - return []; - } -}; - -const saveHistory = (records: MatchRecord[]) => { - try { - window.localStorage.setItem(historyKey, JSON.stringify(records)); - } catch (error) { - console.error('Failed to save Steal a Color match history', error); - } -}; - -const formatDate = (value: string) => - new Intl.DateTimeFormat('en', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }).format(new Date(value)); - -export default function PlayStealAColor() { - const [phase, setPhase] = useState('setup'); - const [playerName, setPlayerName] = useState('Player One'); - const [board, setBoard] = useState(createBoard); - const [secondsLeft, setSecondsLeft] = useState(matchSeconds); - const [captured, setCaptured] = useState(0); - const [history, setHistory] = useState([]); - const [selectedMatchId, setSelectedMatchId] = useState(null); - const [message, setMessage] = useState('Name your blocky avatar, start a match, then smack rival color blocks to steal them.'); - - useEffect(() => { - const records = loadHistory(); - setHistory(records); - setSelectedMatchId(records[0]?.id ?? null); - }, []); - - const counts = useMemo(() => getCounts(board), [board]); - const leader = useMemo(() => [...counts].sort((a, b) => b.tiles - a.tiles)[0], [counts]); - const playerScore = counts[0]?.tiles ?? 0; - const selectedMatch = history.find((record) => record.id === selectedMatchId) ?? history[0]; - - const finishMatch = useCallback( - (finalBoard: Owner[]) => { - const finalCounts = getCounts(finalBoard); - const winner = [...finalCounts].sort((a, b) => b.tiles - a.tiles)[0]; - const record: MatchRecord = { - id: `${Date.now()}`, - playerName: playerName.trim() || 'Player One', - score: finalCounts[0].tiles, - winner: winner.name, - won: winner.id === 0, - captured, - createdAt: new Date().toISOString(), - board: finalBoard, - }; - const nextHistory = [record, ...history].slice(0, 6); - setPhase('finished'); - setMessage(winner.id === 0 ? 'Victory! Your color owns the arena.' : `${winner.name} stole the crown. Hit rematch and take it back.`); - setHistory(nextHistory); - setSelectedMatchId(record.id); - saveHistory(nextHistory); - }, - [captured, history, playerName] - ); - - useEffect(() => { - if (phase !== 'playing') { - return undefined; - } - - if (secondsLeft <= 0) { - finishMatch(board); - return undefined; - } - - const timer = window.setTimeout(() => setSecondsLeft((current) => current - 1), 1000); - return () => window.clearTimeout(timer); - }, [board, finishMatch, phase, secondsLeft]); - - useEffect(() => { - if (phase !== 'playing') { - return undefined; - } - - const aiTimer = window.setInterval(() => { - setBoard((currentBoard) => { - const nextBoard = [...currentBoard]; - const ai = (1 + Math.floor(Math.random() * 3)) as Owner; - const candidates = nextBoard - .map((owner, index) => ({ owner, index })) - .filter((tile) => tile.owner !== ai); - - if (candidates.length === 0) { - return currentBoard; - } - - const target = candidates[Math.floor(Math.random() * candidates.length)]; - nextBoard[target.index] = ai; - return nextBoard; - }); - }, 1050); - - return () => window.clearInterval(aiTimer); - }, [phase]); - - const startMatch = () => { - const safeName = playerName.trim(); - if (!safeName) { - setMessage('Please add a player name before starting.'); - return; - } - - setBoard(createBoard()); - setSecondsLeft(matchSeconds); - setCaptured(0); - setPhase('playing'); - setMessage(`Go ${safeName}! Tap any rival block to flip it mint.`); - }; - - const stealTile = (index: number) => { - if (phase !== 'playing') { - setMessage('Press Start first to unlock the arena.'); - return; - } - - setBoard((currentBoard) => { - if (currentBoard[index] === 0) { - setMessage('That block already belongs to you. Find a rival color.'); - return currentBoard; - } - - const nextBoard = [...currentBoard]; - nextBoard[index] = 0; - setCaptured((current) => current + 1); - setMessage('Steal confirmed! Keep the mint wave rolling.'); - return nextBoard; - }); - }; - - const clearHistory = () => { - setHistory([]); - setSelectedMatchId(null); - saveHistory([]); - setMessage('Local match history cleared. The arena is reset.'); - }; - - return ( - <> - - {getPageTitle('Play Steal a Color')} - - -
-
-
-
-
-
-
-
- - - {players.map((player) => ( - - ))} - - - Steal a Color - Block Arena - - - - -
- -
- - -
-
-
-

Arena

-

Tap-to-steal blocks

-
-
-
- Time - 0:{String(secondsLeft).padStart(2, '0')} -
-
- Your blocks - {playerScore} -
-
-
- -
- {counts.map((player) => ( -
- -

{player.name === 'You' ? playerName || 'You' : player.name}

-

{player.tiles}

-
- ))} -
- -
- {board.map((owner, index) => { - const tileOwner = players[owner]; - return ( - - ); - })} -
- - {phase === 'finished' ? ( -
-

Results

-

- {leader.id === 0 ? 'You won the color crown!' : `${leader.name} wins this round.`} -

-

- {playerName.trim() || 'Player One'} finished with {playerScore} blocks and made {captured} manual steals. -

-
- ) : null} -
- - -
-
-
-
- - ); -} - -PlayStealAColor.getLayout = function getLayout(page: ReactElement) { - return {page}; -};