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

162 lines
7.5 KiB
HTML

{% extends "base.html" %}
{% block title %}Bank Connections{% endblock %}
{% block page_title %}Bank Connections{% 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>
<button id="tellerConnectBtn" class="btn btn-sm btn-primary" style="font-size:12px;"
{% if not teller_app_id %}title="TELLER_APP_ID not set in .env" style="opacity:.6;"{% endif %}>
<i class="bi bi-bank me-1"></i>Connect a Bank
</button>
{% endblock %}
{% block content %}
{% if enrollments %}
<div class="row g-3 mb-4">
{% for enrollment in enrollments %}
{% set accounts = enrollment.accounts.filter_by(is_active=True).all() %}
<div class="col-12 col-lg-8">
<div class="pcard">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<div style="font-size:15px;font-weight:600;">{{ enrollment.institution_name or 'Unknown Bank' }}</div>
<div style="font-size:12px;color:var(--muted);">
Connected {{ enrollment.created_at.strftime('%b %d, %Y') }}
{% if enrollment.last_synced_at %}
· Last synced {{ enrollment.last_synced_at.strftime('%b %d, %H:%M') }}
{% endif %}
</div>
</div>
<form method="POST" action="{{ url_for('teller.disconnect', enrollment_db_id=enrollment.id) }}"
onsubmit="return confirm('Disconnect from {{ enrollment.institution_name }}?\nExisting transactions are kept.')">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn btn-sm btn-outline-danger" style="font-size:11px;">
<i class="bi bi-x-circle me-1"></i>Disconnect
</button>
</form>
</div>
{% for ta in accounts %}
<div class="d-flex justify-content-between align-items-center py-2" style="border-top:1px solid var(--border);">
<div class="d-flex align-items-center gap-2">
<div style="width:30px;height:30px;border-radius:8px;background:#dbeafe;color:#1e40af;display:flex;align-items:center;justify-content:center;font-size:13px;flex-shrink:0;">
<i class="bi {% if ta.account_subtype == 'credit_card' %}bi-credit-card{% elif ta.account_subtype == 'savings' %}bi-piggy-bank{% else %}bi-bank{% endif %}"></i>
</div>
<div>
<div style="font-size:13px;font-weight:500;">{{ ta.account_name }}</div>
<div style="font-size:11px;color:var(--muted);">
{{ ta.account_subtype | replace('_',' ') | title }}
{% if ta.pfm_account %}
· linked to <span style="color:var(--text);font-weight:500;">{{ ta.pfm_account.name }}</span>
{% else %}
· <span style="color:#f59e0b;font-weight:500;">Not mapped</span>
{% endif %}
</div>
</div>
</div>
{% if not ta.pfm_account %}
<a href="{{ url_for('teller.map_accounts', enrollment_id=enrollment.enrollment_id) }}"
class="btn btn-sm btn-outline-warning" style="font-size:11px;">Map Account</a>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<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">No banks connected</h5>
<p class="text-muted small mb-3">
Connect your US bank accounts to automatically sync transactions and balances.
Once connected, use the <strong>Accounts</strong> page to sync transactions and refresh balances.
</p>
<button id="tellerConnectBtn2" class="btn btn-primary">
<i class="bi bi-bank me-1"></i>Connect a Bank
</button>
</div>
{% endif %}
<!-- Setup status -->
<div class="pcard mt-2" style="background:#f8fafc;">
<div class="pcard-title mb-2">Setup Status</div>
<div style="font-size:13px;line-height:1.9;">
<div>
<i class="bi bi-{% if teller_app_id %}check-circle-fill text-success{% else %}x-circle-fill text-danger{% endif %} me-2"></i>
Teller App ID: {% if teller_app_id %}<code>{{ teller_app_id[:8] }}…</code>{% else %}<span class="text-danger">Not set — add TELLER_APP_ID to .env</span>{% endif %}
</div>
<div>
<i class="bi bi-{% if teller_cert_path %}check-circle-fill text-success{% else %}x-circle-fill text-danger{% endif %} me-2"></i>
Certificate: <code>{{ teller_cert_path or 'Not set' }}</code>
</div>
<div>
<i class="bi bi-{% if teller_key_path %}check-circle-fill text-success{% else %}x-circle-fill text-danger{% endif %} me-2"></i>
Private Key: <code>{{ teller_key_path or 'Not set' }}</code>
</div>
<div>
<i class="bi bi-info-circle text-muted me-2"></i>
Environment: <strong>{{ teller_env }}</strong>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.teller.io/connect/connect.js"></script>
<script>
const CSRF = '{{ csrf_token() }}';
function initTellerConnect(btn) {
if (!btn) return;
btn.addEventListener('click', function() {
const appId = '{{ teller_app_id }}';
if (!appId) {
alert('TELLER_APP_ID is not set in .env\nAdd it and restart the app.');
return;
}
const teller = TellerConnect.setup({
applicationId: '{{ teller_app_id }}',
environment: '{{ teller_env }}',
products: ['transactions'],
onSuccess: function(enrollment) {
btn.disabled = true;
btn.innerHTML = '<i class="bi bi-hourglass-split me-1"></i>Connecting…';
fetch('/teller/callback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': CSRF,
},
body: JSON.stringify(enrollment),
})
.then(r => r.json())
.then(data => {
if (data.redirect) {
window.location.href = data.redirect;
} else {
alert('Connection error: ' + (data.error || 'Unknown'));
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-bank me-1"></i>Connect a Bank';
}
})
.catch(() => {
alert('Network error. Please try again.');
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-bank me-1"></i>Connect a Bank';
});
},
onExit: function() {},
onFailure: function(error) {
alert('Connection failed: ' + error.message);
},
});
teller.open();
});
}
initTellerConnect(document.getElementById('tellerConnectBtn'));
initTellerConnect(document.getElementById('tellerConnectBtn2'));
</script>
{% endblock %}