749 lines
22 KiB
HTML
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>
|