Add admin panel

This commit is contained in:
Esa Kataja
2024-12-02 16:18:31 +02:00
parent aed5c675d3
commit a8809ace9b
2 changed files with 317 additions and 102 deletions
+132 -52
View File
@@ -1,60 +1,131 @@
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import type { Actions } from './$types'; import type { Actions, PageServerLoad } from './$types';
const api_url = import.meta.env.VITE_API_URL; 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 = { export const actions: Actions = {
addUser: async ({ request, fetch }) => { addUser: async ({ request }) => {
const formData = await request.formData(); const data = await request.formData();
const userData = Object.fromEntries(formData); const username = data.get('username');
const password = data.get('password');
const response = await fetch(`${api_url}/admin/user`, { try {
method: 'POST', const response = await fetch(`${api_url}/admin/user`, {
headers: { method: 'POST',
'Content-Type': 'application/json' headers: {
}, 'Content-Type': 'application/json',
body: JSON.stringify(userData) },
}); body: JSON.stringify({
username,
password
})
});
if (!response.ok) { if (!response.ok) {
throw error(response.status, 'Failed to add user'); 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' };
} }
return { success: true };
}, },
deleteUser: async ({ request, fetch }) => { deleteUser: async ({ request }) => {
const formData = await request.formData(); const data = await request.formData();
const userId = formData.get('userId'); const userId = data.get('userId');
const response = await fetch(`${api_url}/admin/user/${userId}`, { try {
method: 'DELETE' const response = await fetch(`${api_url}/admin/user?user_id=${userId}`, {
}); method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) { if (!response.ok) {
throw error(response.status, 'Failed to delete user'); 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' };
} }
return { success: true };
}, },
addMovie: async ({ request, fetch }) => { addMovie: async ({ request }) => {
const formData = await request.formData(); const data = await request.formData();
const movieData = Object.fromEntries(formData); const movieData = {
imdb_id: data.get('imdb_id'),
showtime: data.get('showtime'),
plot: data.get('plot')
};
const response = await fetch(`${api_url}/admin/movie`, { try {
method: 'POST', const response = await fetch(`${api_url}/admin/movie`, {
headers: { method: 'POST',
'Content-Type': 'application/json' headers: {
}, 'Content-Type': 'application/json'
body: JSON.stringify(movieData) },
}); body: JSON.stringify(movieData)
});
if (!response.ok) { if (!response.ok) {
throw error(response.status, 'Failed to add movie'); 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' };
} }
return { success: true };
}, },
setMovieWatched: async ({ request, fetch }) => { setMovieWatched: async ({ request, fetch }) => {
@@ -77,22 +148,31 @@ export const actions: Actions = {
}, },
addCard: async ({ request, fetch }) => { addCard: async ({ request, fetch }) => {
const formData = await request.formData(); const data = await request.formData();
const cardData = Object.fromEntries(formData); const cardData = {
title: data.get('title'),
description: data.get('description'),
point_value: Number(data.get('point_value'))
};
const response = await fetch(`${api_url}/admin/card`, { try {
method: 'POST', const response = await fetch(`${api_url}/admin/card`, {
headers: { method: 'POST',
'Content-Type': 'application/json' headers: {
}, 'Content-Type': 'application/json'
body: JSON.stringify(cardData) },
}); body: JSON.stringify(cardData)
});
if (!response.ok) { if (!response.ok) {
throw error(response.status, 'Failed to add card'); 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' };
} }
return { success: true };
}, },
deleteCard: async ({ request, fetch }) => { deleteCard: async ({ request, fetch }) => {
+184 -49
View File
@@ -1,6 +1,9 @@
<script lang="ts"> <script lang="ts">
import { enhance } from '$app/forms'; import { enhance } from '$app/forms';
let activeTab = 'users'; let activeTab = 'users';
export let data;
const { users, movies, cards } = data;
</script> </script>
<div class="admin-panel"> <div class="admin-panel">
@@ -19,79 +22,153 @@
<div class="tab-content"> <div class="tab-content">
{#if activeTab === 'users'} {#if activeTab === 'users'}
<div class="section"> <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> <h2>Add User</h2>
<form method="POST" action="?/addUser" use:enhance> <form method="POST" action="?/addUser" use:enhance>
<div class="form-group"> <div class="form-group">
<label for="username">Username</label> <label for="username">Username:</label>
<input type="text" id="username" name="username" required> <input type="text" id="username" name="username" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">Email</label> <label for="password">Password:</label>
<input type="email" id="email" name="email" required> <input type="password" id="password" name="password" required>
</div> </div>
<button type="submit">Add User</button> <button type="submit">Add User</button>
</form> </form>
<h2>Delete User</h2>
<form method="POST" action="?/deleteUser" use:enhance>
<div class="form-group">
<label for="userId">User ID</label>
<input type="text" id="userId" name="userId" required>
</div>
<button type="submit" class="danger">Delete User</button>
</form>
</div> </div>
{/if} {/if}
{#if activeTab === 'movies'} {#if activeTab === 'movies'}
<div class="section"> <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> <h2>Add Movie</h2>
<form method="POST" action="?/addMovie" use:enhance> <form method="POST" action="?/addMovie" use:enhance>
<div class="form-group"> <div class="form-group">
<label for="title">Title</label> <label for="imdb_id">IMDB ID:</label>
<input type="text" id="title" name="title" required> <input type="text" id="imdb_id" name="imdb_id" placeholder="tt1234567" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">Description</label> <label for="showtime">Showtime:</label>
<textarea id="description" name="description" required></textarea> <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> </div>
<button type="submit">Add Movie</button> <button type="submit">Add Movie</button>
</form> </form>
<h2>Mark Movie as Watched</h2>
<form method="POST" action="?/setMovieWatched" use:enhance>
<div class="form-group">
<label for="movieId">Movie ID</label>
<input type="text" id="movieId" name="movieId" required>
</div>
<button type="submit">Mark as Watched</button>
</form>
</div> </div>
{/if} {/if}
{#if activeTab === 'cards'} {#if activeTab === 'cards'}
<div class="section"> <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> <h2>Add Card</h2>
<form method="POST" action="?/addCard" use:enhance> <form method="POST" action="?/addCard" use:enhance>
<div class="form-group"> <div class="form-group">
<label for="cardName">Card Name</label> <label for="title">Title:</label>
<input type="text" id="cardName" name="cardName" required> <input type="text" id="title" name="title" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="cardValue">Card Value</label> <label for="description">Description:</label>
<input type="number" id="cardValue" name="cardValue" required> <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> </div>
<button type="submit">Add Card</button> <button type="submit">Add Card</button>
</form> </form>
<h2>Delete Card</h2>
<form method="POST" action="?/deleteCard" use:enhance>
<div class="form-group">
<label for="cardId">Card ID</label>
<input type="text" id="cardId" name="cardId" required>
</div>
<button type="submit" class="danger">Delete Card</button>
</form>
</div> </div>
{/if} {/if}
</div> </div>
@@ -99,16 +176,19 @@
<style> <style>
.admin-panel { .admin-panel {
max-width: 800px; max-width: 1200px;
margin: 2rem auto; margin: 2rem auto;
padding: 1rem; padding: 1rem;
background-color: #1a472a; /* Dark Christmas green */
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
} }
.tabs { .tabs {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
margin-bottom: 2rem; margin-bottom: 2rem;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #2c6842;
padding-bottom: 1rem; padding-bottom: 1rem;
} }
@@ -118,26 +198,32 @@
background: none; background: none;
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
color: #666; color: #fff;
border-radius: 4px; border-radius: 4px;
transition: background-color 0.2s;
}
.tabs button:hover {
background: #2c6842;
} }
.tabs button.active { .tabs button.active {
background: #007bff; background: #c41e3a; /* Christmas red for active tab */
color: white; color: white;
} }
.section { .section {
background: white; background: #234332; /* Slightly lighter green for sections */
padding: 2rem; padding: 2rem;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
margin-bottom: 1rem;
} }
h2 { h2 {
margin-top: 2rem; margin-top: 2rem;
margin-bottom: 1rem; margin-bottom: 1rem;
color: #333; color: #fff;
} }
.form-group { .form-group {
@@ -147,15 +233,22 @@
label { label {
display: block; display: block;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
color: #555; color: #fff;
} }
input, textarea { input, textarea {
width: 100%; width: 100%;
padding: 0.5rem; padding: 0.5rem;
border: 1px solid #ddd; border: 1px solid #2c6842;
border-radius: 4px; border-radius: 4px;
font-size: 1rem; font-size: 1rem;
background: #1a472a;
color: #fff;
}
input:focus, textarea:focus {
outline: none;
border-color: #c41e3a;
} }
textarea { textarea {
@@ -165,16 +258,17 @@
button { button {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
background: #007bff; background: #c41e3a; /* Christmas red for buttons */
color: white; color: white;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
transition: background-color 0.2s;
} }
button:hover { button:hover {
background: #0056b3; background: #a01830;
} }
button.danger { button.danger {
@@ -184,4 +278,45 @@
button.danger:hover { button.danger:hover {
background: #c82333; 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> </style>