Update all page templates to accept an optional error message parameter and display it via the ErrorToast component when data loading fails.
116 lines
5.9 KiB
Templ
116 lines
5.9 KiB
Templ
package templates
|
|
|
|
import "bookhoard/internal/handlers"
|
|
|
|
templ Progress(user User, progressData []handlers.ProgressWithMedia, errorMessage string) {
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Reading Progress - Bookhoard</title>
|
|
<script src="/static/htmx.min.js"></script>
|
|
<script src="/static/toast.js"></script>
|
|
<script src="/static/header.js"></script>
|
|
<link href="/static/style.css" rel="stylesheet">
|
|
</head>
|
|
<body class="theme-{ user.Theme }">
|
|
@Header(user, "/progress")
|
|
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold" style="color: var(--text-primary)">Reading Progress</h1>
|
|
<p style="color: var(--text-secondary)">Track your reading progress across all devices</p>
|
|
</div>
|
|
|
|
<div id="progress-container" class="space-y-6">
|
|
if len(progressData) == 0 {
|
|
<div class="text-center py-16" style="color: var(--text-secondary)">
|
|
<div class="text-6xl mb-4">📖</div>
|
|
<h3 class="text-xl font-semibold mb-2" style="color: var(--text-primary)">No Progress Yet</h3>
|
|
<p>Start reading a book to track your progress</p>
|
|
</div>
|
|
}
|
|
|
|
for _, item := range progressData {
|
|
<div class="card p-6 rounded-lg border" style="background-color: var(--bg-secondary); border-color: var(--border);">
|
|
<div class="flex gap-6">
|
|
<div class="flex-shrink-0">
|
|
<img src="{ item.CoverImagePath }" alt="Cover"
|
|
class="w-24 h-32 object-cover rounded"
|
|
onerror="this.src='/static/placeholder-book.svg'">
|
|
</div>
|
|
<div class="flex-1">
|
|
<div class="flex justify-between items-start mb-2">
|
|
<div>
|
|
<h3 class="font-semibold text-lg" style="color: var(--text-primary)">{ item.Title }</h3>
|
|
if item.Author != "" {
|
|
<p class="text-sm" style="color: var(--text-secondary)">by { item.Author }</p>
|
|
}
|
|
</div>
|
|
<div class="text-right">
|
|
<span class="text-2xl font-bold" style="color: var(--accent)">{ item.ProgressPercentage }%</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<div class="w-full bg-gray-700 rounded-full h-3">
|
|
<div class="h-3 rounded-full transition-all" style="width: { item.ProgressPercentage }%; background-color: var(--accent);"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
|
|
<div>
|
|
<p style="color: var(--text-secondary)">Current Position</p>
|
|
<p style="color: var(--text-primary)">
|
|
{ item.CurrentPage } / { item.TotalPages }
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p style="color: var(--text-secondary)">Last Updated</p>
|
|
<p style="color: var(--text-primary)">{ item.LastUpdated }</p>
|
|
</div>
|
|
<div>
|
|
<p style="color: var(--text-secondary)">Synced From</p>
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-xl">{ item.DeviceIcon }</span>
|
|
<span style="color: var(--text-primary)">{ item.DeviceName }</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
if item.DeviceIcon != "" {
|
|
<div class="mt-3 pt-3 border-t" style="border-color: var(--border);">
|
|
<div class="flex items-center gap-2 text-xs" style="color: var(--text-secondary);">
|
|
<span>🔄</span>
|
|
<span>Last sync from <strong>{ item.DeviceName }</strong></span>
|
|
<span>({ item.DeviceType })</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
if item.EpubCFI != "" {
|
|
<div class="mt-2 text-xs" style="color: var(--text-secondary);">
|
|
<span>📍</span>
|
|
<span>CFI: { item.EpubCFI }</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function logout() {
|
|
localStorage.removeItem('token');
|
|
window.location.href = '/login';
|
|
}
|
|
</script>
|
|
|
|
@ErrorToast(errorMessage)
|
|
</body>
|
|
</html>
|
|
}
|