Files
TimeTracker/app/templates/components/support_modal.html
T
Dries Peeters b0dde80ba9 feat(web): high-visibility support modal, prompts, and supporter UX
Add a support modal with usage stats, tier and license links, share control, and offline-safe outbound CTAs. Surface support from the header, sidebar, user menu, dashboard card, and settings "Support & Community" section without hiding entry points when a supporter license is active.

Introduce UsageStatsService and a persisted users.support_stats_reports_generated counter incremented on key report exports and custom report views. Add SupportPromptService for session-scoped soft toasts (after export, dashboard milestones, long session via POST /donate/request-soft-prompt).

Wire consent-aware track_event names support.* and mirror funnel rows in DonationInteraction; fix has_recent_donation_click to treat link_clicked as a recent click. Document events and SUPPORT_* / migration notes in docs.

Tests: tests/test_support_services.py for prompt and usage stats behavior.
2026-04-15 10:55:37 +02:00

57 lines
4.7 KiB
HTML

{# Support & donation modal — strings use Flask-Babel; stats from support_usage_stats_modal #}
{% set s = support_usage_stats_modal or {} %}
<div id="supportModal" class="fixed inset-0 z-[100] hidden" aria-hidden="true" role="dialog" aria-labelledby="supportModalTitle" aria-modal="true">
<div class="absolute inset-0 bg-black/50" data-support-modal-overlay tabindex="-1"></div>
<div class="relative max-w-lg mx-auto mt-16 sm:mt-24 mb-8 px-4 max-h-[calc(100vh-4rem)] overflow-y-auto">
<div class="bg-card-light dark:bg-card-dark text-text-light dark:text-text-dark rounded-xl shadow-xl border border-border-light dark:border-border-dark">
<div class="flex items-start justify-between gap-3 p-5 border-b border-border-light dark:border-border-dark">
<div>
<h2 id="supportModalTitle" class="text-lg font-semibold">{{ _('Support TimeTracker') }}</h2>
<p class="text-sm text-text-muted-light dark:text-text-muted-dark mt-1">
{% if is_license_activated %}
{{ _('Thank you for being a supporter. Sharing the app helps others discover it too.') }}
{% else %}
{{ _('TimeTracker is free and built independently. If it helps you, consider supporting its development.') }}
{% endif %}
</p>
</div>
<button type="button" class="p-2 rounded-lg hover:bg-background-light dark:hover:bg-background-dark text-text-muted-light" data-support-modal-close aria-label="{{ _('Close') }}">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</div>
<div class="p-5 space-y-4">
<div class="grid grid-cols-3 gap-2 text-center text-sm">
<div class="rounded-lg bg-background-light dark:bg-background-dark p-3">
<div class="text-xs text-text-muted-light dark:text-text-muted-dark">{{ _('Hours tracked') }}</div>
<div class="font-semibold tabular-nums" id="supportStatHours">{{ '%.1f'|format(s.get('total_hours', 0)|float) }}</div>
</div>
<div class="rounded-lg bg-background-light dark:bg-background-dark p-3">
<div class="text-xs text-text-muted-light dark:text-text-muted-dark">{{ _('Entries') }}</div>
<div class="font-semibold tabular-nums" id="supportStatEntries">{{ s.get('time_entries_count', 0)|int }}</div>
</div>
<div class="rounded-lg bg-background-light dark:bg-background-dark p-3">
<div class="text-xs text-text-muted-light dark:text-text-muted-dark">{{ _('Reports') }}</div>
<div class="font-semibold tabular-nums" id="supportStatReports">{{ s.get('reports_generated_count', 0)|int }}</div>
</div>
</div>
<p id="supportSocialLine" class="text-xs text-text-muted-light dark:text-text-muted-dark text-center">{{ _('Trusted by teams and freelancers who want simple, reliable time tracking.') }}</p>
<p id="supportModalOffline" class="hidden text-sm text-amber-700 dark:text-amber-300"></p>
<div class="flex flex-col sm:flex-row gap-2">
<a href="#" rel="noopener noreferrer" data-support-tier="eur5" class="support-tier-btn btn btn-secondary text-center flex-1">{{ _('Donate') }} (€5)</a>
<a href="#" rel="noopener noreferrer" data-support-tier="eur10" class="support-tier-btn btn btn-secondary text-center flex-1">{{ _('Donate') }} (€10)</a>
<a href="#" rel="noopener noreferrer" data-support-tier="eur25" class="support-tier-btn btn btn-secondary text-center flex-1">{{ _('Donate') }} (€25)</a>
</div>
<div class="flex flex-col sm:flex-row gap-2">
<a href="{{ support_purchase_url }}" target="_blank" rel="noopener noreferrer" data-support-tier="license" class="btn btn-primary text-center flex-1">
{{ _('Buy license (€25)') }} <i class="fas fa-external-link-alt text-xs ml-1" aria-hidden="true"></i>
</a>
<button type="button" id="supportShareBtn" class="btn btn-secondary text-center flex-1">{{ _('Love TimeTracker? Share it') }}</button>
</div>
<p class="text-xs text-text-muted-light dark:text-text-muted-dark">
{{ _('A license is a supporter badge — it does not lock features. You keep full access either way.') }}
</p>
</div>
</div>
</div>
</div>