Files
tink-demo/src/templates/step.html
Henrik Jess Nielsen b14b88dadd
All checks were successful
Build and Deploy / deploy (push) Successful in 27s
fix: server-side token store — løser cookie overflow bug (>4KB)
JWTs (app_token + user_token) gemmes nu i _token_store dict server-side.
Cookien holder kun sid UUID (~36 chars) — aldrig under 4KB grænsen.

- Tilføjet _token_store, _get_sid, _store_token, _load_token helpers
- Step 1-5 + /callback migreret til _store_token/_load_token
- Reset rydder nu token store for den aktuelle session
- Verified: fuldt flow gennemkørt lokalt, Step 4 virker
2026-05-22 23:38:37 +02:00

336 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %} — Step {{ step }}{% endblock %}
{% block stepper %}
<div class="bg-slate-900/60 border-b border-slate-800">
<div class="max-w-6xl mx-auto px-4 py-3">
<div class="flex items-center gap-1 overflow-x-auto">
{% set step_names = ["Auth", "Opret Bruger", "Tilslut Bank", "Konti", "Transaktioner", "Events"] %}
{% for i in range(1, 7) %}
<a href="/demo/step/{{ i }}"
class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm whitespace-nowrap transition
{% if i == step %}bg-violet-600 text-white font-semibold
{% elif i < step %}text-slate-300 hover:text-white
{% else %}text-slate-600 hover:text-slate-400{% endif %}">
<span class="w-5 h-5 rounded-full text-xs font-bold flex items-center justify-center
{% if i < step %}bg-slate-700 text-slate-300
{% elif i == step %}bg-violet-500 text-white
{% else %}bg-slate-800 text-slate-600{% endif %}">{{ i }}</span>
{{ step_names[i-1] }}
</a>
{% if i < 6 %}
<span class="text-slate-700"></span>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="grid grid-cols-1 lg:grid-cols-5 gap-6">
<!-- Left: info panel -->
<div class="lg:col-span-2 space-y-4">
<!-- Step header -->
<div>
<div class="flex items-center gap-2 mb-2">
<span class="w-8 h-8 rounded-full bg-violet-600 text-white text-sm font-bold flex items-center justify-center">{{ step }}</span>
<div>
<h2 class="text-xl font-bold text-white">{{ title }}</h2>
<p class="text-slate-400 text-sm">{{ subtitle }}</p>
</div>
</div>
</div>
<!-- Endpoint badge -->
<div class="bg-slate-900 border border-slate-800 rounded-xl p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-xs text-slate-500 uppercase tracking-wider">Endpoint</span>
<span class="text-xs px-2 py-0.5 rounded-full font-mono font-semibold
{% if 'v2' in api_version %}bg-violet-900/50 text-violet-300 border border-violet-700/40
{% else %}bg-slate-800 text-slate-400{% endif %}">
{{ api_version }}
</span>
</div>
<code class="text-sm text-emerald-400 font-mono break-all">{{ endpoint }}</code>
</div>
<!-- Description -->
<div class="bg-slate-900 border border-slate-800 rounded-xl p-4">
<p class="text-slate-300 text-sm leading-relaxed">{{ description | safe }}</p>
</div>
<!-- curl example -->
<div class="bg-slate-900 border border-slate-800 rounded-xl p-4">
<div class="flex items-center justify-between mb-3">
<span class="text-xs text-slate-500 uppercase tracking-wider">cURL eksempel</span>
<button onclick="copyToClipboard('curl-{{ step }}')"
class="text-xs text-slate-400 hover:text-white transition px-2 py-1 rounded bg-slate-800 hover:bg-slate-700">
Kopier
</button>
</div>
<pre id="curl-{{ step }}" class="text-xs text-amber-300 font-mono whitespace-pre-wrap leading-relaxed">{{ curl_example }}</pre>
</div>
<!-- Navigation -->
<div class="flex gap-3">
{% if prev_step %}
<a href="/demo/step/{{ prev_step }}"
class="flex-1 text-center px-4 py-2.5 border border-slate-700 text-slate-300 hover:text-white hover:border-slate-500 rounded-xl text-sm transition">
← Step {{ prev_step }}
</a>
{% endif %}
{% if next_step %}
<a href="/demo/step/{{ next_step }}"
class="flex-1 text-center px-4 py-2.5 bg-violet-600 hover:bg-violet-500 text-white rounded-xl text-sm font-semibold transition">
Step {{ next_step }} →
</a>
{% endif %}
</div>
</div>
<!-- Right: response panel -->
<div class="lg:col-span-3 space-y-4">
<!-- Tink Link special button (step 3) -->
{% if tink_link_url %}
{% if cb_success %}
<!-- Already connected — show success, hide connection UI -->
<div class="bg-emerald-950/60 border border-emerald-700/50 rounded-xl p-5 flex items-start gap-4">
<div class="w-10 h-10 rounded-full bg-emerald-900/60 flex items-center justify-center flex-shrink-0">
<svg class="w-5 h-5 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
</div>
<div class="flex-1">
<p class="text-emerald-300 font-semibold text-base">Bank forbundet!</p>
<p class="text-emerald-400/70 text-sm mt-0.5">User token gemt i session. Trin 46 er klar.</p>
<a href="/demo/reset"
class="inline-flex items-center gap-1.5 mt-3 text-xs text-slate-500 hover:text-slate-300 transition">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
Start forfra
</a>
</div>
</div>
{% else %}
<!-- Not yet connected — show connection UI -->
<!-- PRIMARY: direct callback flow -->
<div class="bg-slate-900 border border-emerald-700/40 rounded-xl p-5 space-y-4">
<div class="flex items-start gap-3">
<div class="w-8 h-8 rounded-full bg-emerald-900/50 flex items-center justify-center flex-shrink-0">
<svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg>
</div>
<div>
<h3 class="text-white font-semibold mb-1">Tilslut testbank</h3>
<p class="text-slate-400 text-sm">Klik knappen, vælg Demo Bank og log ind — du redirectes automatisk tilbage.</p>
</div>
</div>
<!-- Instructions -->
<div class="bg-slate-800/60 border border-slate-700/50 rounded-lg p-4 text-sm space-y-2">
<p class="text-slate-300 font-semibold flex items-center gap-1.5">
<svg class="w-4 h-4 text-amber-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Trin i Tink Link
</p>
<ol class="text-slate-400 space-y-1.5 list-decimal list-inside leading-relaxed">
<li>Vælg <span class="text-slate-300 font-medium">Tink Demo Bank</span></li>
<li>Vælg <span class="text-slate-300">Open Banking</span><span class="text-slate-300">Password And OTP</span></li>
<li>Hent credentials: <span class="font-mono text-amber-300 text-xs bg-slate-900 px-1.5 py-0.5 rounded">Console → Demo Bank → Transactions → DK</span></li>
<li>Indtast username + password → OTP vises på siden → Continue</li>
<li>Vælg en konto → du redirectes automatisk tilbage her ✓</li>
</ol>
</div>
<!-- Demo Bank credentials hint -->
<div class="bg-slate-800/70 border border-amber-700/30 rounded-lg p-4">
<div class="flex items-center gap-2 mb-3">
<svg class="w-4 h-4 text-amber-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/></svg>
<span class="text-amber-300 text-sm font-semibold">Demo Bank Credentials</span>
</div>
<div class="grid grid-cols-2 gap-2">
<div>
<div class="text-xs text-slate-500 mb-1">Username</div>
<div class="flex items-center gap-2 bg-slate-900 border border-slate-700 rounded px-3 py-2">
<code id="demo-user" class="text-sm font-mono text-emerald-300 flex-1">u04877810</code>
<button onclick="copyText('demo-user', this)"
class="text-xs text-slate-500 hover:text-slate-300 transition flex-shrink-0">Kopier</button>
</div>
</div>
<div>
<div class="text-xs text-slate-500 mb-1">Password</div>
<div class="flex items-center gap-2 bg-slate-900 border border-slate-700 rounded px-3 py-2">
<code id="demo-pass" class="text-sm font-mono text-emerald-300 flex-1">vxw774</code>
<button onclick="copyText('demo-pass', this)"
class="text-xs text-slate-500 hover:text-slate-300 transition flex-shrink-0">Kopier</button>
</div>
</div>
</div>
<p class="text-xs text-slate-500 mt-2">Vælg <span class="text-slate-400">Tink Demo Bank → Open Banking → Password And OTP</span></p>
</div>
<div class="flex items-center gap-3 flex-wrap">
<a href="{{ tink_link_url }}"
class="inline-flex items-center gap-2 px-5 py-2.5 bg-emerald-600 hover:bg-emerald-500 text-white font-semibold rounded-lg transition">
Åbn Tink Link
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"/></svg>
</a>
{% if demo_bank_users %}
<button onclick="document.getElementById('demo-users-modal').classList.remove('hidden')"
class="inline-flex items-center gap-1.5 px-4 py-2.5 border border-violet-700 text-violet-400 hover:text-violet-200 hover:border-violet-500 rounded-lg text-sm transition">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
Vis testbrugere
</button>
{% endif %}
<a href="/demo/reset"
class="inline-flex items-center gap-1.5 px-4 py-2.5 border border-slate-700 text-slate-400 hover:text-white hover:border-slate-500 rounded-lg text-sm transition">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
Start forfra
</a>
</div>
{% if demo_bank_users %}
<!-- Demo Bank users modal -->
<div id="demo-users-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="absolute inset-0 bg-black/70 backdrop-blur-sm" onclick="document.getElementById('demo-users-modal').classList.add('hidden')"></div>
<div class="relative bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl w-full max-w-2xl overflow-hidden">
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-800">
<div>
<h3 class="text-white font-semibold text-base">Demo Bank — Testbrugere</h3>
<p class="text-slate-500 text-xs mt-0.5">Brug disse kredentialer når du logger ind i Tink Demo Bank. OTP er altid <code class="text-violet-300">1234</code> hvor det er påkrævet.</p>
</div>
<button onclick="document.getElementById('demo-users-modal').classList.add('hidden')"
class="text-slate-500 hover:text-white transition p-1.5 rounded-lg hover:bg-slate-800">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
<div class="overflow-auto max-h-96">
<table class="w-full text-sm">
<thead class="bg-slate-800/50 sticky top-0">
<tr class="text-slate-400 text-xs uppercase tracking-wider">
<th class="px-4 py-2.5 text-left">Market</th>
<th class="px-4 py-2.5 text-left">Brugernavn</th>
<th class="px-4 py-2.5 text-left">Password</th>
<th class="px-4 py-2.5 text-left">OTP</th>
<th class="px-4 py-2.5 text-left">Scenarie</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-800">
{% for u in demo_bank_users %}
<tr class="hover:bg-slate-800/40 transition">
<td class="px-4 py-2.5 text-slate-300 font-medium">{{ u.market }}</td>
<td class="px-4 py-2.5">
<div class="flex items-center gap-2">
<code class="font-mono text-emerald-300 text-xs">{{ u.username }}</code>
<button onclick="navigator.clipboard.writeText('{{ u.username }}')" title="Kopier"
class="text-slate-600 hover:text-slate-300 transition">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>
</button>
</div>
</td>
<td class="px-4 py-2.5">
<div class="flex items-center gap-2">
<code class="font-mono text-violet-300 text-xs">{{ u.password }}</code>
<button onclick="navigator.clipboard.writeText('{{ u.password }}')" title="Kopier"
class="text-slate-600 hover:text-slate-300 transition">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>
</button>
</div>
</td>
<td class="px-4 py-2.5 font-mono text-xs text-slate-400">{{ u.otp or "—" }}</td>
<td class="px-4 py-2.5">
<span class="px-2 py-0.5 rounded-full text-xs font-medium {% if 'fejl' in u.scenario.lower() %}bg-red-900/40 text-red-400{% else %}bg-emerald-900/40 text-emerald-400{% endif %}">
{{ u.scenario }}
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="px-6 py-3 border-t border-slate-800 text-xs text-slate-500">
Kilde: <a href="https://docs.tink.com/resources/tutorials/test-your-integration-with-demo-bank" target="_blank" class="text-violet-400 hover:text-violet-300 underline">Tink Demo Bank dokumentation</a>
</div>
</div>
</div>
{% endif %}
</div>
<!-- FALLBACK: console.tink.com/callback + manual code paste -->
<details class="bg-slate-900 border border-slate-700/30 rounded-xl overflow-hidden">
<summary class="px-5 py-3 cursor-pointer text-slate-500 hover:text-slate-400 text-xs flex items-center gap-2 select-none">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
Alternativ: manuel kode-indsætning via console.tink.com
<span class="ml-auto"></span>
</summary>
<div class="px-5 pb-4 pt-3 border-t border-slate-800 space-y-3">
<a href="{{ dev_tink_link_url }}" target="_blank"
class="inline-flex items-center gap-2 px-4 py-2 bg-slate-700 hover:bg-slate-600 text-slate-300 text-sm rounded-lg transition">
Åbn med console callback
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
</a>
<form method="POST" action="/demo/step/3" class="flex gap-2 items-stretch">
<input type="text" name="code" placeholder="Indsæt code=XXXX fra console.tink.com/callback..."
class="flex-1 bg-slate-800 border border-slate-700 text-slate-200 rounded-lg px-3 py-2 text-sm font-mono placeholder-slate-600 focus:outline-none focus:border-emerald-600" required>
<button type="submit"
class="px-4 py-2 bg-emerald-700 hover:bg-emerald-600 text-white text-sm font-semibold rounded-lg transition whitespace-nowrap">
Brug kode
</button>
</form>
</div>
</details>
{% endif %}{# end not cb_success #}
{% endif %}{# end tink_link_url #}
<!-- Error state -->
{% if error %}
<div class="bg-red-950/50 border border-red-800/50 rounded-xl p-5">
<div class="flex items-start gap-3">
<span class="text-red-400 text-xl flex-shrink-0"></span>
<div>
<h3 class="text-red-300 font-semibold mb-1">Fejl</h3>
<pre class="text-red-400 text-sm font-mono whitespace-pre-wrap">{{ error }}</pre>
</div>
</div>
</div>
{% endif %}
<!-- JSON response -->
{% if result %}
<div class="bg-slate-900 border border-slate-800 rounded-xl overflow-hidden">
<div class="flex items-center justify-between px-4 py-3 border-b border-slate-800">
<div class="flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-emerald-400"></span>
<span class="text-sm text-slate-300 font-semibold">Response</span>
<span class="text-xs text-emerald-400 font-mono">200 OK</span>
{% if is_demo %}
<span class="text-xs px-2 py-0.5 rounded-full font-semibold bg-amber-900/60 text-amber-300 border border-amber-700/40">⚠ Sample Data</span>
{% endif %}
</div>
<button onclick="copyToClipboard('json-{{ step }}')"
class="text-xs text-slate-400 hover:text-white transition px-2 py-1 rounded bg-slate-800 hover:bg-slate-700">
Kopier JSON
</button>
</div>
<div class="p-4 max-h-[520px] overflow-y-auto">
<pre id="json-{{ step }}" class="raw-json">{{ result | tojson(indent=2) }}</pre>
</div>
</div>
{% elif not error %}
<!-- Waiting state -->
<div class="bg-slate-900 border border-slate-800 rounded-xl p-10 flex flex-col items-center justify-center gap-3 text-center">
<div class="w-12 h-12 rounded-full bg-slate-800 flex items-center justify-center">
<svg class="w-6 h-6 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
</div>
<p class="text-slate-400 text-sm">Klik på knappen ovenfor for at køre dette API-kald og se svaret her.</p>
</div>
{% endif %}
</div>
</div>
{% endblock %}