WIP: Working login, and creation of Lobby, aswell as showing new Users
This commit is contained in:
22
src/app.d.ts
vendored
22
src/app.d.ts
vendored
@@ -5,11 +5,7 @@ declare global {
|
|||||||
namespace App {
|
namespace App {
|
||||||
// interface Error {}
|
// interface Error {}
|
||||||
interface Locals {
|
interface Locals {
|
||||||
// user: {
|
user: User | null;
|
||||||
// isLoggedIn: boolean;
|
|
||||||
// email: string | null;
|
|
||||||
// username: string | null;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface PageState {}
|
// interface PageState {}
|
||||||
@@ -18,15 +14,15 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
email: string | null;
|
email: string;
|
||||||
username: string | null;
|
username: string;
|
||||||
}
|
|
||||||
|
|
||||||
export interface Player {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
isHost: boolean;
|
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
// export interface Player {
|
||||||
|
// id: number;
|
||||||
|
// name: string;
|
||||||
|
// isHost: boolean;
|
||||||
|
// }
|
||||||
|
|
||||||
export interface GameMode {
|
export interface GameMode {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,44 +1,50 @@
|
|||||||
// import { redirect, type Handle } from '@sveltejs/kit';
|
import { type Handle, type HandleFetch } from '@sveltejs/kit';
|
||||||
//
|
|
||||||
// export const handle: Handle = async ({ event, resolve }) => {
|
|
||||||
// const sessionId = event.cookies.get('session_id');
|
|
||||||
// let user = {
|
|
||||||
// isLoggedIn: false,
|
|
||||||
// email: '',
|
|
||||||
// username: ''
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// if (sessionId) {
|
|
||||||
// const response = await fetch('http://hitstar.xyz/api/user/me', {
|
|
||||||
// headers: {
|
|
||||||
// 'Content-Type': 'application/json'
|
|
||||||
// },
|
|
||||||
// credentials: 'include'
|
|
||||||
// });
|
|
||||||
// console.log(response.status);
|
|
||||||
// console.log(await response.text());
|
|
||||||
//
|
|
||||||
// if (response.status >= 200 && response.status < 300) {
|
|
||||||
// const uBody = await response.json();
|
|
||||||
// user = {
|
|
||||||
// isLoggedIn: true,
|
|
||||||
// email: uBody.email,
|
|
||||||
// username: uBody.display_name || 'Unknown username'
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (event.url.pathname.startsWith('/private') && !user.isLoggedIn) {
|
|
||||||
// redirect(307, '/error');
|
|
||||||
// } else if (event.url.pathname.startsWith('/api') && !user.isLoggedIn) {
|
|
||||||
// return new Response(null, { status: 401 });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// event.locals.user = user;
|
|
||||||
// const response = await resolve(event);
|
|
||||||
// return response;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const handleFetch: HandleFetch = async({request, fetch}) => {
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
// if (request.url
|
const sessionId = event.cookies.get('session_id');
|
||||||
// }
|
event.locals.user = null;
|
||||||
|
|
||||||
|
if (sessionId) {
|
||||||
|
const request = new Request('http://hitstar.xyz/api/user/me');
|
||||||
|
request.headers.set(
|
||||||
|
'cookie',
|
||||||
|
event.cookies
|
||||||
|
.getAll()
|
||||||
|
.filter(({ value }) => value !== '') // account for cookie that got deleted in the current request
|
||||||
|
.map(({ name, value }) => `${name}=${encodeURIComponent(value)}`)
|
||||||
|
.join('; ')
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await fetch(request);
|
||||||
|
if (response.status >= 200 && response.status < 300) {
|
||||||
|
const uBody = await response.json();
|
||||||
|
const user = {
|
||||||
|
email: uBody.email,
|
||||||
|
username: uBody.username || 'Unknown username'
|
||||||
|
};
|
||||||
|
event.locals.user = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await resolve(event);
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const handleFetch = (async ({ event, request, fetch }) => {
|
||||||
|
// console.log("fetch request to " + request.url)
|
||||||
|
//
|
||||||
|
// if (request.url.startsWith('/api/')) {
|
||||||
|
// console.log("rerouting");
|
||||||
|
// request = new Request(request.url, request);
|
||||||
|
//
|
||||||
|
// request.headers.set(
|
||||||
|
// 'cookie',
|
||||||
|
// event.cookies
|
||||||
|
// .getAll()
|
||||||
|
// .filter(({ value }) => value !== '') // account for cookie that got deleted in the current request
|
||||||
|
// .map(({ name, value }) => `${name}=${encodeURIComponent(value)}`)
|
||||||
|
// .join('; ')
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// return fetch(request);
|
||||||
|
// }) satisfies HandleFetch;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Player } from '$lib/types';
|
import type { User } from "../../app";
|
||||||
|
|
||||||
let {players = [], maxPlayers = 8 }: { players: Player[], maxPlayers: number} = $props();
|
let {players = [], maxPlayers = 8, host }: { players: User[], maxPlayers: number, host: User} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-sm p-5 border border-gray-100">
|
<div class="bg-white rounded-lg shadow-sm p-5 border border-gray-100">
|
||||||
@@ -11,14 +11,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
{#each players as player (player.id)}
|
{#each players as player (player.email)}
|
||||||
<li class="flex items-center p-2 rounded-md {player.isHost ? 'bg-blue-50' : 'bg-gray-50'}">
|
<li class="flex items-center p-2 rounded-md {player.email === host.email ? 'bg-blue-50' : 'bg-gray-50'}">
|
||||||
<div class="bg-gradient-to-br from-blue-500 to-indigo-600 h-8 w-8 rounded-full flex items-center justify-center text-white font-medium text-sm">
|
<div class="bg-gradient-to-br from-blue-500 to-indigo-600 h-8 w-8 rounded-full flex items-center justify-center text-white font-medium text-sm">
|
||||||
{player.name.substring(0, 2).toUpperCase()}
|
{player.username.substring(0, 2).toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
<span class="ml-2 text-gray-800 font-medium">{player.name}</span>
|
<span class="ml-2 text-gray-800 font-medium">{player.username}</span>
|
||||||
|
|
||||||
{#if player.isHost}
|
{#if player.email === host.email}
|
||||||
<span class="ml-auto flex items-center text-blue-600 text-sm font-medium">
|
<span class="ml-auto flex items-center text-blue-600 text-sm font-medium">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path d="M9.504 5.071a.5.5 0 01.992 0l1.5 10a.5.5 0 01-.992 0l-1.5-10zM8 9a1 1 0 100 2h4a1 1 0 100-2H8z" />
|
<path d="M9.504 5.071a.5.5 0 01.992 0l1.5 10a.5.5 0 01-.992 0l-1.5-10zM8 9a1 1 0 100 2h4a1 1 0 100-2H8z" />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface Player {
|
export interface Player {
|
||||||
id: number;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
isHost: boolean;
|
isHost: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import type { User } from '../app';
|
||||||
import type { Player, Settings, WebSocketMessage } from './types';
|
import type { Player, Settings, WebSocketMessage } from './types';
|
||||||
|
|
||||||
export class WebsocketClient {
|
export class WebsocketClient {
|
||||||
connected = $state(false);
|
connected = $state(false);
|
||||||
players: Player[] = $state([]);
|
players: User[] = $state([]);
|
||||||
gameSettings: Settings = $state({
|
gameSettings: Settings = $state({
|
||||||
maxPlayers: 8,
|
maxPlayers: 8,
|
||||||
gameMode: 'classic',
|
gameMode: 'classic',
|
||||||
@@ -40,7 +41,7 @@ export class WebsocketClient {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'playerLeave':
|
case 'playerLeave':
|
||||||
this.players = this.players.filter((p) => p.id !== message.playerId);
|
this.players = this.players.filter((p) => p.email !== message.email);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'playerList':
|
case 'playerList':
|
||||||
|
|||||||
7
src/routes/+page.server.ts
Normal file
7
src/routes/+page.server.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
return {
|
||||||
|
user: locals.user
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
isLoggingIn = true;
|
isLoggingIn = true;
|
||||||
loginError = '';
|
loginError = '';
|
||||||
|
|
||||||
goto('/login');
|
goto('api/auth/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLogout() {
|
function handleLogout() {
|
||||||
@@ -23,8 +23,18 @@
|
|||||||
goto('/logout');
|
goto('/logout');
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLobby() {
|
async function createLobby() {
|
||||||
goto('/lobby/create');
|
const res = await fetch('/api/lobby/create', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
const lobby = await res.json()
|
||||||
|
lobbyCode = lobby.id
|
||||||
|
goto(`/lobby/${lobbyCode}`);
|
||||||
|
} else {
|
||||||
|
console.log(await res.text());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function joinLobby() {
|
function joinLobby() {
|
||||||
@@ -113,7 +123,7 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="w-full py-3 px-4 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-md transition-colors flex items-center justify-center"
|
class="w-full py-3 px-4 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-md transition-colors flex items-center justify-center"
|
||||||
onclick={createLobby}
|
onclick={async () => await createLobby()}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import type { User } from '../app';
|
|
||||||
import type { PageLoad } from './$types';
|
|
||||||
|
|
||||||
export const load: PageLoad = async ({ fetch }) => {
|
|
||||||
const response = await fetch('http://hitstar.xyz/api/user/me', {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (response.status >= 200 && response.status < 300) {
|
|
||||||
const user: User = await response.json();
|
|
||||||
return {
|
|
||||||
user
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
console.log(await response.text());
|
|
||||||
return {
|
|
||||||
user: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onMount, onDestroy } from 'svelte';
|
|
||||||
import { wsClient } from '$lib/websocketClient.svelte';
|
|
||||||
import type { Settings, GameMode, Playlist } from '$lib/types';
|
|
||||||
import PlayerList from '$lib/components/PlayerList.svelte';
|
|
||||||
import GameSettings from '$lib/components/GameSettings.svelte';
|
|
||||||
|
|
||||||
// Create WebSocket client
|
|
||||||
|
|
||||||
// Current user state
|
|
||||||
let isHost = $state(true); // Assume current user is host for this example
|
|
||||||
let lobbyCode = $state('GAME123');
|
|
||||||
|
|
||||||
// Available options for settings
|
|
||||||
let gameModes = $state<GameMode[]>([
|
|
||||||
{ id: 'classic', name: 'Classic Mode' },
|
|
||||||
{ id: 'team', name: 'Team Battle' },
|
|
||||||
{ id: 'speed', name: 'Speed Round' }
|
|
||||||
]);
|
|
||||||
|
|
||||||
let playlists = $state<Playlist[]>([
|
|
||||||
{ id: 1, name: 'Pop Hits 2023', imageUrl: 'https://example.com/images/pop2023.jpg', songCount: 25 },
|
|
||||||
{ id: 2, name: 'Rock Classics', imageUrl: 'https://example.com/images/rock.jpg', songCount: 30 },
|
|
||||||
{ id: 3, name: '80s Mixtape', imageUrl: 'https://example.com/images/80s.jpg', songCount: 20 },
|
|
||||||
{ id: 4, name: 'Movie Soundtracks', imageUrl: 'https://example.com/images/movies.jpg', songCount: 15 },
|
|
||||||
{ id: 5, name: 'Hip Hop Essentials', imageUrl: 'https://example.com/images/hiphop.jpg', songCount: 40 },
|
|
||||||
{ id: 6, name: 'Indie Discoveries', imageUrl: 'https://example.com/images/indie.jpg', songCount: 35 }
|
|
||||||
]);
|
|
||||||
|
|
||||||
function handleSettingsUpdate(settings: Settings) {
|
|
||||||
// Send updated settings to other players via websocket
|
|
||||||
wsClient.gameSettings = settings;
|
|
||||||
wsClient.sendMessage({
|
|
||||||
type: 'settingsUpdate',
|
|
||||||
settings: settings
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function startGame() {
|
|
||||||
wsClient.sendMessage({
|
|
||||||
type: 'startGame',
|
|
||||||
settings: wsClient.gameSettings
|
|
||||||
});
|
|
||||||
// In a real app, this would navigate to the game screen
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyLobbyCode() {
|
|
||||||
navigator.clipboard.writeText(lobbyCode);
|
|
||||||
// Would show a toast notification in a real app
|
|
||||||
}
|
|
||||||
|
|
||||||
function leaveLobby() {
|
|
||||||
wsClient.disconnect();
|
|
||||||
// In a real app, you'd likely redirect to another page here
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
wsClient.connect(`ws://localhost/api/lobby?id=${lobbyCode}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
// Clean up WebSocket connection when component is destroyed
|
|
||||||
wsClient.disconnect();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="min-h-screen bg-gray-50 py-8 px-4 sm:px-6 lg:px-8">
|
|
||||||
<div class="max-w-5xl mx-auto">
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="mb-8 text-center">
|
|
||||||
<h1 class="text-3xl font-bold text-gray-900">Game Lobby</h1>
|
|
||||||
<div class="mt-3 flex items-center justify-center">
|
|
||||||
<span class="text-gray-600 mr-2">Invite your friends using code:</span>
|
|
||||||
<span class="font-mono bg-white px-3 py-1.5 rounded-md border border-gray-200 text-blue-600 font-semibold">{lobbyCode}</span>
|
|
||||||
<!-- svelte-ignore a11y_consider_explicit_label -->
|
|
||||||
<button
|
|
||||||
class="ml-2 p-1.5 text-gray-500 hover:text-blue-600 transition-colors"
|
|
||||||
title="Copy to clipboard"
|
|
||||||
onclick={copyLobbyCode}
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
|
||||||
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
||||||
<!-- Left column: Player list -->
|
|
||||||
<div class="md:col-span-1">
|
|
||||||
{#if wsClient.connected}
|
|
||||||
<PlayerList
|
|
||||||
players={wsClient.players}
|
|
||||||
maxPlayers={wsClient.gameSettings.maxPlayers}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<div class="bg-white rounded-lg shadow-sm p-5 flex items-center justify-center">
|
|
||||||
<div class="flex items-center text-amber-600">
|
|
||||||
<svg class="animate-spin -ml-1 mr-3 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
Connecting to lobby...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Right column: Game settings and controls -->
|
|
||||||
<div class="md:col-span-2 space-y-6">
|
|
||||||
<GameSettings
|
|
||||||
settings={wsClient.gameSettings}
|
|
||||||
gameModes={gameModes}
|
|
||||||
playlists={playlists}
|
|
||||||
isHost={isHost}
|
|
||||||
onUpdate={handleSettingsUpdate}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Action buttons -->
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<button
|
|
||||||
class="px-4 py-2.5 bg-white border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
||||||
onclick={leaveLobby}
|
|
||||||
>
|
|
||||||
Leave Lobby
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if isHost}
|
|
||||||
<button
|
|
||||||
class="px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center"
|
|
||||||
onclick={startGame}
|
|
||||||
>
|
|
||||||
<span>Start Game</span>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-1.5" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<div class="text-gray-500 italic self-center flex items-center">
|
|
||||||
<svg class="animate-pulse h-5 w-5 mr-2 text-blue-400" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<circle cx="12" cy="12" r="10" />
|
|
||||||
</svg>
|
|
||||||
Waiting for host to start...
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,29 +1,39 @@
|
|||||||
import { db } from "$lib/server/db";
|
import { redirect } from '@sveltejs/kit';
|
||||||
import { eq } from "drizzle-orm";
|
import type { PageServerLoad } from './$types';
|
||||||
import type { PageServerLoad } from "./$types";
|
import type { User } from '../../../app';
|
||||||
import { lobbysTable } from "$lib/server/db/schema";
|
import type { Settings } from '$lib/types';
|
||||||
import { redirect } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params, locals }) => {
|
export const load: PageServerLoad = async ({ params, fetch, locals: { user } }) => {
|
||||||
const id = Number(params.id);
|
if (!user) {
|
||||||
|
redirect(307, '/');
|
||||||
|
}
|
||||||
|
|
||||||
const lobby = await db.query.lobbysTable.findFirst({
|
const response = await fetch(`http://hitstar.xyz/api/lobby/${params.id}`, {
|
||||||
with: {
|
headers: {
|
||||||
usersInLobby: {
|
'Content-Type': 'application/json'
|
||||||
with: {
|
|
||||||
user: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
where: eq(lobbysTable.id, id)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!lobby) {
|
|
||||||
redirect(307, "/error");
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
if (response.status != 200) {
|
||||||
lobby: lobby,
|
redirect(307, '/');
|
||||||
username: locals.user.username
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
host,
|
||||||
|
players,
|
||||||
|
gameSettings
|
||||||
|
}: { id: string; host: User; players: User[]; gameSettings: Settings } = await response.json();
|
||||||
|
console.log('Successful request for lobby');
|
||||||
|
console.log(id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: user,
|
||||||
|
lobby: {
|
||||||
|
id,
|
||||||
|
host,
|
||||||
|
players,
|
||||||
|
gameSettings
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,18 +1,217 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
import { wsClient } from '$lib/websocketClient.svelte';
|
||||||
|
import type { Settings, GameMode, Playlist } from '$lib/types';
|
||||||
|
import PlayerList from '$lib/components/PlayerList.svelte';
|
||||||
|
import GameSettings from '$lib/components/GameSettings.svelte';
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
import { onDestroy } from 'svelte';
|
|
||||||
|
// Create WebSocket client
|
||||||
|
|
||||||
|
// Current user state
|
||||||
|
|
||||||
let { data }: PageProps = $props();
|
let { data }: PageProps = $props();
|
||||||
|
|
||||||
onDestroy(() => console.log('left site'));
|
let lobbyCode = $state(data.lobby.id);
|
||||||
|
let isHost = $state(data.user.email === data.lobby.host.email); // Assume current user is host for this example
|
||||||
|
|
||||||
|
// Available options for settings
|
||||||
|
let gameModes = $state<GameMode[]>([
|
||||||
|
{ id: 'classic', name: 'Classic Mode' },
|
||||||
|
{ id: 'team', name: 'Team Battle' },
|
||||||
|
{ id: 'speed', name: 'Speed Round' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
let playlists = $state<Playlist[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Pop Hits 2023',
|
||||||
|
imageUrl: 'https://example.com/images/pop2023.jpg',
|
||||||
|
songCount: 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Rock Classics',
|
||||||
|
imageUrl: 'https://example.com/images/rock.jpg',
|
||||||
|
songCount: 30
|
||||||
|
},
|
||||||
|
{ id: 3, name: '80s Mixtape', imageUrl: 'https://example.com/images/80s.jpg', songCount: 20 },
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Movie Soundtracks',
|
||||||
|
imageUrl: 'https://example.com/images/movies.jpg',
|
||||||
|
songCount: 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'Hip Hop Essentials',
|
||||||
|
imageUrl: 'https://example.com/images/hiphop.jpg',
|
||||||
|
songCount: 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: 'Indie Discoveries',
|
||||||
|
imageUrl: 'https://example.com/images/indie.jpg',
|
||||||
|
songCount: 35
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
function handleSettingsUpdate(settings: Settings) {
|
||||||
|
// Send updated settings to other players via websocket
|
||||||
|
wsClient.gameSettings = settings;
|
||||||
|
wsClient.sendMessage({
|
||||||
|
type: 'settingsUpdate',
|
||||||
|
settings: settings
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
wsClient.sendMessage({
|
||||||
|
type: 'startGame',
|
||||||
|
settings: wsClient.gameSettings
|
||||||
|
});
|
||||||
|
// In a real app, this would navigate to the game screen
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyLobbyCode() {
|
||||||
|
navigator.clipboard.writeText(lobbyCode);
|
||||||
|
// Would show a toast notification in a real app
|
||||||
|
}
|
||||||
|
|
||||||
|
function leaveLobby() {
|
||||||
|
wsClient.disconnect();
|
||||||
|
// In a real app, you'd likely redirect to another page here
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
wsClient.connect(`ws://hitstar.xyz/api/lobby?id=${lobbyCode}`);
|
||||||
|
wsClient.gameSettings = data.lobby.gameSettings
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
// Clean up WebSocket connection when component is destroyed
|
||||||
|
wsClient.disconnect();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>You are in lobby with ID: {data.lobby?.id}</h1>
|
<div class="min-h-screen bg-gray-50 py-8 px-4 sm:px-6 lg:px-8">
|
||||||
<h2>Your username is: {data.username}</h2>
|
<div class="max-w-5xl mx-auto">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="mb-8 text-center">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900">Game Lobby</h1>
|
||||||
|
<div class="mt-3 flex items-center justify-center">
|
||||||
|
<span class="text-gray-600 mr-2">Invite your friends using code:</span>
|
||||||
|
<span
|
||||||
|
class="font-mono bg-white px-3 py-1.5 rounded-md border border-gray-200 text-blue-600 font-semibold"
|
||||||
|
>{lobbyCode}</span
|
||||||
|
>
|
||||||
|
<!-- svelte-ignore a11y_consider_explicit_label -->
|
||||||
|
<button
|
||||||
|
class="ml-2 p-1.5 text-gray-500 hover:text-blue-600 transition-colors"
|
||||||
|
title="Copy to clipboard"
|
||||||
|
onclick={copyLobbyCode}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
||||||
|
<path
|
||||||
|
d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>Players in Lobby:</h2>
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<ul>
|
<!-- Left column: Player list -->
|
||||||
{#each data.lobby.usersInLobby as player (player.userEmail)}
|
<div class="md:col-span-1">
|
||||||
<li>{player.user.username}</li>
|
{#if wsClient.connected}
|
||||||
{/each}
|
<PlayerList players={wsClient.players} maxPlayers={wsClient.gameSettings.maxPlayers} host={data.lobby.host} />
|
||||||
</ul>
|
{:else}
|
||||||
|
<div class="bg-white rounded-lg shadow-sm p-5 flex items-center justify-center">
|
||||||
|
<div class="flex items-center text-amber-600">
|
||||||
|
<svg
|
||||||
|
class="animate-spin -ml-1 mr-3 h-5 w-5"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
class="opacity-25"
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="10"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="4"
|
||||||
|
></circle>
|
||||||
|
<path
|
||||||
|
class="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Connecting to lobby...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right column: Game settings and controls -->
|
||||||
|
<div class="md:col-span-2 space-y-6">
|
||||||
|
<GameSettings
|
||||||
|
settings={wsClient.gameSettings}
|
||||||
|
{gameModes}
|
||||||
|
{playlists}
|
||||||
|
{isHost}
|
||||||
|
onUpdate={handleSettingsUpdate}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Action buttons -->
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<button
|
||||||
|
class="px-4 py-2.5 bg-white border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
onclick={leaveLobby}
|
||||||
|
>
|
||||||
|
Leave Lobby
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if isHost}
|
||||||
|
<button
|
||||||
|
class="px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center"
|
||||||
|
onclick={startGame}
|
||||||
|
>
|
||||||
|
<span>Start Game</span>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 ml-1.5"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<div class="text-gray-500 italic self-center flex items-center">
|
||||||
|
<svg
|
||||||
|
class="animate-pulse h-5 w-5 mr-2 text-blue-400"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
</svg>
|
||||||
|
Waiting for host to start...
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user