430 lines
15 KiB
JavaScript
430 lines
15 KiB
JavaScript
import { t, i18n } from '../../i18n.js'
|
|
import { auth } from '../../services/auth.js'
|
|
import { favoritesService } from '../../services/favorites.js'
|
|
|
|
class PageSettings extends HTMLElement {
|
|
constructor() {
|
|
super()
|
|
this.isLoggedIn = false
|
|
this.user = null
|
|
}
|
|
|
|
async connectedCallback() {
|
|
this.isLoggedIn = auth.isLoggedIn()
|
|
|
|
if (!this.isLoggedIn) {
|
|
window.location.hash = '#/'
|
|
return
|
|
}
|
|
|
|
this.user = await auth.getUser()
|
|
this.render()
|
|
this.setupEventListeners()
|
|
|
|
this.unsubscribe = i18n.subscribe(() => {
|
|
this.render()
|
|
this.setupEventListeners()
|
|
})
|
|
|
|
this.authUnsubscribe = auth.subscribe(() => {
|
|
this.isLoggedIn = auth.isLoggedIn()
|
|
|
|
if (!this.isLoggedIn) {
|
|
window.location.hash = '#/'
|
|
}
|
|
})
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
if (this.unsubscribe) this.unsubscribe()
|
|
if (this.authUnsubscribe) this.authUnsubscribe()
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Theme toggle
|
|
this.querySelectorAll('input[name="theme"]').forEach(input => {
|
|
input.addEventListener('change', (e) => {
|
|
this.setTheme(e.target.value)
|
|
})
|
|
})
|
|
|
|
// Language select
|
|
this.querySelector('#lang-select')?.addEventListener('change', async (e) => {
|
|
i18n.setLocale(e.target.value)
|
|
// Save to user profile if logged in
|
|
if (this.isLoggedIn) {
|
|
await auth.updatePreferences({ preferred_locale: e.target.value })
|
|
}
|
|
})
|
|
|
|
// Currency select
|
|
this.querySelector('#currency-select')?.addEventListener('change', async (e) => {
|
|
await this.setCurrency(e.target.value)
|
|
this.showToast(t('settings.currencyChanged'))
|
|
})
|
|
|
|
// Clear favorites
|
|
this.querySelector('#clear-favorites')?.addEventListener('click', async () => {
|
|
if (confirm(t('settings.confirmClearFavorites'))) {
|
|
const ids = favoritesService.getAll()
|
|
for (const id of ids) {
|
|
await favoritesService.toggle(id)
|
|
}
|
|
this.showToast(t('settings.favoritesCleared'))
|
|
}
|
|
})
|
|
|
|
// Clear search history
|
|
this.querySelector('#clear-search')?.addEventListener('click', () => {
|
|
if (confirm(t('settings.confirmClearSearch'))) {
|
|
localStorage.removeItem('searchFilters')
|
|
this.showToast(t('settings.searchCleared'))
|
|
}
|
|
})
|
|
|
|
// Logout
|
|
this.querySelector('#logout-btn')?.addEventListener('click', () => {
|
|
auth.logout()
|
|
this.showToast(t('settings.loggedOut'))
|
|
})
|
|
|
|
// Login
|
|
this.querySelector('#login-btn')?.addEventListener('click', () => {
|
|
document.querySelector('auth-modal')?.show()
|
|
})
|
|
}
|
|
|
|
setTheme(theme) {
|
|
if (theme === 'system') {
|
|
localStorage.removeItem('theme')
|
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
document.documentElement.dataset.theme = prefersDark ? 'dark' : 'light'
|
|
} else {
|
|
localStorage.setItem('theme', theme)
|
|
document.documentElement.dataset.theme = theme
|
|
}
|
|
}
|
|
|
|
getCurrentTheme() {
|
|
return localStorage.getItem('theme') || 'system'
|
|
}
|
|
|
|
getCurrentCurrency() {
|
|
return localStorage.getItem('dgray_currency') || 'USD'
|
|
}
|
|
|
|
async setCurrency(currency) {
|
|
localStorage.setItem('dgray_currency', currency)
|
|
window.dispatchEvent(new CustomEvent('currency-changed', { detail: { currency } }))
|
|
|
|
// Save to user profile if logged in
|
|
if (auth.isLoggedIn()) {
|
|
await auth.updatePreferences({ preferred_currency: currency })
|
|
}
|
|
}
|
|
|
|
showToast(message) {
|
|
const existing = document.querySelector('.settings-toast')
|
|
existing?.remove()
|
|
|
|
const toast = document.createElement('div')
|
|
toast.className = 'settings-toast'
|
|
toast.textContent = message
|
|
document.body.appendChild(toast)
|
|
|
|
requestAnimationFrame(() => toast.classList.add('visible'))
|
|
|
|
setTimeout(() => {
|
|
toast.classList.remove('visible')
|
|
setTimeout(() => toast.remove(), 300)
|
|
}, 2000)
|
|
}
|
|
|
|
render() {
|
|
const currentTheme = this.getCurrentTheme()
|
|
const currentLang = i18n.getLocale()
|
|
const user = this.user
|
|
|
|
this.innerHTML = /* html */`
|
|
<div class="settings-page">
|
|
<header class="page-header">
|
|
<h1>${t('settings.title')}</h1>
|
|
</header>
|
|
|
|
<div class="settings-sections">
|
|
<!-- Appearance -->
|
|
<section class="settings-section">
|
|
<h2>${t('settings.appearance')}</h2>
|
|
|
|
<div class="setting-item">
|
|
<label>${t('settings.theme')}</label>
|
|
<div class="theme-options">
|
|
<label class="theme-option">
|
|
<input type="radio" name="theme" value="light" ${currentTheme === 'light' ? 'checked' : ''}>
|
|
<span>☀️ ${t('settings.themeLight')}</span>
|
|
</label>
|
|
<label class="theme-option">
|
|
<input type="radio" name="theme" value="dark" ${currentTheme === 'dark' ? 'checked' : ''}>
|
|
<span>🌙 ${t('settings.themeDark')}</span>
|
|
</label>
|
|
<label class="theme-option">
|
|
<input type="radio" name="theme" value="system" ${currentTheme === 'system' ? 'checked' : ''}>
|
|
<span>💻 ${t('settings.themeSystem')}</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-item">
|
|
<label for="lang-select">${t('settings.language')}</label>
|
|
<select id="lang-select">
|
|
<option value="de" ${currentLang === 'de' ? 'selected' : ''}>Deutsch</option>
|
|
<option value="en" ${currentLang === 'en' ? 'selected' : ''}>English</option>
|
|
<option value="fr" ${currentLang === 'fr' ? 'selected' : ''}>Français</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="setting-item">
|
|
<label for="currency-select">${t('settings.currency')}</label>
|
|
<select id="currency-select">
|
|
<option value="USD" ${this.getCurrentCurrency() === 'USD' ? 'selected' : ''}>USD ($)</option>
|
|
<option value="EUR" ${this.getCurrentCurrency() === 'EUR' ? 'selected' : ''}>EUR (€)</option>
|
|
<option value="CHF" ${this.getCurrentCurrency() === 'CHF' ? 'selected' : ''}>CHF</option>
|
|
</select>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Account -->
|
|
<section class="settings-section">
|
|
<h2>${t('settings.account')}</h2>
|
|
|
|
${this.isLoggedIn ? /* html */`
|
|
<div class="setting-item">
|
|
<label>${t('settings.userId')}</label>
|
|
<code class="user-id">${user?.id?.slice(0, 8)}...</code>
|
|
</div>
|
|
<div class="setting-item">
|
|
<button id="logout-btn" class="btn btn-outline btn-danger">
|
|
${t('settings.logout')}
|
|
</button>
|
|
</div>
|
|
` : /* html */`
|
|
<div class="setting-item">
|
|
<p class="setting-hint">${t('settings.notLoggedIn')}</p>
|
|
<button id="login-btn" class="btn btn-primary">
|
|
${t('settings.login')}
|
|
</button>
|
|
</div>
|
|
`}
|
|
</section>
|
|
|
|
<!-- Data -->
|
|
<section class="settings-section">
|
|
<h2>${t('settings.data')}</h2>
|
|
|
|
<div class="setting-item">
|
|
<div>
|
|
<label>${t('settings.favorites')}</label>
|
|
<p class="setting-hint">${t('settings.favoritesHint')}</p>
|
|
</div>
|
|
<button id="clear-favorites" class="btn btn-outline btn-sm">
|
|
${t('settings.clear')}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="setting-item">
|
|
<div>
|
|
<label>${t('settings.searchHistory')}</label>
|
|
<p class="setting-hint">${t('settings.searchHistoryHint')}</p>
|
|
</div>
|
|
<button id="clear-search" class="btn btn-outline btn-sm">
|
|
${t('settings.clear')}
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- About -->
|
|
<section class="settings-section">
|
|
<h2>${t('settings.about')}</h2>
|
|
<div class="about-links">
|
|
<a href="#/about">${t('footer.about')}</a>
|
|
<a href="#/privacy">${t('footer.privacy')}</a>
|
|
<a href="#/terms">${t('footer.terms')}</a>
|
|
<a href="#/contact">${t('footer.contact')}</a>
|
|
</div>
|
|
<p class="version">dgray.io v1.0.0</p>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
`
|
|
}
|
|
}
|
|
|
|
customElements.define('page-settings', PageSettings)
|
|
|
|
const style = document.createElement('style')
|
|
style.textContent = /* css */`
|
|
page-settings .settings-page {
|
|
padding: var(--space-lg) 0;
|
|
max-width: 600px;
|
|
}
|
|
|
|
page-settings .page-header {
|
|
margin-bottom: var(--space-xl);
|
|
}
|
|
|
|
page-settings .page-header h1 {
|
|
margin: 0;
|
|
}
|
|
|
|
page-settings .settings-sections {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xl);
|
|
}
|
|
|
|
page-settings .settings-section {
|
|
background: var(--color-bg-secondary);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--space-lg);
|
|
}
|
|
|
|
page-settings .settings-section h2 {
|
|
font-size: var(--font-size-lg);
|
|
margin: 0 0 var(--space-lg);
|
|
padding-bottom: var(--space-sm);
|
|
border-bottom: 1px solid var(--color-border);
|
|
}
|
|
|
|
page-settings .setting-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: var(--space-md);
|
|
padding: var(--space-sm) 0;
|
|
}
|
|
|
|
page-settings .setting-item + .setting-item {
|
|
border-top: 1px solid var(--color-border);
|
|
margin-top: var(--space-sm);
|
|
padding-top: var(--space-md);
|
|
}
|
|
|
|
page-settings .setting-item > label {
|
|
font-weight: var(--font-weight-medium);
|
|
}
|
|
|
|
page-settings .setting-hint {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-muted);
|
|
margin: var(--space-xs) 0 0;
|
|
}
|
|
|
|
page-settings .theme-options {
|
|
display: flex;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
page-settings .theme-option {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
padding: var(--space-xs) var(--space-sm);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
font-size: var(--font-size-sm);
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
page-settings .theme-option:has(input:checked) {
|
|
border-color: var(--color-primary);
|
|
background: var(--color-primary-light);
|
|
}
|
|
|
|
page-settings .theme-option input {
|
|
display: none;
|
|
}
|
|
|
|
page-settings select {
|
|
padding: var(--space-xs) var(--space-sm);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--color-bg);
|
|
color: var(--color-text);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
page-settings .user-id {
|
|
padding: var(--space-xs) var(--space-sm);
|
|
background: var(--color-bg-tertiary);
|
|
border-radius: var(--radius-sm);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
page-settings .btn-danger {
|
|
color: var(--color-error, #dc2626);
|
|
border-color: var(--color-error, #dc2626);
|
|
}
|
|
|
|
page-settings .btn-danger:hover {
|
|
background: var(--color-error, #dc2626);
|
|
color: white;
|
|
}
|
|
|
|
page-settings .about-links {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-md);
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
page-settings .about-links a {
|
|
color: var(--color-text-secondary);
|
|
text-decoration: none;
|
|
}
|
|
|
|
page-settings .about-links a:hover {
|
|
color: var(--color-primary);
|
|
}
|
|
|
|
page-settings .version {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-muted);
|
|
margin: 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
page-settings .setting-item {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
page-settings .theme-options {
|
|
flex-wrap: wrap;
|
|
}
|
|
}
|
|
|
|
/* Toast */
|
|
.settings-toast {
|
|
position: fixed;
|
|
bottom: var(--space-lg);
|
|
left: 50%;
|
|
transform: translateX(-50%) translateY(100px);
|
|
padding: var(--space-sm) var(--space-lg);
|
|
background: var(--color-bg-secondary);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-md);
|
|
box-shadow: var(--shadow-lg);
|
|
opacity: 0;
|
|
transition: all 0.3s ease;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.settings-toast.visible {
|
|
transform: translateX(-50%) translateY(0);
|
|
opacity: 1;
|
|
}
|
|
`
|
|
document.head.appendChild(style)
|