- settings.py: JSON-on-volume store with env fallback; secrets never rendered. - /settings page (⚙️ in nav) to set Gmail address + app password + reminder prefs; password fields are write-only (blank keeps existing). - reminders.py reads Gmail creds from Settings instead of a hardcoded password (resolves #2 in code — note the old password is still in git history; rotate it). - mac_notification no longer crashes off-macOS; FLASK_DEBUG gates debug (#7). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
74 lines
2.9 KiB
HTML
74 lines
2.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>🌙 Moon Household Budget</title>
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
</head>
|
|
<body>
|
|
<nav>
|
|
<div class="nav-brand">🌙 Moon Household Budget</div>
|
|
<div class="nav-links">
|
|
<a href="{{ url_for('weekly_overview') }}">Weekly</a>
|
|
<a href="{{ url_for('index') }}">Overview</a>
|
|
<a href="{{ url_for('bill_calendar') }}">Calendar</a>
|
|
<a href="{{ url_for('transactions_view') }}">Transactions</a>
|
|
<a href="{{ url_for('income_view') }}">Income</a>
|
|
<a href="{{ url_for('taxes_view') }}">Taxes</a>
|
|
<a href="{{ url_for('bills_list') }}">Bills</a>
|
|
<a href="{{ url_for('sinking_view') }}">Savings</a>
|
|
<a href="{{ url_for('assets_view') }}">Assets</a>
|
|
<a href="{{ url_for('mortgage_view') }}">Mortgage</a>
|
|
<a href="{{ url_for('debts_view') }}">Debt</a>
|
|
<a href="{{ url_for('net_worth_view') }}">Net Worth</a>
|
|
<a href="{{ url_for('summary') }}">Spending Report</a>
|
|
<a href="{{ url_for('suggestions.suggestions_page') }}" class="nav-suggest" title="Suggestions & feedback" aria-label="Suggestions">💡</a>
|
|
<a href="{{ url_for('settings_view') }}" class="nav-settings" title="Settings" aria-label="Settings">⚙️</a>
|
|
</div>
|
|
</nav>
|
|
<main>
|
|
{% if unreviewed_count > 0 %}
|
|
<div class="alert-banner" style="margin-bottom:1rem;">
|
|
<span>You have <strong>{{ unreviewed_count }}</strong> transaction{{ 's' if unreviewed_count != 1 }} waiting to be categorized.</span>
|
|
<a href="{{ url_for('review') }}" class="btn btn-primary">Review Now</a>
|
|
</div>
|
|
{% endif %}
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Wrap all number budget-inputs with a $ prefix and format on blur
|
|
document.querySelectorAll('input[type="number"].budget-input').forEach(function (input) {
|
|
// Skip if already wrapped or explicitly opted out
|
|
if (input.classList.contains('no-dollar') || input.parentElement.classList.contains('dollar-wrap')) return;
|
|
|
|
// Wrap in dollar-sign container
|
|
const wrap = document.createElement('span');
|
|
wrap.className = 'dollar-wrap';
|
|
wrap.style.width = input.style.width || '100%';
|
|
input.parentNode.insertBefore(wrap, input);
|
|
wrap.appendChild(input);
|
|
|
|
// Format existing value on load
|
|
if (input.value !== '' && !isNaN(parseFloat(input.value))) {
|
|
input.value = parseFloat(input.value).toFixed(2);
|
|
}
|
|
|
|
// Format to 2 decimal places on blur
|
|
input.addEventListener('blur', function () {
|
|
if (this.value !== '' && !isNaN(parseFloat(this.value))) {
|
|
this.value = parseFloat(this.value).toFixed(2);
|
|
}
|
|
});
|
|
|
|
// On focus, select all for easy overtyping
|
|
input.addEventListener('focus', function () {
|
|
this.select();
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|