Compare commits

...
9 Commits
Author SHA1 Message Date
Esa Kataja 8cf2557896 refactor: improve login form and button styling
- Replace hardcoded credentials with appropriate placeholders
- Add descriptive Finnish placeholders for login inputs
- Style 'Start game' button with primary button class and center alignment
- Improve security by removing exposed default credentials
2024-12-02 16:34:04 +02:00
Esa Kataja ba8db120b4 style: enhance checkbox appearance in Card component
- Increase checkbox size to 24x24px
- Add rounded corners
- Implement visible border with theme color
- Add smooth transition animation
- Improve checked state visibility
2024-12-02 16:30:28 +02:00
Esa Kataja a8809ace9b Add admin panel 2024-12-02 16:18:31 +02:00
Esa Kataja aed5c675d3 Add admin panel 2024-12-02 15:26:24 +02:00
Esa Kataja fe14660601 Add Play logic 2024-11-25 18:51:32 +02:00
Esa Kataja b652447121 Change Push footer to bottom of page 2024-11-25 18:51:01 +02:00
Esa Kataja 23c738d218 Add Final score display 2024-11-25 18:46:53 +02:00
Esa Kataja add6a061bf Add Ability to choose basic cards 2024-11-20 15:41:58 +02:00
Esa Kataja dcaf98e612 Refactor CSS 2024-11-20 15:41:40 +02:00
18 changed files with 920 additions and 124 deletions
-7
View File
@@ -29,11 +29,4 @@ h1, h2, h3, h4, h5, h6 {
.button-primary {
@apply border-primary-GoldTinsel bg-primary-GoldTinsel-dark text-primary-SantaSuitBlack font-bold mx-auto px-6 hover:bg-primary-GoldTinsel-light
}
.movie-poster {
@apply rounded-md mx-2 w-[60rem]
/* aspect-ratio: auto; */
/* width: 100%; */
}
+11
View File
@@ -13,6 +13,17 @@ export const handle: Handle = async ({ event, resolve }) => {
}
if (event.url.pathname.startsWith('/admin')) {
const current_user = await authenticateUser(event);
if (!current_user) {
throw redirect(302, '/login');
}
if (!current_user.is_admin) {
throw redirect(302, '/');
}
event.locals.user = current_user;
}
const response = await resolve(event);
return response;
};
+38 -17
View File
@@ -1,32 +1,53 @@
<script lang="ts">
let { id, title, description, points }: Cliche = $props();
import type { Cliche } from '$lib/types';
let {
cardData,
onCardChecked,
isBonus = false
}: { cardData: Cliche; onCardChecked: any; isBonus?: boolean } = $props();
type Cliche = {
id: number;
title: string;
description: string;
points: number;
};
let isCardChecked: boolean = $state(false);
let isSelectedAtStart: boolean = $state(false);
function toggleCardChecked() {
isCardChecked = !isCardChecked;
onCardChecked(cardData, isCardChecked, isBonus);
}
console.log('isBonus', isBonus);
</script>
<div class="card">
<div class="card" id="card">
<div class="relative">
<input class="left-0 top-0" type="checkbox" name="" id="card-{id}" />
<input type="checkbox" class="peer hidden" name="cardDetails-{id}" id="cardDetails-{id}" />
<label for="cardDetails-{id}">{title}</label>
<!-- <button class="text-center text-xl font-semibold">{title}</button> -->
<div>
<input
type="checkbox"
name=""
id="card-{cardData.id}"
onchange={toggleCardChecked}
class="appearance-none h-6 w-6 rounded-lg border-2 border-primary-FestiveRed-dark checked:bg-primary-FestiveRed-dark checked:border-primary-FestiveRed-dark cursor-pointer transition-all duration-200 ease-in-out"
/>
</div>
<div>
<input
type="checkbox"
class="peer hidden"
name="cardDetails-{cardData.id}"
id="cardDetails-{cardData.id}"
/>
<label for="cardDetails-{cardData.id}">{cardData.title}</label>
<div class="hidden flex-col gap-4 peer-checked:flex">
<!-- <img src="https://placehold.co/150x200" class="mx-auto" alt="Descriptive" /> -->
<p>{description}</p>
<p>Points: {points}</p>
<div class="hidden flex-col gap-4 peer-checked:flex">
<!-- <img src="https://placehold.co/150x200" class="mx-auto" alt="Descriptive" /> -->
<p>{cardData.description}</p>
<p>Pisteet: {cardData.point_value}</p>
</div>
</div>
</div>
</div>
<style lang="postcss">
.card {
@apply mx-auto my-6 flex max-w-sm flex-col gap-4 rounded-xl border-2 border-primary-FestiveRed-dark p-6 text-center;
@apply mx-auto w-full max-w-sm gap-4 rounded-xl border-2 border-primary-FestiveRed-dark p-4 text-center;
}
.card input {
@apply absolute left-0 top-0 m-0 appearance-none rounded-lg p-0 accent-black;
+28 -64
View File
@@ -1,73 +1,37 @@
<script lang="ts">
import type { Cliche } from '$lib/types';
import Card from './Card.svelte';
type Cliche = {
id: number;
title: string;
description: string;
points: number;
};
const cards: Cliche[] = $props();
let selected_cliches: Cliche[] = $state([]);
const cards: Cliche[] = [
{
id: 1,
title: 'Punainen villakangastakki',
description: 'Päähenkilöllä on aina kirkkaanpunainen, täydellisesti istuva talvitakki.',
points: 1
},
{
id: 2,
title: 'Keskeytetty suudelma',
description: 'Juuri, kun romanttinen hetki on käsillä, jokin keskeyttää ensisuudelman.',
points: 1
},
{
id: 3,
title: 'Miespääosa yh',
description:
'Miespäähenkilö on yksinhuoltaja, joka tasapainoilee uran ja lapsenhoidon välillä.',
points: 2
},
{
id: 4,
title: 'Leskimies',
description: 'Päähenkilö on menettänyt puolisonsa ja yrittää nyt toipua elämässään.',
points: 2
},
{
id: 5,
title: 'Jouluoksennus',
description:
'Koti, työpaikka tai jokin muu koristeltu liioitellusti jouluteemalla, ilman mitään hillittyä tyylitajua.',
points: 1
},
{
id: 6,
title: 'Evergreen',
description:
"Tarinan kylä tai kaupunki on idyllinen paikka, nimeltään jotain jouluista, kuten 'Evergreen' tai 'Snowville'.",
points: 3
},
{
id: 7,
title: 'Yritys pulassa',
description: 'Päähenkilön yritys on konkurssin partaalla tai vaarassa tulla ostetuksi.',
points: 2
},
{
id: 8,
title: 'Naispääosa pitkä blondi',
description: 'Naispääosalla pitkät blondit hiukset.',
points: 1
function handleChildData(card: Cliche) {
for (let i = 0; i < selected_cliches.length; i++) {
if (selected_cliches[i].id == card.id) {
selected_cliches.splice(i, 1);
return;
}
}
];
let selected_cliches: Cliche[] = [];
selected_cliches = [...selected_cliches, card];
}
</script>
<form action="">
{#each cards as card}
<Card id={card.id} title={card.title} description={card.description} points={card.points} />
{/each}
<button class="button button-primary mx-auto">Valitse kortit</button>
<div class="space-y-2 lg:grid lg:grid-cols-2">
{#each cards as card}
<Card cardData={card} onCardChecked={handleChildData} />
{/each}
</div>
<p>Kortteja valittu: {selected_cliches.length} / 5</p>
{#if selected_cliches.length == 5}
<button class="button button-primary mx-auto">Valitse kortit</button>
{:else}
<button class="button button-disabled mx-auto" disabled>Korjaa valintaa</button>
{/if}
</form>
<div></div>
<style lang="postcss">
button {
@apply mx-auto mt-6;
}
</style>
+2 -2
View File
@@ -15,7 +15,7 @@
class="input-text"
required
minlength="3"
value="admin"
placeholder="käyttäjätunnus"
/>
<label for="password">Salasana</label>
<input
@@ -24,8 +24,8 @@
id="password"
class="input-text"
required
value="password"
minlength="5"
placeholder="salasana"
/>
<hr class="my-4 border-0" />
<button type="submit" class="button button-primary">Sisään</button>
+3 -3
View File
@@ -1,14 +1,14 @@
<script>
let data = $props();
const movie = data.data.movie;
const movie = data;
let actors = movie.actors.join(', ');
const imdb_link = `https://www.imdb.com/title/${movie.imdb_id}/`;
</script>
<div>
<h2 class="mb-4 text-center text-xl font-semibold">{movie.name}</h2>
<div class="mx-auto flex gap-2 px-3">
<div class="movie-poster">
<div class="max-w-1/2 mx-auto flex flex-col gap-4 px-3 lg:flex-row">
<div class="mx-auto">
<img class="rounded-2xl" src={movie.poster_url} alt="Poster" />
</div>
<div class="space-y-8">
+20
View File
@@ -6,4 +6,24 @@ export function decodeUSerCookie(userCookie: string) {
const decodedCookie = decodeURIComponent(userCookie);
const parsedCookie = JSON.parse(decodedCookie);
return parsedCookie;
}
export function getNextMovie() {
const get_next_movie_url = `${api_url}/movie/next`;
return fetch(get_next_movie_url)
.then(response => response.json())
.then(data => {
const year = new Date(data.release_date).getFullYear();
data.release_date = `${year}`;
return data;
});
}
export function getCards() {
const get_cards_url = `${api_url}/card`;
return fetch(get_cards_url)
.then(response => response.json())
.then(data => {
return data;
});
}
+18
View File
@@ -0,0 +1,18 @@
export type movie = {
id: string;
name: string;
imdb_id: string;
actors: Array<string>;
plot: string;
poster_url: string;
release_date: string;
showtime: string;
is_watched: boolean;
}
export type Cliche = {
id: number;
title: string;
description: string;
point_value: number;
};
+1 -1
View File
@@ -12,7 +12,7 @@
<div class="flex min-h-[100dvh] flex-col">
<Header user={data.user} />
<main class="container mx-auto max-w-4xl flex-grow overflow-hidden">
<main class="container mx-auto max-w-4xl flex-grow">
{@render children()}
</main>
<Footer />
+2 -23
View File
@@ -1,30 +1,9 @@
import type { PageServerLoad } from "./$types";
import { api_url } from "$lib";
type movie = {
id: string;
name: string;
imdb_id: string;
actors: Array<string>;
plot: string;
poster_url: string;
release_date: string;
showtime: string;
is_watched: boolean;
}
import { getNextMovie } from "$lib";
export const load: PageServerLoad = async (event) => {
const get_next_movie_url = `${api_url}/movie/next`;
const response = await fetch(get_next_movie_url);
const data: movie = await response.json();
const year = new Date(data.release_date).getFullYear();
data.release_date = `${year}`;
const data = await getNextMovie();
return {
movie: data,
-2
View File
@@ -2,5 +2,3 @@
import MovieDetails from '$lib/components/MovieDetails.svelte';
let data = $props();
</script>
<MovieDetails {...data} />
+192
View File
@@ -0,0 +1,192 @@
import { error } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
const api_url = import.meta.env.VITE_API_URL;
interface Movie {
name: string;
imdb_id: string;
showtime: string;
plot: string;
actors: string[];
release_date: string;
poster_url: string;
id: string;
created_at: string;
modified_at: string;
is_watched: boolean;
}
export const load: PageServerLoad = async ({ fetch }) => {
try {
const [usersRes, moviesRes, cardsRes] = await Promise.all([
fetch(`${api_url}/user`),
fetch(`${api_url}/movie`),
fetch(`${api_url}/card`)
]);
if (!usersRes.ok) throw error(usersRes.status, 'Failed to load users');
if (!moviesRes.ok) throw error(moviesRes.status, 'Failed to load movies');
if (!cardsRes.ok) throw error(cardsRes.status, 'Failed to load cards');
const users = await usersRes.json();
const movies: Movie[] = await moviesRes.json();
const cards = await cardsRes.json();
return {
users,
movies,
cards
};
} catch (error) {
console.error('Error loading data:', error);
return {
users: [],
movies: [],
cards: []
};
}
};
export const actions: Actions = {
addUser: async ({ request }) => {
const data = await request.formData();
const username = data.get('username');
const password = data.get('password');
try {
const response = await fetch(`${api_url}/admin/user`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
password
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return { success: true };
} catch (error) {
console.error('Error adding user:', error);
return { success: false, message: 'Failed to add user' };
}
},
deleteUser: async ({ request }) => {
const data = await request.formData();
const userId = data.get('userId');
try {
const response = await fetch(`${api_url}/admin/user?user_id=${userId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return { success: true };
} catch (error) {
console.error('Error deleting user:', error);
return { success: false, message: 'Failed to delete user' };
}
},
addMovie: async ({ request }) => {
const data = await request.formData();
const movieData = {
imdb_id: data.get('imdb_id'),
showtime: data.get('showtime'),
plot: data.get('plot')
};
try {
const response = await fetch(`${api_url}/admin/movie`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(movieData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return { success: true };
} catch (error) {
console.error('Error adding movie:', error);
return { success: false, message: 'Failed to add movie' };
}
},
setMovieWatched: async ({ request, fetch }) => {
const formData = await request.formData();
const movieId = formData.get('movieId');
const response = await fetch(`${api_url}/admin/movie/${movieId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ is_watched: true })
});
if (!response.ok) {
throw error(response.status, 'Failed to update movie status');
}
return { success: true };
},
addCard: async ({ request, fetch }) => {
const data = await request.formData();
const cardData = {
title: data.get('title'),
description: data.get('description'),
point_value: Number(data.get('point_value'))
};
try {
const response = await fetch(`${api_url}/admin/card`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(cardData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return { success: true };
} catch (error) {
console.error('Error adding card:', error);
return { success: false, message: 'Failed to add card' };
}
},
deleteCard: async ({ request, fetch }) => {
const formData = await request.formData();
const cardId = formData.get('cardId');
const response = await fetch(`${api_url}/admin/card/${cardId}`, {
method: 'DELETE'
});
if (!response.ok) {
throw error(response.status, 'Failed to delete card');
}
return { success: true };
}
};
+322
View File
@@ -0,0 +1,322 @@
<script lang="ts">
import { enhance } from '$app/forms';
let activeTab = 'users';
export let data;
const { users, movies, cards } = data;
</script>
<div class="admin-panel">
<nav class="tabs">
<button
class:active={activeTab === 'users'}
on:click={() => activeTab = 'users'}>Users</button>
<button
class:active={activeTab === 'movies'}
on:click={() => activeTab = 'movies'}>Movies</button>
<button
class:active={activeTab === 'cards'}
on:click={() => activeTab = 'cards'}>Cards</button>
</nav>
<div class="tab-content">
{#if activeTab === 'users'}
<div class="section">
<h2>Existing Users</h2>
<div class="table-container">
<table>
<thead>
<tr>
<th>Username</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each users as user}
<tr>
<td>{user.username}</td>
<td>
<form method="POST" action="?/deleteUser" use:enhance>
<input type="hidden" name="userId" value={user.id}>
<button type="submit" class="danger small">Delete</button>
</form>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<h2>Add User</h2>
<form method="POST" action="?/addUser" use:enhance>
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Add User</button>
</form>
</div>
{/if}
{#if activeTab === 'movies'}
<div class="section">
<h2>Existing Movies</h2>
<div class="table-container">
<table>
<thead>
<tr>
<th>Name</th>
<th>Plot</th>
<th>IMDB</th>
<th>Showtime</th>
<th>Watched</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each movies as movie}
<tr>
<td>{movie.name}</td>
<td>{movie.plot}</td>
<td>
<a href="https://www.imdb.com/title/{movie.imdb_id}" target="_blank" rel="noopener noreferrer">
View on IMDB
</a>
</td>
<td>{new Date(movie.showtime).toLocaleDateString()}</td>
<td>{movie.is_watched ? 'Yes' : 'No'}</td>
<td>
{#if !movie.is_watched}
<form method="POST" action="?/setMovieWatched" use:enhance>
<input type="hidden" name="movieId" value={movie.id}>
<button type="submit" class="small">Mark Watched</button>
</form>
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<h2>Add Movie</h2>
<form method="POST" action="?/addMovie" use:enhance>
<div class="form-group">
<label for="imdb_id">IMDB ID:</label>
<input type="text" id="imdb_id" name="imdb_id" placeholder="tt1234567" required>
</div>
<div class="form-group">
<label for="showtime">Showtime:</label>
<input type="date" id="showtime" name="showtime" required>
</div>
<div class="form-group">
<label for="plot">Plot:</label>
<textarea id="plot" name="plot" rows="3" required></textarea>
</div>
<button type="submit">Add Movie</button>
</form>
</div>
{/if}
{#if activeTab === 'cards'}
<div class="section">
<h2>Existing Cards</h2>
<div class="table-container">
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Points</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each cards as card}
<tr>
<td>{card.title}</td>
<td>{card.description}</td>
<td>{card.point_value}</td>
<td>
<form method="POST" action="?/deleteCard" use:enhance>
<input type="hidden" name="cardId" value={card.id}>
<button type="submit" class="danger small">Delete</button>
</form>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<h2>Add Card</h2>
<form method="POST" action="?/addCard" use:enhance>
<div class="form-group">
<label for="title">Title:</label>
<input type="text" id="title" name="title" required>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea id="description" name="description" rows="3" required></textarea>
</div>
<div class="form-group">
<label for="point_value">Points:</label>
<input type="number" id="point_value" name="point_value" min="1" required>
</div>
<button type="submit">Add Card</button>
</form>
</div>
{/if}
</div>
</div>
<style>
.admin-panel {
max-width: 1200px;
margin: 2rem auto;
padding: 1rem;
background-color: #1a472a; /* Dark Christmas green */
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.tabs {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
border-bottom: 1px solid #2c6842;
padding-bottom: 1rem;
}
.tabs button {
padding: 0.5rem 1rem;
border: none;
background: none;
cursor: pointer;
font-size: 1rem;
color: #fff;
border-radius: 4px;
transition: background-color 0.2s;
}
.tabs button:hover {
background: #2c6842;
}
.tabs button.active {
background: #c41e3a; /* Christmas red for active tab */
color: white;
}
.section {
background: #234332; /* Slightly lighter green for sections */
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
margin-bottom: 1rem;
}
h2 {
margin-top: 2rem;
margin-bottom: 1rem;
color: #fff;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
color: #fff;
}
input, textarea {
width: 100%;
padding: 0.5rem;
border: 1px solid #2c6842;
border-radius: 4px;
font-size: 1rem;
background: #1a472a;
color: #fff;
}
input:focus, textarea:focus {
outline: none;
border-color: #c41e3a;
}
textarea {
height: 100px;
resize: vertical;
}
button {
padding: 0.5rem 1rem;
background: #c41e3a; /* Christmas red for buttons */
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
}
button:hover {
background: #a01830;
}
button.danger {
background: #dc3545;
}
button.danger:hover {
background: #c82333;
}
button.small {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.table-container {
overflow-x: auto;
margin-bottom: 2rem;
background: #1a472a;
border-radius: 8px;
padding: 1rem;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
}
th, td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #2c6842;
color: #fff;
}
th {
background-color: #2c6842;
font-weight: 600;
color: #fff;
}
tr:hover {
background-color: #2c6842;
}
/* Add a placeholder style for better visibility */
input::placeholder, textarea::placeholder {
color: rgba(255, 255, 255, 0.6);
}
</style>
+104 -4
View File
@@ -1,7 +1,107 @@
import type { PageServerLoad } from "./$types";
import type { PageServerLoad, Actions } from "./$types";
import { getNextMovie, getCards } from "$lib";
import { redirect } from "@sveltejs/kit";
import type { Cliche } from "$lib/types";
const api_url = import.meta.env.VITE_API_URL
export const load: PageServerLoad = async (event) => {
return {
user: event.locals.user
const cards = await getCards();
const movies = await getNextMovie();
const selected_cliches = {
baseCards: JSON.parse(event.cookies.get('selected_cliches') ?? '[]'),
bonusCards: JSON.parse(event.cookies.get('selected_bonus_cliches') ?? '[]')
};
};
return {
movie: movies,
cards: cards,
user: event.locals.user,
selected_cliches: selected_cliches
};
};
export const actions: Actions = {
/**
* Starts a new game. Sets the selected cliche and bonus cliche cookies for the user.
* @param {RequestEvent} event - The request event.
* @param {Cookie | undefined} cookies - The cookies object.
* @returns {Promise<void>} - The response object.
*/
startGame: async ({ request, cookies }) => {
const formData = await request.formData();
const selected_cliches = formData.get('selected_cliches')?.toString() ?? '[]';
const selected_bonus_cliches = formData.get('selected_bonus_cliches')?.toString() ?? '[]';
cookies.set('selected_cliches', selected_cliches, {
path: '/',
sameSite: 'strict',
secure: false,
maxAge: 60 * 60 * 24
});
cookies.set('selected_bonus_cliches', selected_bonus_cliches, {
path: '/',
sameSite: 'strict',
secure: false,
maxAge: 60 * 60 * 24
});
},
/**
* End game action. Calculates the total score for the user and sends it to the
* server to be saved. Redirects to the game over page.
* @param {RequestEvent} event - The request event.
* @param {Cookie | undefined} cookies - The cookies object.
* @returns {Promise<Response>} - The response object.
*/
endGame: async ({ request, cookies }) => {
const formData = await request.formData();
console.log(formData);
const movieId = formData.get('movieId');
const userId = formData.get('userId');
const resultCards = JSON.parse(formData.get('result_cards')?.toString() ?? '[]');
const resultBonusCards = JSON.parse(formData.get('result_bonus_cards')?.toString() ?? '[]');
const selectedBonusCards = JSON.parse(cookies.get('selected_bonus_cliches') ?? '[]');
let totalScore: number = 0;
for (let i = 0; i < resultCards.length; i++) {
console.log('resultCards[i].point_value', resultCards[i].point_value);
totalScore += resultCards[i].point_value;
}
for (let i = 0; i < selectedBonusCards.length; i++) {
if (resultBonusCards.some((card: Cliche) => card.id === selectedBonusCards[i].id)) {
totalScore += selectedBonusCards[i].point_value*2;
} else {
totalScore -= selectedBonusCards[i].point_value*2;
}
}
const response = await fetch(`${api_url}/score`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
user_id: userId,
movie_id: movieId,
score: totalScore
})
});
console.log('response', await response);
// console.log(baseCards, bonusCards);
// cookies.delete('selected_cliches', {
// path: '/',
// sameSite: 'strict',
// secure: false,
// });
// cookies.delete('selected_bonus_cliches', {
// path: '/',
// sameSite: 'strict',
// secure: false,
// });
throw redirect(302, `/gameroom/${totalScore}`);
}
}
+151 -1
View File
@@ -1,6 +1,156 @@
<script lang="ts">
import MovieDetails from '$lib/components/MovieDetails.svelte';
import Card from '$lib/components/Card.svelte';
import type { Cliche } from '$lib/types';
import type { PageData } from './$types';
import { enhance } from '$app/forms';
let { data }: { data: PageData } = $props();
let roundNumber: number = $state(1);
const cards: Cliche[] = data.cards;
let selected_cliches: Cliche[] = $state([]);
let selected_bonus_cliches: Cliche[] = $state([]);
let result_cards: Cliche[] = $state([]);
let result_bonus_cards: Cliche[] = $state([]);
function onCardChecked(card: Cliche) {
for (let i = 0; i < selected_cliches.length; i++) {
if (selected_cliches[i].id == card.id) {
selected_cliches.splice(i, 1);
return;
}
}
selected_cliches = [...selected_cliches, card];
}
function onBonusCardChecked(card: Cliche) {
for (let i = 0; i < selected_bonus_cliches.length; i++) {
if (selected_bonus_cliches[i].id == card.id) {
selected_bonus_cliches.splice(i, 1);
return;
}
}
selected_bonus_cliches = [...selected_bonus_cliches, card];
}
function setResult(cliche: Cliche, isCardChecked: boolean, isBonus: boolean) {
if (isCardChecked) {
if (isBonus) {
result_bonus_cards = [...result_bonus_cards, cliche];
} else {
result_cards = [...result_cards, cliche];
}
} else {
if (isBonus) {
result_bonus_cards = result_bonus_cards.filter((card) => card.id != cliche.id);
} else {
result_cards = result_cards.filter((card) => card.id != cliche.id);
}
}
}
$inspect(result_bonus_cards);
</script>
<h1>gameroom</h1>
<div class="space-y-8">
<input type="checkbox" checked name="moviedetails" id="moviedetails" class="peer hidden" />
<div class="flex justify-center border-2 border-primary-FestiveRed-dark">
<label for="moviedetails" class="cursor-pointer text-xl font-semibold">Elokuva</label>
</div>
<div class="hidden peer-checked:flex">
<MovieDetails {...data.movie} />
</div>
</div>
<div class="mt-8 justify-center space-y-4">
{#if roundNumber == 1 && data.selected_cliches.baseCards.length == 0}
<h2 class="text-center text-xl font-semibold">Valitse kortit</h2>
<div class="flex flex-col space-y-4 lg:grid lg:grid-cols-2">
{#each cards as card}
<Card cardData={card} {onCardChecked} />
{/each}
</div>
<p>Kortteja valittu: {selected_cliches.length} / 5</p>
{#if selected_cliches.length == 5}
<button onclick={() => roundNumber++} class="button button-primary mx-auto"
>Valitse kortit</button
>
{:else}
<button class="button button-disabled mx-auto" disabled>Korjaa valintaa</button>
{/if}
{/if}
{#if roundNumber == 2 && data.selected_cliches.baseCards.length == 0}
<h2 class="text-center text-xl font-semibold">Bonus kortit</h2>
<div class="flex flex-col space-y-4 lg:grid lg:grid-cols-2">
{#each cards as card}
{#if !selected_cliches.some((c) => c.id === card.id)}
<!-- <p>Kortti {card.title} valittu</p> -->
<Card cardData={card} onCardChecked={onBonusCardChecked} />
<!-- {:else} -->
{/if}
{/each}
</div>
<p>Kortteja valittu: {selected_bonus_cliches.length} / 3</p>
{#if selected_bonus_cliches.length < 4}
<!-- <button onclick={() => roundNumber++} class="button button-primary mx-auto"
>Aloita peli</button
> -->
<form action="?/startGame" method="post" use:enhance>
<input type="hidden" name="selected_cliches" value={JSON.stringify(selected_cliches)} />
<input
type="hidden"
name="selected_bonus_cliches"
value={JSON.stringify(selected_bonus_cliches)}
/>
<button type="submit" class="button button-primary mx-auto">Aloita peli</button>
</form>
{:else}
<button class="button button-disabled mx-auto" disabled>Korjaa valintaa</button>
{/if}
{/if}
{#if data.selected_cliches.baseCards.length > 0}
<p class="text-center text-xl font-semibold">Peli alkaa</p>
<form action="?/endGame" method="post" use:enhance>
<div>
<p class="text-center">Valitut kortit</p>
{#each data.selected_cliches.baseCards as cliche}
<Card cardData={cliche} onCardChecked={setResult} isBonus={false} />
{/each}
</div>
<div>
<p class="text-center">Valitut bonus kortit</p>
{#each data.selected_cliches.bonusCards as cliche}
<Card cardData={cliche} onCardChecked={setResult} isBonus={true} />
{/each}
</div>
<input type="hidden" name="userId" id="userId" value={data.user.id} />
<input type="hidden" name="movieId" id="movieId" value={data.movie.id} />
<input
type="hidden"
name="result_cards"
id="result_cards"
value={JSON.stringify(result_cards)}
/>
<input
type="hidden"
name="result_bonus_cards"
id="result_bonus_cards"
value={JSON.stringify(result_bonus_cards)}
/>
<input
type="hidden"
name="bonusCards"
id="bonusCards"
value={JSON.stringify(data.selected_cliches.bonusCards)}
/>
<button type="submit" class="button button-primary mx-auto">Lopeta</button>
</form>
{/if}
</div>
<style>
</style>
+13
View File
@@ -0,0 +1,13 @@
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<div class="flex flex-col items-center space-y-4">
<h1 class="text-2xl font-semibold">Game Over!</h1>
<p class="text-md">Peli on päättynyt!</p>
<p class="text-md">Sait</p>
<p class="rounded-xl border-2 p-4 text-xl font-semibold">{data.score}</p>
<p class="text-md">pistettä!</p>
</div>
+5
View File
@@ -0,0 +1,5 @@
export function load({ params }) {
return {
score: params.score
};
}
+10
View File
@@ -6,5 +6,15 @@ export const load: PageServerLoad = async (event) => {
sameSite: 'strict',
secure: false,
});
event.cookies.delete('selected_cliches', {
path: '/',
sameSite: 'strict',
secure: false,
});
event.cookies.delete('selected_bonus_cliches', {
path: '/',
sameSite: 'strict',
secure: false,
});
throw redirect(302, '/login');
};