Introduces support for custom OpenAI-compatible AI providers with configurable base URLs, including UI, backend validation, and connection testing. Enhances qBittorrent integration to support HTTP Basic Auth for reverse proxies, adds detailed debug logging, and updates documentation for both features. Also improves login page description logic and AI prompt generation for recommendations.
5.1 KiB
Login Page
Status: ✅ Implemented | Real floating book covers with professional animations
Stylized entry point with Plex/Audiobookshelf authentication, animated floating popular audiobook covers, and dynamic description based on backend configuration.
Design
- Full-screen immersive experience with gradient background
- Centered hero with login button
- Animated floating real audiobook covers (popular releases)
- 100 randomly positioned covers with varied sizes, animations, and depth
- Multi-layer depth effect with z-index layering (0-20)
- Dark theme optimized with glassmorphism card
- Professional streaming service aesthetic
- Dynamic description based on backend mode (Plex/Audiobookshelf) and automation status
Authentication Flow
- User visits protected route → redirected to
/login - Clicks "Login with Plex"
POST /api/auth/plex/login→ requests PIN- Opens Plex OAuth in popup
- Polls
/api/auth/plex/callbackfor authorization - User authorizes in Plex popup
- Callback receives auth token
- Creates/updates user in DB
- Returns JWT tokens
- Client stores tokens in localStorage
- Redirects to originally requested page or homepage
Book Covers
Data Source: GET /api/audiobooks/covers
- Returns up to 200 popular audiobook covers
- Uses cached thumbnails from
audible_cachetable - Shuffled on each request for variety
- Fallback to placeholder elements if API fails
Display:
- 100 covers shown simultaneously for immersive experience
- Varied sizes: 80-160px wide (1.5 aspect ratio)
- Opacity range: 0.15-0.35 for subtle layering and depth
- Staggered animation delays (0-10s) for natural movement
- Z-index layering (0-20) for depth perception
- Programmatic positioning using seeded random for consistency
- Lazy loading (first 10 eager, rest lazy) for performance
- Hover pauses animation and scales for interaction
Positioning Algorithm:
- Seeded random function ensures consistent positions per cover index
- Random distribution across full viewport (0-100% both axes)
- Each cover gets unique: size, position, opacity, delay, z-index, animation type
- Seed multipliers (7, 13, 17, 23, 29, 31) prevent pattern repetition
- Math.sin() based pseudo-random for deterministic results
Dynamic Description
Description text adapts to backend configuration:
Plex + Automation Enabled: "Request audiobooks and they'll automatically download and appear in your Plex library" Plex + No Automation: "Request audiobooks for your Plex library" Audiobookshelf + Automation: "Request audiobooks and they'll automatically download and appear in your Audiobookshelf library" Audiobookshelf + No Automation: "Request audiobooks for your Audiobookshelf library" Loading State: "Your Personal Audiobook Library Manager"
Automation is detected by checking for configured indexer (Prowlarr) via /api/auth/providers endpoint.
State
interface LoginPageState {
isLoggingIn: boolean;
error: string | null;
pinId: number | null;
authWindow: Window | null;
bookCovers: BookCover[];
showAdminLogin: boolean;
adminUsername: string;
adminPassword: string;
authProviders: {
backendMode: string;
providers: string[];
registrationEnabled: boolean;
hasLocalUsers: boolean;
oidcProviderName: string | null;
localLoginDisabled: boolean;
automationEnabled: boolean;
} | null;
}
interface BookCover {
asin: string;
title: string;
author: string;
coverUrl: string;
}
Error Handling
Popup Blocked: "Popup was blocked. Please allow popups." Login Timeout: 2 min polling timeout Plex Unavailable: "Plex services currently unavailable." Covers Fail: Silent fallback to placeholder gradient elements
Animations
Three animation speeds with realistic floating motion:
@keyframes float-slow {
/* 22s cycle with 4 keyframes */
0%, 100% { transform: translateY(0) translateX(0) rotate(0deg) scale(1); }
25% { transform: translateY(-25px) translateX(15px) rotate(2deg) scale(1.03); }
50% { transform: translateY(-35px) translateX(25px) rotate(4deg) scale(1.05); }
75% { transform: translateY(-20px) translateX(-10px) rotate(-2deg) scale(1.02); }
}
@keyframes float-medium {
/* 16s cycle with 3 keyframes */
0%, 100% { transform: translateY(0) translateX(0) rotate(0deg) scale(1); }
33% { transform: translateY(-30px) translateX(-20px) rotate(-3deg) scale(1.04); }
66% { transform: translateY(-15px) translateX(10px) rotate(3deg) scale(1.02); }
}
@keyframes float-fast {
/* 12s cycle with 2 keyframes */
0%, 100% { transform: translateY(0) translateX(0) rotate(0deg) scale(1); }
50% { transform: translateY(-28px) translateX(18px) rotate(5deg) scale(1.06); }
}
Features:
- Scale transformations (1.02-1.06) for depth
- Rotation (-5° to +5°) for natural movement
- X/Y translation for floating effect
- Hover pauses animation
- Shadow-2xl for 3D depth
Security
- Tokens in localStorage (access 1hr, refresh 7d)
- Tokens cleared on logout
- OAuth state parameter validation
- SameSite cookie attributes
Tech Stack
- Next.js 14+ Client Component
- Tailwind CSS with custom animations
- Plex OAuth via AuthContext