Files

749 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Profile - Akiyama Manga</title>
<style>
:root {
--bg: #09090b;
--card: #1a1a1a;
--border: rgba(255, 255, 255, 0.1);
--text: #ffffff;
--text-muted: rgba(255, 255, 255, 0.7);
--primary: #3b82f6;
--primary-dark: #2563eb;
--accent: #ec4899;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
}
/* Header */
header {
position: sticky;
top: 0;
z-index: 50;
width: 100%;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
background: rgba(9, 9, 11, 0.8);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.header-content {
max-width: 1280px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
gap: 1rem;
}
.logo {
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
color: var(--text);
font-size: 1.125rem;
font-weight: 700;
}
.logo-icon {
width: 2rem;
height: 2rem;
border-radius: 0.5rem;
background: linear-gradient(135deg, var(--primary), var(--accent));
display: flex;
align-items: center;
justify-content: center;
}
.nav {
display: none;
align-items: center;
gap: 0.5rem;
flex: 1;
max-width: 40rem;
margin: 0 1rem;
}
.nav-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border: none;
border-radius: 0.5rem;
background: transparent;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition: all 0.2s;
text-decoration: none;
}
.nav-button:hover {
background: rgba(255, 255, 255, 0.05);
color: var(--text);
}
@media (min-width: 768px) {
.nav {
display: flex;
}
}
/* Auth Container */
.auth-container {
position: relative;
display: flex;
align-items: center;
gap: 1rem;
}
.auth-buttons {
display: flex;
gap: 0.5rem;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
transition: all 0.2s;
}
.btn-secondary {
border: 1px solid rgba(255, 255, 255, 0.2);
background: transparent;
color: var(--text);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.3);
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover {
background: var(--primary-dark);
}
/* User Profile Button */
.user-profile-btn {
display: none;
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
background: var(--primary);
color: white;
border: none;
cursor: pointer;
font-weight: bold;
font-size: 1.1rem;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.user-profile-btn:hover {
background: var(--primary-dark);
}
/* Dropdown Menu */
.dropdown-menu {
position: absolute;
top: 100%;
right: 0;
min-width: 200px;
margin-top: 0.5rem;
background: var(--card);
border-radius: 0.5rem;
border: 1px solid var(--border);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
display: none;
flex-direction: column;
z-index: 1000;
}
.dropdown-menu.active {
display: flex;
}
.dropdown-header {
padding: 1rem;
border-bottom: 1px solid var(--border);
font-size: 0.9rem;
}
.dropdown-user-name {
font-weight: 600;
color: var(--text);
margin-bottom: 0.25rem;
}
.dropdown-user-email {
font-size: 0.85rem;
color: var(--text-muted);
}
.dropdown-items {
display: flex;
flex-direction: column;
padding: 0.5rem;
}
.dropdown-item {
padding: 0.75rem 1rem;
color: var(--text);
text-decoration: none;
cursor: pointer;
background: none;
border: none;
text-align: left;
transition: background 0.2s;
font-size: 0.95rem;
width: 100%;
}
.dropdown-item:hover {
background: rgba(255, 255, 255, 0.1);
border-radius: 0.25rem;
}
.dropdown-item.danger {
color: #ef4444;
}
.dropdown-item.danger:hover {
background: rgba(239, 68, 68, 0.2);
}
/* Container */
.container {
max-width: 56rem;
margin: 0 auto;
padding: 2rem 1rem;
}
/* Profile Card */
.profile-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 0.75rem;
padding: 2rem;
margin-bottom: 2rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
@media (min-width: 768px) {
.profile-card {
flex-direction: row;
align-items: center;
}
}
.avatar {
width: 6rem;
height: 6rem;
min-width: 6rem;
border-radius: 50%;
background: var(--primary);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
font-weight: bold;
}
.profile-info {
flex: 1;
}
.profile-name {
font-size: 1.875rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.profile-contact {
display: flex;
flex-wrap: wrap;
gap: 1rem;
font-size: 0.875rem;
color: var(--text-muted);
margin-bottom: 1rem;
}
.profile-contact-item {
display: flex;
align-items: center;
gap: 0.5rem;
}
.profile-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
/* Stats Card */
.stats-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 0.75rem;
padding: 1.5rem;
margin-bottom: 2rem;
}
.stats-title {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
}
.stat-item {
padding: 1rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
border-radius: 0.5rem;
text-align: center;
}
.stat-value {
font-size: 1.5rem;
font-weight: 600;
color: var(--text);
margin-bottom: 0.25rem;
}
.stat-label {
font-size: 0.875rem;
color: var(--text-muted);
}
/* Activity Card */
.activity-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 0.75rem;
padding: 1.5rem;
}
.activity-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.activity-title {
font-size: 1.125rem;
font-weight: 600;
}
.activity-summary {
display: flex;
gap: 2rem;
font-size: 0.875rem;
color: var(--text-muted);
}
.activity-summary-item {
display: flex;
flex-direction: column;
}
.activity-summary-value {
font-weight: 700;
color: var(--text);
font-size: 1rem;
}
/* Library Stats */
.library-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 1rem;
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border);
}
.library-stat {
text-align: center;
}
.library-stat-value {
font-size: 1.5rem;
font-weight: 600;
display: block;
margin-bottom: 0.5rem;
}
.library-stat-label {
font-size: 0.875rem;
color: var(--text-muted);
}
/* Empty State */
.empty-state {
text-align: center;
padding: 2rem;
color: var(--text-muted);
}
.empty-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.empty-message {
font-size: 1rem;
margin-bottom: 1rem;
}
.empty-hint {
font-size: 0.875rem;
color: var(--text-muted);
}
/* Responsive */
@media (max-width: 640px) {
.profile-card {
padding: 1rem;
}
.profile-name {
font-size: 1.5rem;
}
.container {
padding: 1rem;
}
.avatar {
width: 5rem;
height: 5rem;
}
}
</style>
</head>
<body>
<!-- Header -->
<header>
<div class="header-content">
<a href="/" class="logo">
<div class="logo-icon">📚</div>
<span>Akiyama</span>
</a>
<nav class="nav">
<a href="/browse" class="nav-button">Browse</a>
<a href="/library" class="nav-button">Library</a>
</nav>
<div class="auth-container">
<div class="auth-buttons" id="authButtons">
<a href="/auth/signin" class="btn btn-secondary">Sign In</a>
<a href="/auth/signup" class="btn btn-primary">Sign Up</a>
</div>
<button class="user-profile-btn" id="userProfileBtn" style="display: none;">
<span id="userInitial">A</span>
</button>
<div class="dropdown-menu" id="dropdownMenu">
<div class="dropdown-header">
<div class="dropdown-user-name" id="dropdownUserName">User</div>
<div class="dropdown-user-email" id="dropdownUserEmail">user@example.com</div>
</div>
<div class="dropdown-items">
<a href="/profile" class="dropdown-item">My Profile</a>
<a href="/library" class="dropdown-item">My Library</a>
<a href="#" class="dropdown-item">Settings</a>
<button class="dropdown-item danger" id="logoutBtn">Logout</button>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="container">
<!-- Profile Card -->
<div class="profile-card">
<div class="avatar" id="profileAvatar">U</div>
<div class="profile-info">
<h1 class="profile-name" id="profileUsername">Loading...</h1>
<div class="profile-contact">
<div class="profile-contact-item">
<span>✉️</span>
<span id="profileEmail">user@example.com</span>
</div>
</div>
<div class="profile-actions">
<button class="btn btn-secondary" onclick="shareProfile()">Share Profile</button>
<a href="/settings" class="btn btn-primary">Edit Profile</a>
</div>
</div>
</div>
<!-- Stats Card -->
<div class="stats-card">
<h2 class="stats-title">Library Statistics</h2>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value" id="statsInLibrary">0</div>
<div class="stat-label">In Library</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statsReading">0</div>
<div class="stat-label">Reading</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statsCompleted">0</div>
<div class="stat-label">Completed</div>
</div>
<div class="stat-item">
<div class="stat-value" id="joinDate">--</div>
<div class="stat-label">Joined</div>
</div>
</div>
</div>
<!-- Activity Card -->
<div class="activity-card">
<div class="activity-header">
<h2 class="activity-title">Reading Activity</h2>
<div class="activity-summary">
<div class="activity-summary-item">
<span class="activity-summary-value" id="chaptersRead">0</span>
<span>chapters read</span>
</div>
<div class="activity-summary-item">
<span class="activity-summary-value" id="dayStreak">0</span>
<span>day streak</span>
</div>
</div>
</div>
<div class="library-stats">
<div class="library-stat">
<span class="library-stat-value" id="totalManga">0</span>
<span class="library-stat-label">Total Manga</span>
</div>
<div class="library-stat">
<span class="library-stat-value" id="avgRating">0</span>
<span class="library-stat-label">Avg Rating</span>
</div>
<div class="library-stat">
<span class="library-stat-value" id="totalChapters">0</span>
<span class="library-stat-label">Chapters</span>
</div>
<div class="library-stat">
<span class="library-stat-value" id="lastRead">--</span>
<span class="library-stat-label">Last Read</span>
</div>
</div>
</div>
</div>
<script>
// Get DOM elements
const authButtons = document.getElementById('authButtons');
const userProfileBtn = document.getElementById('userProfileBtn');
const dropdownMenu = document.getElementById('dropdownMenu');
const logoutBtn = document.getElementById('logoutBtn');
// Check authentication and load profile
async function checkAuth() {
try {
const response = await fetch('/api/auth/me', {
method: 'GET',
credentials: 'include'
});
if (response.ok) {
const user = await response.json();
showUserMenu(user);
loadUserProfile(user);
} else {
showLoginButtons();
showEmptyProfile();
}
} catch (error) {
console.error('Auth check failed:', error);
showLoginButtons();
showEmptyProfile();
}
}
// Show user menu
function showUserMenu(user) {
authButtons.style.display = 'none';
userProfileBtn.style.display = 'flex';
const initial = (user.name || user.email)[0].toUpperCase();
document.getElementById('userInitial').textContent = initial;
document.getElementById('dropdownUserName').textContent = user.name || 'User';
document.getElementById('dropdownUserEmail').textContent = user.email;
}
// Show login buttons
function showLoginButtons() {
authButtons.style.display = 'flex';
userProfileBtn.style.display = 'none';
}
// Load user profile data
async function loadUserProfile(user) {
// Set basic profile info
document.getElementById('profileUsername').textContent = user.name || user.email;
document.getElementById('profileEmail').textContent = user.email;
// Set avatar
const initial = (user.name || user.email)[0].toUpperCase();
document.getElementById('profileAvatar').textContent = initial;
// Set join date
if (user.created_at) {
const joinDate = new Date(user.created_at);
document.getElementById('joinDate').textContent = joinDate.toLocaleDateString();
}
// Load library stats
try {
const libraryResponse = await fetch('/api/library', {
method: 'GET',
credentials: 'include'
});
if (libraryResponse.ok) {
const library = await libraryResponse.json();
updateLibraryStats(library);
}
} catch (error) {
console.error('Failed to load library:', error);
}
}
// Update library statistics
function updateLibraryStats(library) {
// Count manga in different statuses
const libraryArray = Array.isArray(library) ? library : (library.library || []);
document.getElementById('statsInLibrary').textContent = libraryArray.length;
document.getElementById('totalManga').textContent = libraryArray.length;
// Calculate other stats
let totalChapters = 0;
let completedCount = 0;
let readingCount = 0;
libraryArray.forEach(manga => {
if (manga.chapters) {
totalChapters += manga.chapters.length;
}
// These would be set by user status tracking
});
document.getElementById('totalChapters').textContent = totalChapters;
document.getElementById('statsCompleted').textContent = completedCount;
document.getElementById('statsReading').textContent = readingCount;
}
// Show empty profile
function showEmptyProfile() {
document.getElementById('profileUsername').textContent = 'Guest User';
document.getElementById('profileEmail').textContent = 'Not signed in';
}
// Share profile
function shareProfile() {
const profileUrl = window.location.href;
const username = document.getElementById('profileUsername').textContent;
if (navigator.share) {
navigator.share({
title: `${username}'s Profile`,
text: `Check out my manga profile on Akiyama Manga!`,
url: profileUrl
}).catch(err => console.error('Share failed:', err));
} else {
// Fallback: copy to clipboard
navigator.clipboard.writeText(profileUrl).then(() => {
alert('Profile link copied to clipboard!');
});
}
}
// Dropdown menu toggle
userProfileBtn.addEventListener('click', (e) => {
e.stopPropagation();
dropdownMenu.classList.toggle('active');
});
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!e.target.closest('.auth-container')) {
dropdownMenu.classList.remove('active');
}
});
// Logout handler
logoutBtn.addEventListener('click', async (e) => {
e.preventDefault();
try {
const response = await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include'
});
if (response.ok) {
showLoginButtons();
showEmptyProfile();
window.location.href = '/';
}
} catch (error) {
console.error('Logout error:', error);
}
});
// Initialize on page load
document.addEventListener('DOMContentLoaded', checkAuth);
</script>
</body>
</html>