Files
wled-screen-controller-mixed/server/src/wled_controller/static/js/features/donation.ts
alexei.dolgolyov f3d07fc47f
Some checks failed
Lint & Test / test (push) Has been cancelled
feat: donation banner, About tab, settings UI improvements
- Dismissible donation/open-source banner after 3+ sessions (30-day snooze)
- New About tab in Settings: version, repo link, license info
- Centralize project URLs (REPO_URL, DONATE_URL) in __init__.py, served via /health
- Center settings tab bar, reduce tab padding for 6-tab fit
- External URL save button: icon button instead of full-width text button
- Remove redundant settings footer close button
- Footer "Source Code" link replaced with "About" opening settings
- i18n keys for en/ru/zh
2026-03-27 21:09:34 +03:00

144 lines
4.9 KiB
TypeScript

/**
* Donation banner — shows a dismissible open-source/donation notice
* after the user has had a few sessions with the app.
*/
import { t } from '../core/i18n.ts';
import { ICON_HEART, ICON_EXTERNAL_LINK, ICON_X, ICON_GITHUB } from '../core/icons.ts';
// ─── Config ─────────────────────────────────────────────────
/** URLs are set from the server /health response via setProjectUrls(). */
let _donateUrl = '';
let _repoUrl = '';
/** Minimum number of app opens before showing the banner. */
const MIN_SESSIONS = 3;
/** "Remind me later" snooze duration (30 days). */
const SNOOZE_MS = 30 * 24 * 60 * 60 * 1000;
// ─── localStorage keys ──────────────────────────────────────
const LS_DISMISSED = 'donation-banner-dismissed';
const LS_SNOOZE = 'donation-banner-snooze-until';
const LS_SESSIONS = 'donation-banner-sessions';
// ─── Public API ─────────────────────────────────────────────
/** Set project URLs from server health response. Call before initDonationBanner. */
export function setProjectUrls(repoUrl: string, donateUrl: string): void {
_repoUrl = repoUrl || '';
_donateUrl = donateUrl || '';
}
/** Call once on app init. Increments session count and shows banner if conditions met. */
export function initDonationBanner(): void {
// No URLs configured — nothing to show
if (!_donateUrl && !_repoUrl) return;
const sessions = (parseInt(localStorage.getItem(LS_SESSIONS) || '0', 10) || 0) + 1;
localStorage.setItem(LS_SESSIONS, String(sessions));
if (sessions < MIN_SESSIONS) return;
if (localStorage.getItem(LS_DISMISSED) === '1') return;
const snoozeUntil = parseInt(localStorage.getItem(LS_SNOOZE) || '0', 10) || 0;
if (snoozeUntil > Date.now()) return;
_showBanner();
}
/** Dismiss forever. */
export function dismissDonation(): void {
localStorage.setItem(LS_DISMISSED, '1');
_hideBanner();
}
/** Snooze for 30 days. */
export function snoozeDonation(): void {
localStorage.setItem(LS_SNOOZE, String(Date.now() + SNOOZE_MS));
_hideBanner();
}
/** Render the About panel content in settings modal. */
export function renderAboutPanel(): void {
const container = document.getElementById('about-panel-content');
if (!container) return;
const version = document.getElementById('version-number')?.textContent || '';
let links = '';
if (_repoUrl) {
links += `<a href="${_repoUrl}" target="_blank" rel="noopener" class="about-link">
${ICON_GITHUB}
<span>${t('donation.view_source')}</span>
${ICON_EXTERNAL_LINK}
</a>`;
}
if (_donateUrl) {
links += `<a href="${_donateUrl}" target="_blank" rel="noopener" class="about-link about-link-donate">
${ICON_HEART}
<span>${t('donation.about_donate')}</span>
${ICON_EXTERNAL_LINK}
</a>`;
}
container.innerHTML = `
<div class="about-section">
<div class="about-logo">${ICON_HEART}</div>
<h3 class="about-title">${t('donation.about_title')}</h3>
${version ? `<span class="about-version">${version}</span>` : ''}
<p class="about-text">${t('donation.about_opensource')}</p>
${links ? `<div class="about-links">${links}</div>` : ''}
<p class="about-license">${t('donation.about_license')}</p>
</div>
`;
}
// ─── Internal ───────────────────────────────────────────────
function _showBanner(): void {
const banner = document.getElementById('donation-banner');
if (!banner) return;
let actions = '';
if (_donateUrl) {
actions += `<a href="${_donateUrl}" target="_blank" rel="noopener"
class="btn btn-icon donation-banner-action donation-banner-donate"
title="${t('donation.support')}">
${ICON_HEART}
</a>`;
}
if (_repoUrl) {
actions += `<a href="${_repoUrl}" target="_blank" rel="noopener"
class="btn btn-icon donation-banner-action"
title="${t('donation.view_source')}">
${ICON_GITHUB}
</a>`;
}
actions += `<button class="btn btn-icon donation-banner-action"
onclick="snoozeDonation()" title="${t('donation.later')}">
${ICON_X}
</button>`;
banner.innerHTML = `
<span class="donation-banner-text">
${ICON_HEART}
${t('donation.message')}
</span>
${actions}
`;
banner.style.display = 'flex';
}
function _hideBanner(): void {
const banner = document.getElementById('donation-banner');
if (banner) banner.style.display = 'none';
}