Add admin panel
This commit is contained in:
@@ -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`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(userData)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
try {
|
||||||
throw error(response.status, 'Failed to add user');
|
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' };
|
||||||
}
|
}
|
||||||
|
|
||||||
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
@@ -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>
|
||||||
Reference in New Issue
Block a user