276 lines
11 KiB
HTML
276 lines
11 KiB
HTML
<!-- OnlyPrompt - Settings page:
|
|
- User preferences with tabs for profile, security, and notifications -->
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>OnlyPrompt - Settings</title>
|
|
<link rel="stylesheet" href="../css/variables.css">
|
|
<link rel="stylesheet" href="../css/base.css">
|
|
<link rel="stylesheet" href="../css/sidebar.css">
|
|
<link rel="stylesheet" href="../css/login.css">
|
|
<link rel="stylesheet" href="../css/topbar.css">
|
|
<link rel="stylesheet" href="../css/settings.css">
|
|
<script src="../js/profile-shared.js"></script>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
</head>
|
|
<body>
|
|
<a class="skip-link" href="#main-content">Skip to main content</a>
|
|
<div class="layout">
|
|
|
|
<div id="sidebar-container"></div>
|
|
|
|
<div class="page-body">
|
|
|
|
<div id="topbar-container"></div>
|
|
|
|
<main class="settings-main" id="main-content" tabindex="-1">
|
|
<div class="settings-container">
|
|
<div class="settings-header">
|
|
<h1>Settings</h1>
|
|
<p>Manage your account preferences and profile.</p>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div class="settings-tabs" role="tablist" aria-label="Settings sections">
|
|
<button type="button" class="tab-btn active" data-tab="profile" id="profileTabButton" role="tab" aria-selected="true" aria-controls="profileTab">Profile</button>
|
|
<button type="button" class="tab-btn" data-tab="security" id="securityTabButton" role="tab" aria-selected="false" aria-controls="securityTab">Security</button>
|
|
<button type="button" class="tab-btn" data-tab="notifications" id="notificationsTabButton" role="tab" aria-selected="false" aria-controls="notificationsTab">Notifications</button>
|
|
</div>
|
|
|
|
<!-- Tab Content: Profile -->
|
|
<div id="profileTab" class="tab-content active" role="tabpanel" aria-labelledby="profileTabButton">
|
|
<form class="settings-form" id="profileSettingsForm">
|
|
<div class="form-group">
|
|
<label for="avatar">Profile Picture</label>
|
|
<div class="avatar-upload">
|
|
<img src="../images/content/cat.png" alt="Profile picture preview" class="settings-avatar" id="avatarPreview">
|
|
<input type="file" id="avatarUpload" accept="image/png, image/jpeg">
|
|
<button type="button" class="upload-btn" aria-controls="avatarUpload">Upload new</button>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="displayName">Display Name</label>
|
|
<input type="text" id="displayName" placeholder="Display name">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="username">Username</label>
|
|
<input type="text" id="username" placeholder="username" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="bio">Bio</label>
|
|
<textarea id="bio" rows="3" placeholder="Tell us about yourself..."></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="email">Email Address</label>
|
|
<input type="email" id="email" placeholder="your@email.com" readonly>
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="submit" class="save-btn">Save Changes</button>
|
|
<p id="profileSaveStatus" role="status" aria-live="polite"></p>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Tab Content: Security -->
|
|
<div id="securityTab" class="tab-content" role="tabpanel" aria-labelledby="securityTabButton" hidden>
|
|
<form class="settings-form">
|
|
<div class="form-group">
|
|
<label for="currentPw">Current Password</label>
|
|
<input type="password" id="currentPw" placeholder="Enter current password">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="newPw">New Password</label>
|
|
<input type="password" id="newPw" placeholder="Enter new password">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="confirmPw">Confirm New Password</label>
|
|
<input type="password" id="confirmPw" placeholder="Confirm new password">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="twoFactor"> Enable Two-Factor Authentication
|
|
</label>
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="submit" class="save-btn">Update Password</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Tab Content: Notifications (erweitert) -->
|
|
<div id="notificationsTab" class="tab-content" role="tabpanel" aria-labelledby="notificationsTabButton" hidden>
|
|
<form class="settings-form">
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="notifLikes"> When someone likes my prompt
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="notifSaves"> When someone saves my prompt
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="notifMessages"> New message received
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="notifFollowers"> New follower
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="notifPurchases"> Prompt purchase (paid prompts)
|
|
</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="notifComments"> Comment on my prompt
|
|
</label>
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="submit" class="save-btn">Save Preferences</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Tab switching
|
|
const tabBtns = document.querySelectorAll('.tab-btn');
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
tabBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const tabId = btn.getAttribute('data-tab');
|
|
tabBtns.forEach(b => {
|
|
b.classList.remove('active');
|
|
b.setAttribute('aria-selected', 'false');
|
|
});
|
|
btn.classList.add('active');
|
|
btn.setAttribute('aria-selected', 'true');
|
|
tabContents.forEach(content => {
|
|
content.classList.remove('active');
|
|
content.hidden = true;
|
|
});
|
|
const selectedTab = document.getElementById(`${tabId}Tab`);
|
|
selectedTab.classList.add('active');
|
|
selectedTab.hidden = false;
|
|
});
|
|
});
|
|
|
|
let currentAvatarUrl = '';
|
|
let currentSlug = '';
|
|
|
|
function setProfileForm(profile) {
|
|
document.getElementById('displayName').value = profile.displayName || '';
|
|
document.getElementById('username').value = profile.user?.userName || '';
|
|
document.getElementById('email').value = profile.user?.email || '';
|
|
document.getElementById('bio').value = profile.bio || '';
|
|
currentAvatarUrl = profile.avatarUrl || '../images/content/cat.png';
|
|
currentSlug = profile.slug || profile.user?.userName || '';
|
|
avatarPreview.src = currentAvatarUrl;
|
|
}
|
|
|
|
async function loadProfileSettings() {
|
|
try {
|
|
const profile = await window.loadCurrentProfile();
|
|
setProfileForm(profile);
|
|
} catch (error) {
|
|
document.getElementById('profileSaveStatus').textContent = 'Profile could not be loaded.';
|
|
}
|
|
}
|
|
|
|
// Avatar preview and save as data URL
|
|
const avatarUpload = document.getElementById('avatarUpload');
|
|
const avatarPreview = document.getElementById('avatarPreview');
|
|
if (avatarUpload) {
|
|
avatarUpload.addEventListener('change', function(e) {
|
|
const file = e.target.files[0];
|
|
if (file && (file.type === 'image/png' || file.type === 'image/jpeg')) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(ev) {
|
|
currentAvatarUrl = ev.target.result;
|
|
avatarPreview.src = currentAvatarUrl;
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
});
|
|
}
|
|
|
|
document.getElementById('profileSettingsForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const status = document.getElementById('profileSaveStatus');
|
|
status.textContent = 'Saving...';
|
|
|
|
try {
|
|
const userName = document.getElementById('username').value.trim();
|
|
const response = await fetch('/api/v1/profiles', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'same-origin',
|
|
body: JSON.stringify({
|
|
displayName: document.getElementById('displayName').value.trim(),
|
|
userName,
|
|
slug: userName || currentSlug,
|
|
bio: document.getElementById('bio').value.trim(),
|
|
avatarUrl: currentAvatarUrl,
|
|
specialities: null,
|
|
isPublic: true
|
|
})
|
|
});
|
|
|
|
if (response.status === 401) {
|
|
location.href = '/login';
|
|
return;
|
|
}
|
|
if (!response.ok) throw new Error('Profile could not be saved.');
|
|
|
|
const savedProfile = await window.loadCurrentProfile(true);
|
|
window.setCurrentProfile(savedProfile);
|
|
setProfileForm(savedProfile);
|
|
status.textContent = 'Saved.';
|
|
} catch (error) {
|
|
status.textContent = error.message;
|
|
}
|
|
});
|
|
|
|
// Demo form submits for forms that are not connected yet
|
|
document.querySelectorAll('.settings-form:not(#profileSettingsForm)').forEach(form => {
|
|
form.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
alert('Settings saved (demo)');
|
|
});
|
|
});
|
|
|
|
// Fetch sidebar and topbar
|
|
fetch('/sidebar.html')
|
|
.then(r => r.text())
|
|
.then(data => {
|
|
document.getElementById('sidebar-container').innerHTML = data;
|
|
document.querySelectorAll('#sidebar-container .sidebar a').forEach(link => {
|
|
link.classList.remove('active');
|
|
link.removeAttribute('aria-current');
|
|
});
|
|
const settingsLink = document.querySelector('#sidebar-container a[href="settings.html"]');
|
|
if (settingsLink) {
|
|
settingsLink.classList.add('active');
|
|
settingsLink.setAttribute('aria-current', 'page');
|
|
}
|
|
});
|
|
fetch('/topbar.html')
|
|
.then(r => r.text())
|
|
.then(data => document.getElementById('topbar-container').innerHTML = data);
|
|
|
|
loadProfileSettings();
|
|
</script>
|
|
</body>
|
|
</html>
|