Files
Personal-Finance-Management/app/templates/schwab/index.html
T

158 lines
7.9 KiB
HTML

{% extends "base.html" %}
{% block title %}Schwab Connection{% endblock %}
{% block page_title %}Schwab Bank Sync{% endblock %}
{% block topbar_actions %}
<a href="{{ url_for('settings.index') }}" class="btn btn-sm btn-outline-secondary me-1" style="font-size:12px;"><i class="bi bi-arrow-left me-1"></i>Settings</a>
{% if connection %}
<form method="POST" action="{{ url_for('schwab.disconnect') }}"
onsubmit="return confirm('Disconnect Schwab? Imported transactions are kept.')" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger" style="font-size:12px;">Disconnect</button>
</form>
{% endif %}
{% endblock %}
{% block content %}
{% if not schwab_configured %}
<div class="pcard mb-4" style="border-left:4px solid #f59e0b;">
<div class="d-flex align-items-start gap-3">
<i class="bi bi-exclamation-triangle-fill text-warning mt-1" style="font-size:1.4rem;flex-shrink:0;"></i>
<div>
<div style="font-weight:600;font-size:14px;margin-bottom:6px;">Schwab credentials not configured</div>
<p style="font-size:13px;color:var(--muted);margin-bottom:10px;">
Register your app at <strong>developer.schwab.com</strong>, then add these to your <code>.env</code>:
</p>
<pre style="font-size:12px;background:#f8fafc;padding:12px;border-radius:8px;margin:0;overflow-x:auto;">SCHWAB_CLIENT_ID=your-client-id
SCHWAB_CLIENT_SECRET=your-client-secret
SCHWAB_REDIRECT_URI=https://pfm.ngodanguyen.tech/schwab/callback</pre>
<p style="font-size:12px;color:var(--muted);margin-top:8px;">
Also register <code>https://pfm.ngodanguyen.tech/schwab/callback</code> as the redirect URI in the Schwab developer portal, then restart the app.
</p>
</div>
</div>
</div>
{% endif %}
{% if connection %}
<!-- Connected state -->
<div class="pcard mb-4" style="border-left:4px solid #10b981;">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
<div class="d-flex align-items-center gap-3">
<div style="width:42px;height:42px;border-radius:10px;background:#d1fae5;color:#065f46;display:flex;align-items:center;justify-content:center;font-size:20px;">
<i class="bi bi-bank2"></i>
</div>
<div>
<div style="font-weight:600;font-size:15px;">Charles Schwab</div>
<div style="font-size:12px;color:var(--muted);">
Connected {{ connection.created_at.strftime('%b %d, %Y') }} ·
Last synced: {{ connection.last_synced_at.strftime('%b %d, %H:%M') if connection.last_synced_at else 'Never' }}
{% if connection.token_is_expired %}
· <span style="color:#f59e0b;"><i class="bi bi-exclamation-circle me-1"></i>Token expired — sync will auto-refresh</span>
{% endif %}
</div>
</div>
</div>
<a href="{{ url_for('schwab.map_accounts') }}" class="btn btn-sm btn-outline-secondary" style="font-size:12px;">
<i class="bi bi-diagram-2 me-1"></i>Manage Mapping
</a>
</div>
</div>
{% if accounts %}
<div class="pcard p-0">
<div class="d-flex justify-content-between align-items-center px-4 py-3" style="border-bottom:1px solid var(--border);">
<span class="pcard-title mb-0">Accounts</span>
</div>
{% for sa in accounts %}
<div class="d-flex justify-content-between align-items-center px-4 py-3" style="{% if not loop.last %}border-bottom:1px solid var(--border);{% endif %}">
<div class="d-flex align-items-center gap-2" style="min-width:0;">
<div style="width:32px;height:32px;border-radius:8px;background:#dbeafe;color:#1e40af;display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0;">
<i class="bi bi-bank"></i>
</div>
<div style="min-width:0;">
<div class="d-flex align-items-center gap-2 flex-wrap">
<span style="font-size:13px;font-weight:500;">{{ sa.account_name }}</span>
{% if sa.last_sync_date %}
<span style="font-size:10px;color:var(--muted);">synced {{ sa.last_sync_date.strftime('%b %d') }}</span>
{% endif %}
</div>
<div style="font-size:11px;color:var(--muted);">
{{ sa.account_number_display }} · {{ sa.account_type }}
{% if sa.pfm_account %}
· <span style="color:var(--muted);">→ {{ sa.pfm_account.name }}</span>
{% else %}
· <span style="color:#f59e0b;">Not mapped</span>
{% endif %}
</div>
</div>
</div>
<div class="d-flex align-items-center gap-2 flex-shrink-0 ms-2">
{% if sa.pfm_account %}
<!-- Sync balance + investment positions -->
<form method="POST" action="{{ url_for('schwab.sync_snapshot', schwab_account_id=sa.id) }}"
style="display:inline;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-success" style="font-size:11px;"
title="Pull live balance and investment positions from Schwab">
<i class="bi bi-arrow-clockwise me-1"></i>Balance &amp; Positions
</button>
</form>
<!-- Sync transactions -->
<a href="{{ url_for('schwab.sync_preview_view', schwab_account_id=sa.id) }}"
class="btn btn-sm btn-outline-primary" style="font-size:11px;"
title="Import new transactions">
<i class="bi bi-cloud-download me-1"></i>Transactions
</a>
<!-- Reset sync cursor -->
<form method="POST" action="{{ url_for('schwab.full_resync', schwab_account_id=sa.id) }}"
style="display:inline;"
onsubmit="return confirm('Reset sync cursor for {{ sa.account_name }}?\nNext sync re-fetches 90 days. Duplicates are skipped.')">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-warning" style="font-size:11px;"
title="Re-fetch full 90-day history on next sync">
<i class="bi bi-arrow-counterclockwise"></i>
</button>
</form>
{% else %}
<a href="{{ url_for('schwab.map_accounts') }}"
class="btn btn-sm btn-outline-warning" style="font-size:11px;">Map Account</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="pcard text-center py-5">
<i class="bi bi-diagram-2 text-muted" style="font-size:3rem;"></i>
<h5 class="mt-3 mb-1">No accounts mapped</h5>
<p class="text-muted small mb-3">Link each Schwab account to a PFM account to start syncing.</p>
<a href="{{ url_for('schwab.map_accounts') }}" class="btn btn-primary btn-sm">Map Accounts</a>
</div>
{% endif %}
{% else %}
<!-- Not connected -->
<div class="pcard text-center py-5 col-12 col-lg-6 mx-auto">
<i class="bi bi-bank2 text-muted" style="font-size:3rem;"></i>
<h5 class="mt-3 mb-1">Connect Charles Schwab</h5>
<p class="text-muted small mb-4">
Sync checking, savings, and brokerage transactions directly from Schwab.
</p>
{% if schwab_configured %}
<a href="{{ url_for('schwab.connect') }}" class="btn btn-primary">
<i class="bi bi-link-45deg me-1"></i>Connect Schwab Account
</a>
{% else %}
<button class="btn btn-primary" disabled>
<i class="bi bi-link-45deg me-1"></i>Connect Schwab Account
</button>
<p class="text-muted small mt-2">Configure credentials first (see above)</p>
{% endif %}
</div>
{% endif %}
{% endblock %}