Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to this project are documented in this file.

## [0.7] - 2026-06-02

### Added
- Admin user login.
- Browsing without editing capabilities.

### Changed
- Updated translations.

## [0.6.1] - 2026-06-01

### Changed
Expand Down
13 changes: 12 additions & 1 deletion add.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ <h1 data-translate="comic.title.add">Add New Comic</h1>
<script>
// Load translations when the page loads
document.addEventListener('DOMContentLoaded', async () => {
if (!isLoggedIn()) {
window.location.href = 'login.html?redirect=add.html';
return;
}

try {
// Get the saved language from localStorage (set by main page)
const savedLang = localStorage.getItem('selectedLanguage') || 'en';
Expand Down Expand Up @@ -160,6 +165,10 @@ <h1 data-translate="comic.title.add">Add New Comic</h1>
}
});

function isLoggedIn() {
return !!localStorage.getItem('authToken');
}

// Handle form submission
async function saveComic(event) {
event.preventDefault();
Expand All @@ -178,8 +187,10 @@ <h1 data-translate="comic.title.add">Add New Comic</h1>
const response = await fetch('/api/comics', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('authToken')}`
},
credentials: 'same-origin',
body: JSON.stringify(formData)
});

Expand Down
13 changes: 13 additions & 0 deletions browse.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Browse Comics</title>
<meta http-equiv="refresh" content="0; url=index.html?mode=browse">
</head>
<body>
<p>Redirecting to the browse-only view...</p>
<p>If you are not redirected automatically, <a href="index.html?mode=browse">click here</a>.</p>
</body>
</html>
13 changes: 12 additions & 1 deletion edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ <h1 data-translate="comic.title.edit">Edit Comic</h1>
const urlParams = new URLSearchParams(window.location.search);
const comicId = urlParams.get('id');

function isLoggedIn() {
return !!localStorage.getItem('authToken');
}

// Load comic data
async function loadComicData() {
try {
Expand Down Expand Up @@ -174,8 +178,10 @@ <h1 data-translate="comic.title.edit">Edit Comic</h1>
const response = await fetch(`/api/comics/${comicId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('authToken')}`
},
credentials: 'same-origin',
body: JSON.stringify(comicData)
});

Expand All @@ -190,6 +196,11 @@ <h1 data-translate="comic.title.edit">Edit Comic</h1>

// Load translations when the page loads
document.addEventListener('DOMContentLoaded', async () => {
if (!isLoggedIn()) {
window.location.href = 'login.html?redirect=' + encodeURIComponent(window.location.pathname + window.location.search);
return;
}

try {
// Get the saved language from localStorage (set by main page)
const savedLang = localStorage.getItem('selectedLanguage') || 'en';
Expand Down
136 changes: 95 additions & 41 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
gap: 10px;
align-items: center;
}
button, .add-btn {
button, .btn, .add-btn {
padding: 0 20px;
border: none;
border-radius: 4px;
Expand All @@ -110,12 +110,14 @@
line-height: 1;
min-width: 120px;
box-sizing: border-box;
text-decoration: none;
}
button {
button, .btn {
background-color: #333;
color: white;
}
button:hover {
button:hover,
.btn:hover {
background-color: #555;
}
.add-btn {
Expand Down Expand Up @@ -271,14 +273,20 @@
<body>
<div class="container">
<div class="header">
<h1>Comic Collection Manager <span id="record-count" data-translate="app.recordCount"></span></h1>
<select id="languageSelect" onchange="translatePage()" class="language-select">
<option value="en">English</option>
<option value="pl">Polski</option>
<option value="sv">Svenska</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
</select>
<div>
<h1>Comic Collection Manager <span id="record-count" data-translate="app.recordCount"></span></h1>
</div>
<div class="button-group">
<a href="login.html" class="btn" id="loginBtn">Login</a>
<button type="button" class="btn" id="logoutBtn" onclick="logout()" style="display:none">Logout</button>
<select id="languageSelect" onchange="translatePage()" class="language-select">
<option value="en">English</option>
<option value="pl">Polski</option>
<option value="sv">Svenska</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
</select>
</div>
</div>

<div class="search-section">
Expand Down Expand Up @@ -316,15 +324,8 @@ <h2 data-translate="search.title">Search Comics</h2>
</div>
</div>
<div class="button-group">
<button type="submit" data-translate="search.searchButton">Search</button>
<a href="add.html" class="add-btn" data-translate="search.addButton">Add New Comic</a>
<select id="languageSelect" onchange="translatePage()" class="language-select">
<option value="en">English</option>
<option value="pl">Polski</option>
<option value="sv">Svenska</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
</select>
<button type="button" onclick="searchComics()" data-translate="search.searchButton">Search</button>
<a href="add.html" class="add-btn" data-translate="search.addButton" id="addComicBtn">Add New Comic</a>
</div>
<div class="button-group">
<button type="button" onclick="importFromGoogleSheets()" data-translate="search.importButton">Import from Google Sheets</button>
Expand Down Expand Up @@ -359,6 +360,54 @@ <h2 data-translate="search.title">Search Comics</h2>
let comicDatabase = [];
let currentSort = { field: null, direction: 'asc' };

function isLoggedIn() {
return !!localStorage.getItem('authToken');
}

function getAuthHeaders() {
const token = localStorage.getItem('authToken');
return token ? { Authorization: `Bearer ${token}` } : {};
}

function updateAuthUI() {
const loginBtn = document.getElementById('loginBtn');
const logoutBtn = document.getElementById('logoutBtn');
const addComicBtn = document.getElementById('addComicBtn');
const importButton = document.querySelector('button[onclick="importFromGoogleSheets()"]');

const authenticated = isLoggedIn();

if (authenticated) {
if (loginBtn) loginBtn.style.display = 'none';
if (logoutBtn) logoutBtn.style.display = 'inline-flex';
} else {
if (loginBtn) loginBtn.style.display = 'inline-flex';
if (logoutBtn) logoutBtn.style.display = 'none';
}

if (!authenticated) {
if (addComicBtn) addComicBtn.style.display = 'none';
if (importButton) importButton.style.display = 'none';
} else {
if (addComicBtn) addComicBtn.style.display = 'inline-flex';
if (importButton) importButton.style.display = 'inline-flex';
}
}

function goToLogin() {
const redirect = encodeURIComponent(window.location.pathname + window.location.search);
window.location.href = `login.html?redirect=${redirect}`;
}

async function logout() {
localStorage.removeItem('authToken');
await fetch('/api/logout', {
method: 'POST',
credentials: 'same-origin'
}).catch(() => {});
updateAuthUI();
}

function getWriters(comic) {
if (Array.isArray(comic.writers)) {
return comic.writers.filter(Boolean);
Expand Down Expand Up @@ -505,9 +554,25 @@ <h2 data-translate="search.title">Search Comics</h2>
function displayComics(comics) {
const tbody = document.getElementById('comicsTableBody');
tbody.innerHTML = '';

const canEdit = isLoggedIn();

comics.forEach(comic => {
const row = document.createElement('tr');
const actions = canEdit ? `
<button class="edit-btn" onclick="editComic('${comic.id}')" title="Edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</button>
<button class="delete-btn" onclick="deleteComic('${comic.id}')" title="Delete">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</button>
` : '';

row.innerHTML = `
<td>${comic.seriesTitle}</td>
<td>${comic.issueTitle}</td>
Expand All @@ -517,20 +582,7 @@ <h2 data-translate="search.title">Search Comics</h2>
<td>${comic.language}</td>
<td>${comic.publisher}</td>
<td>${comic.publicationYear}</td>
<td>
<button class="edit-btn" onclick="editComic('${comic.id}')" title="Edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</button>
<button class="delete-btn" onclick="deleteComic('${comic.id}')" title="Delete">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</button>
</td>
<td>${actions}</td>
`;
tbody.appendChild(row);
});
Expand Down Expand Up @@ -816,11 +868,10 @@ <h2 data-translate="search.title">Search Comics</h2>
const response = await fetch('/api/import', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ url: sheetUrl.trim() })
});

'Content-Type': 'application/json',
...getAuthHeaders()
},
credentials: 'same-origin',
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || 'Import failed');
Expand Down Expand Up @@ -913,7 +964,9 @@ <h2 data-translate="search.title">Search Comics</h2>

try {
const response = await fetch(`/api/comics/${id}`, {
method: 'DELETE'
method: 'DELETE',
headers: getAuthHeaders(),
credentials: 'same-origin'
});

if (!response.ok) {
Expand All @@ -937,6 +990,7 @@ <h2 data-translate="search.title">Search Comics</h2>

// Initial load of comics
loadComicsFromServer();
updateAuthUI();

// Return to top functionality
const returnToTopButton = document.getElementById('returnToTop');
Expand Down
Loading