feat: implement Hybrid SSR for bookshelf page
- Add LibraryData type to templates/types.go - Update bookshelf template to accept libraries parameter - Render libraries server-side for faster initial page load - Libraries now populated from server data instead of AJAX fetch - JavaScript still uses API for dynamic content (bookshelf items) - Update /bookshelf route to fetch libraries server-side before render - Properly handle UUID and pgtype.Text conversions - Maintain API endpoint compatibility for JavaScript calls This improves initial page load performance while preserving dynamic functionality via API calls.
This commit is contained in:
+31
-10
@@ -339,20 +339,41 @@ func main() {
|
||||
|
||||
// Bookshelf route (protected) - new default for logged-in users
|
||||
protected.GET("/bookshelf", func(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userEmail := c.Get("user_email").(string)
|
||||
userUsername := c.Get("user_username").(string)
|
||||
userRole := c.Get("user_role").(string)
|
||||
user := c.Get("user").(database.Users)
|
||||
userUUID := uuid.UUID(user.ID.Bytes)
|
||||
|
||||
user := templates.User{
|
||||
ID: userID,
|
||||
Email: userEmail,
|
||||
Username: userUsername,
|
||||
Role: userRole,
|
||||
// Fetch libraries server-side for SSR
|
||||
librariesData, err := libraryHandler.GetUserVisibleLibrariesData(c.Request().Context(), pgtype.UUID{Bytes: userUUID, Valid: true})
|
||||
if err != nil {
|
||||
return c.HTML(http.StatusInternalServerError, "Error loading libraries")
|
||||
}
|
||||
|
||||
// Convert to template format
|
||||
libraries := make([]templates.LibraryData, len(librariesData))
|
||||
for i, lib := range librariesData {
|
||||
libUUID := uuid.UUID(lib.ID.Bytes)
|
||||
description := ""
|
||||
if lib.Description.Valid {
|
||||
description = lib.Description.String
|
||||
}
|
||||
libraries[i] = templates.LibraryData{
|
||||
ID: libUUID.String(),
|
||||
Name: lib.Name,
|
||||
Description: description,
|
||||
TypeName: lib.TypeName,
|
||||
}
|
||||
}
|
||||
|
||||
userTemplate := templates.User{
|
||||
ID: userUUID.String(),
|
||||
Email: user.Email,
|
||||
Username: user.Username,
|
||||
Role: user.Role,
|
||||
}
|
||||
|
||||
// Render template WITH libraries data (SSR)
|
||||
var buf bytes.Buffer
|
||||
err := templates.BookShelf(user).Render(c.Request().Context(), &buf)
|
||||
err = templates.BookShelf(userTemplate, libraries).Render(c.Request().Context(), &buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+41
-13
@@ -1,6 +1,6 @@
|
||||
package templates
|
||||
|
||||
templ BookShelf(user User) {
|
||||
templ BookShelf(user User, libraries []LibraryData) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -15,14 +15,20 @@ templ BookShelf(user User) {
|
||||
</head>
|
||||
<body class="theme-tokyo-night">
|
||||
@Header(user, "/bookshelf")
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:8 py-8">
|
||||
<!-- Library Selector -->
|
||||
<div class="mb-8">
|
||||
<div class="flex flex-col sm:flex-row gap-4 items-center justify-between">
|
||||
<div class="flex-1 w-full">
|
||||
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">Select Library</label>
|
||||
<select id="library-select" onchange="selectLibrary()" class="w-full px-4 py-3 border rounded-lg text-lg" style="background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border);">
|
||||
<option value="">Loading libraries...</option>
|
||||
if len(libraries) == 0 {
|
||||
<option value="">No libraries available</option>
|
||||
} else {
|
||||
for _, lib := range libraries {
|
||||
<option value={ lib.ID }>{ lib.Name }</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@@ -57,21 +63,43 @@ templ BookShelf(user User) {
|
||||
|
||||
<script src="/static/theme.js"></script>
|
||||
<script>
|
||||
let libraries = [];
|
||||
// Libraries already loaded server-side
|
||||
const libraries = [
|
||||
if len(libraries) > 0 {
|
||||
for _, lib := range libraries {
|
||||
{
|
||||
id: "{ lib.ID }",
|
||||
name: "{ lib.Name }",
|
||||
description: "{ lib.Description }",
|
||||
type_name: "{ lib.TypeName }"
|
||||
},
|
||||
}
|
||||
}
|
||||
];
|
||||
let currentLibrary = null;
|
||||
let mediaItems = [];
|
||||
|
||||
function loadLibraries() {
|
||||
const loading = document.getElementById('loading');
|
||||
function selectLibrary() {
|
||||
const librarySelect = document.getElementById('library-select');
|
||||
|
||||
loading.style.display = 'block';
|
||||
const selectedId = librarySelect.value;
|
||||
currentLibrary = libraries.find(lib => lib.id === selectedId);
|
||||
if (currentLibrary) {
|
||||
loadBookshelf();
|
||||
}
|
||||
}
|
||||
|
||||
fetch('/api/libraries/visible', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token'),
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
// Auto-select first library if available
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadTheme();
|
||||
if (libraries.length > 0) {
|
||||
const librarySelect = document.getElementById('library-select');
|
||||
currentLibrary = libraries[0];
|
||||
librarySelect.value = currentLibrary.id;
|
||||
loadBookshelf();
|
||||
} else {
|
||||
showEmptyState();
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -38,6 +38,13 @@ type BookData struct {
|
||||
CoverImagePath string
|
||||
}
|
||||
|
||||
type LibraryData struct {
|
||||
ID string
|
||||
Name string
|
||||
Description string
|
||||
TypeName string
|
||||
}
|
||||
|
||||
type DeviceData struct {
|
||||
ID string
|
||||
DeviceName string
|
||||
|
||||
Reference in New Issue
Block a user