This commit is contained in:
2026-03-27 10:20:35 +01:00
parent b922a1ced0
commit fa9d7c7f43
48 changed files with 67256 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
FROM python:3.12-slim
LABEL maintainer="DragonBank Team"
LABEL description="DragonBank Frontend Web App"
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
COPY . .
RUN adduser --disabled-password --gecos '' appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=15s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/ || exit 1
CMD ["python", "app.py"]
+357
View File
@@ -0,0 +1,357 @@
"""
DragonBank - Frontend Web Application
======================================
Interface web pour l'application bancaire DragonBank.
"""
import os
from functools import wraps
import requests
from flask import Flask, render_template, request, redirect, url_for, session, flash
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'frontend-secret')
BACKEND_URL = os.environ.get('BACKEND_URL', 'http://backend:5000')
# ============================================
# UTILITAIRES
# ============================================
def api_request(method, endpoint, data=None, token=None):
"""Effectue une requête vers le backend API."""
url = f"{BACKEND_URL}{endpoint}"
headers = {'Content-Type': 'application/json'}
if token:
headers['Authorization'] = f'Bearer {token}'
try:
if method == 'GET':
resp = requests.get(url, headers=headers, timeout=10)
elif method == 'POST':
resp = requests.post(url, json=data, headers=headers, timeout=10)
elif method == 'DELETE':
resp = requests.delete(url, headers=headers, timeout=10)
else:
return None, 'Méthode non supportée'
return resp.json(), resp.status_code
except requests.exceptions.ConnectionError:
return {'error': 'Impossible de contacter le serveur'}, 503
except Exception as e:
return {'error': str(e)}, 500
def login_required(f):
"""Décorateur pour protéger les routes nécessitant une connexion."""
@wraps(f)
def decorated(*args, **kwargs):
if 'token' not in session:
flash('Veuillez vous connecter', 'warning')
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated
# ============================================
# ROUTES PUBLIQUES
# ============================================
@app.route('/')
def home():
"""Page d'accueil."""
if 'token' in session:
return redirect(url_for('dashboard'))
return render_template('login.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
"""Page de connexion."""
if request.method == 'POST':
email = request.form.get('email')
password = request.form.get('password')
data, status = api_request('POST', '/api/auth/login', {
'email': email,
'password': password
})
if status == 200:
session['token'] = data['token']
session['user'] = data['user']
flash(f'Bienvenue {data["user"]["first_name"]} ! 🐉', 'success')
return redirect(url_for('dashboard'))
else:
flash(data.get('error', 'Erreur de connexion'), 'danger')
return render_template('login.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
"""Page d'inscription."""
if request.method == 'POST':
form_data = {
'email': request.form.get('email'),
'password': request.form.get('password'),
'first_name': request.form.get('first_name'),
'last_name': request.form.get('last_name'),
'phone': request.form.get('phone', ''),
'address': request.form.get('address', ''),
'date_of_birth': request.form.get('date_of_birth') or None
}
# Vérifier confirmation mot de passe
if request.form.get('password') != request.form.get('confirm_password'):
flash('Les mots de passe ne correspondent pas', 'danger')
return render_template('register.html')
data, status = api_request('POST', '/api/auth/register', form_data)
if status == 201:
session['token'] = data['token']
session['user'] = data['user']
flash('Compte créé avec succès ! Un compte courant a été ouvert automatiquement. 🎉', 'success')
return redirect(url_for('dashboard'))
else:
flash(data.get('error', 'Erreur lors de l\'inscription'), 'danger')
return render_template('register.html')
@app.route('/logout')
def logout():
"""Déconnexion."""
session.clear()
flash('Vous êtes déconnecté', 'info')
return redirect(url_for('login'))
# ============================================
# ROUTES PROTÉGÉES - DASHBOARD
# ============================================
@app.route('/dashboard')
@login_required
def dashboard():
"""Tableau de bord principal."""
token = session['token']
accounts_data, _ = api_request('GET', '/api/accounts', token=token)
stats_data, _ = api_request('GET', '/api/stats', token=token)
transactions_data, _ = api_request('GET', '/api/transactions?limit=5', token=token)
return render_template('dashboard.html',
user=session.get('user', {}),
accounts=accounts_data.get('accounts', []),
stats=stats_data.get('stats', {}),
recent_transactions=transactions_data.get('transactions', [])
)
# ============================================
# ROUTES - COMPTES
# ============================================
@app.route('/accounts')
@login_required
def accounts():
"""Liste des comptes bancaires."""
token = session['token']
data, status = api_request('GET', '/api/accounts', token=token)
return render_template('accounts.html',
user=session.get('user', {}),
accounts=data.get('accounts', [])
)
@app.route('/accounts/open', methods=['GET', 'POST'])
@login_required
def open_account():
"""Ouverture d'un nouveau compte."""
if request.method == 'POST':
token = session['token']
form_data = {
'account_type': request.form.get('account_type'),
'initial_deposit': float(request.form.get('initial_deposit', 0))
}
data, status = api_request('POST', '/api/accounts', data=form_data, token=token)
if status == 201:
flash(f'Compte {form_data["account_type"]} ouvert avec succès ! 🎉', 'success')
return redirect(url_for('accounts'))
else:
flash(data.get('error', 'Erreur lors de l\'ouverture du compte'), 'danger')
return render_template('open_account.html', user=session.get('user', {}))
# ============================================
# ROUTES - BÉNÉFICIAIRES
# ============================================
@app.route('/beneficiaries')
@login_required
def beneficiaries():
"""Liste des bénéficiaires."""
token = session['token']
data, status = api_request('GET', '/api/beneficiaries', token=token)
return render_template('beneficiaries.html',
user=session.get('user', {}),
beneficiaries=data.get('beneficiaries', [])
)
@app.route('/beneficiaries/add', methods=['GET', 'POST'])
@login_required
def add_beneficiary():
"""Ajout d'un bénéficiaire."""
if request.method == 'POST':
token = session['token']
form_data = {
'beneficiary_name': request.form.get('beneficiary_name'),
'account_number': request.form.get('account_number'),
'bank_name': request.form.get('bank_name', 'DragonBank'),
'iban': request.form.get('iban', ''),
'bic': request.form.get('bic', '')
}
data, status = api_request('POST', '/api/beneficiaries', data=form_data, token=token)
if status == 201:
flash('Bénéficiaire ajouté avec succès !', 'success')
return redirect(url_for('beneficiaries'))
else:
flash(data.get('error', 'Erreur'), 'danger')
return render_template('add_beneficiary.html', user=session.get('user', {}))
@app.route('/beneficiaries/delete/<beneficiary_id>')
@login_required
def delete_beneficiary(beneficiary_id):
"""Suppression d'un bénéficiaire."""
token = session['token']
data, status = api_request('DELETE', f'/api/beneficiaries/{beneficiary_id}', token=token)
if status == 200:
flash('Bénéficiaire supprimé', 'success')
else:
flash(data.get('error', 'Erreur'), 'danger')
return redirect(url_for('beneficiaries'))
# ============================================
# ROUTES - VIREMENTS
# ============================================
@app.route('/transfer', methods=['GET', 'POST'])
@login_required
def transfer():
"""Page de virement (interne et entre personnes)."""
token = session['token']
accounts_data, _ = api_request('GET', '/api/accounts', token=token)
beneficiaries_data, _ = api_request('GET', '/api/beneficiaries', token=token)
if request.method == 'POST':
transfer_type = request.form.get('transfer_type')
amount = float(request.form.get('amount', 0))
from_account_id = request.form.get('from_account_id')
description = request.form.get('description', '')
if transfer_type == 'internal':
to_account_id = request.form.get('to_account_id')
data, status = api_request('POST', '/api/transfers/internal', {
'from_account_id': from_account_id,
'to_account_id': to_account_id,
'amount': amount,
'description': description
}, token=token)
elif transfer_type == 'person':
beneficiary_id = request.form.get('beneficiary_id')
data, status = api_request('POST', '/api/transfers/person', {
'from_account_id': from_account_id,
'beneficiary_id': beneficiary_id,
'amount': amount,
'description': description
}, token=token)
else:
flash('Type de virement invalide', 'danger')
return redirect(url_for('transfer'))
if status == 200:
flash(data.get('message', 'Virement effectué !'), 'success')
return redirect(url_for('dashboard'))
else:
flash(data.get('error', 'Erreur lors du virement'), 'danger')
return render_template('transfer.html',
user=session.get('user', {}),
accounts=accounts_data.get('accounts', []),
beneficiaries=beneficiaries_data.get('beneficiaries', [])
)
@app.route('/transfer/external', methods=['GET', 'POST'])
@login_required
def transfer_external():
"""Virement externe (depuis/vers autre banque)."""
token = session['token']
accounts_data, _ = api_request('GET', '/api/accounts', token=token)
if request.method == 'POST':
form_data = {
'account_id': request.form.get('account_id'),
'amount': float(request.form.get('amount', 0)),
'external_bank_name': request.form.get('external_bank_name'),
'external_account_number': request.form.get('external_account_number'),
'direction': request.form.get('direction'),
'description': request.form.get('description', '')
}
data, status = api_request('POST', '/api/transfers/external', form_data, token=token)
if status == 200:
flash(data.get('message', 'Virement externe effectué !'), 'success')
return redirect(url_for('dashboard'))
else:
flash(data.get('error', 'Erreur'), 'danger')
return render_template('transfer_external.html',
user=session.get('user', {}),
accounts=accounts_data.get('accounts', [])
)
# ============================================
# ROUTES - HISTORIQUE
# ============================================
@app.route('/transactions')
@login_required
def transactions():
"""Historique des transactions."""
token = session['token']
account_id = request.args.get('account_id', '')
endpoint = '/api/transactions?limit=100'
if account_id:
endpoint += f'&account_id={account_id}'
data, status = api_request('GET', endpoint, token=token)
accounts_data, _ = api_request('GET', '/api/accounts', token=token)
return render_template('transactions.html',
user=session.get('user', {}),
transactions=data.get('transactions', []),
accounts=accounts_data.get('accounts', []),
selected_account=account_id
)
# ============================================
# DÉMARRAGE
# ============================================
if __name__ == '__main__':
print("🐉 DragonBank Frontend starting on port 8080...")
app.run(host='0.0.0.0', port=8080, debug=False)
+2
View File
@@ -0,0 +1,2 @@
Flask==3.0.0
requests==2.31.0
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
{% extends "base.html" %}
{% block title %}Mes Comptes{% endblock %}
{% block content %}
<div class="main-content">
<div class="page-header d-flex justify-content-between align-items-center">
<div>
<h1><i class="bi bi-wallet2"></i> Mes Comptes</h1>
<p>Gérez vos comptes bancaires</p>
</div>
<a href="{{ url_for('open_account') }}" class="btn btn-dragon">
<i class="bi bi-plus-circle"></i> Ouvrir un compte
</a>
</div>
<div class="row g-4">
{% for account in accounts %}
<div class="col-lg-6 fade-in delay-{{ loop.index }}">
<div class="account-card {{ account.account_type }}" style="padding: 2rem;">
<div class="d-flex justify-content-between align-items-start mb-3">
<span class="account-type-badge {{ account.account_type }}">
{% if account.account_type == 'courant' %}
<i class="bi bi-credit-card"></i> Compte Courant
{% elif account.account_type == 'livret_a' %}
<i class="bi bi-piggy-bank"></i> Livret A
{% elif account.account_type == 'assurance_vie' %}
<i class="bi bi-shield-check"></i> Assurance Vie
{% endif %}
</span>
<span class="status-badge completed">
<i class="bi bi-check-circle"></i> {{ account.status }}
</span>
</div>
<div class="account-balance mb-2">
{{ "%.2f"|format(account.balance) }} €
</div>
<div class="account-number mb-3">
<i class="bi bi-hash"></i> {{ account.account_number }}
</div>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<i class="bi bi-calendar"></i>
Ouvert le {{ account.created_at[:10] if account.created_at else 'N/A' }}
</small>
{% if account.interest_rate > 0 %}
<small class="text-success fw-bold">
<i class="bi bi-graph-up-arrow"></i>
Taux: {{ "%.2f"|format(account.interest_rate * 100) }}%
</small>
{% endif %}
</div>
<div class="mt-3 d-flex gap-2">
<a href="{{ url_for('transactions', account_id=account.id) }}"
class="btn btn-sm btn-dragon-outline">
<i class="bi bi-clock-history"></i> Historique
</a>
<a href="{{ url_for('transfer') }}" class="btn btn-sm btn-dragon">
<i class="bi bi-arrow-left-right"></i> Virement
</a>
</div>
</div>
</div>
{% else %}
<div class="col-12">
<div class="section-card text-center py-5">
<i class="bi bi-wallet" style="font-size: 4rem; color: var(--dragon-text-light);"></i>
<h3 class="mt-3">Aucun compte bancaire</h3>
<p class="text-muted">Ouvrez votre premier compte pour commencer</p>
<a href="{{ url_for('open_account') }}" class="btn btn-dragon mt-2">
<i class="bi bi-plus-circle"></i> Ouvrir un compte
</a>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
@@ -0,0 +1,70 @@
{% extends "base.html" %}
{% block title %}Ajouter un Bénéficiaire{% endblock %}
{% block content %}
<div class="main-content">
<div class="page-header">
<h1><i class="bi bi-person-plus"></i> Ajouter un Bénéficiaire</h1>
<p>Enregistrez un nouveau contact pour vos virements</p>
</div>
<div class="row justify-content-center">
<div class="col-md-6">
<div class="section-card fade-in">
<form method="POST" action="{{ url_for('add_beneficiary') }}">
<div class="form-group">
<label class="form-label">
<i class="bi bi-person"></i> Nom du bénéficiaire *
</label>
<input type="text" name="beneficiary_name" class="form-control"
placeholder="Ex: Marie Martin" required>
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-bank"></i> Banque
</label>
<input type="text" name="bank_name" class="form-control"
value="DragonBank" placeholder="DragonBank">
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-hash"></i> Numéro de compte *
</label>
<input type="text" name="account_number" class="form-control"
placeholder="Ex: DRG0000000005678" required>
<small class="text-muted">
Pour DragonBank, le numéro commence par DRG
</small>
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-upc"></i> IBAN (optionnel)
</label>
<input type="text" name="iban" class="form-control"
placeholder="FR76 XXXX XXXX XXXX">
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-building"></i> BIC (optionnel)
</label>
<input type="text" name="bic" class="form-control"
placeholder="DRGBFRPP">
</div>
<button type="submit" class="btn btn-dragon w-100 mt-3">
<i class="bi bi-check-circle"></i> Ajouter le bénéficiaire
</button>
<a href="{{ url_for('beneficiaries') }}" class="btn btn-dragon-outline w-100 mt-2">
<i class="bi bi-arrow-left"></i> Retour
</a>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
+123
View File
@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🐉 DragonBank - {% block title %}Accueil{% endblock %}</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
</head>
<body>
{% if session.get('token') %}
<!-- NAVBAR CONNECTÉ -->
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('dashboard') }}">
<span class="dragon-icon">🐉</span> DragonBank
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'dashboard' %}active{% endif %}"
href="{{ url_for('dashboard') }}">
<i class="bi bi-speedometer2"></i> Tableau de bord
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint in ['accounts', 'open_account'] %}active{% endif %}"
href="{{ url_for('accounts') }}">
<i class="bi bi-wallet2"></i> Mes comptes
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint in ['beneficiaries', 'add_beneficiary'] %}active{% endif %}"
href="{{ url_for('beneficiaries') }}">
<i class="bi bi-people"></i> Bénéficiaires
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle {% if request.endpoint in ['transfer', 'transfer_external'] %}active{% endif %}"
href="#" role="button" data-bs-toggle="dropdown">
<i class="bi bi-arrow-left-right"></i> Virements
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{{ url_for('transfer') }}">
<i class="bi bi-arrow-repeat"></i> Virement interne / Personne
</a></li>
<li><a class="dropdown-item" href="{{ url_for('transfer_external') }}">
<i class="bi bi-bank"></i> Virement externe
</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'transactions' %}active{% endif %}"
href="{{ url_for('transactions') }}">
<i class="bi bi-clock-history"></i> Historique
</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item me-3 d-flex align-items-center">
<span class="user-badge">
<i class="bi bi-person-circle"></i>
{{ session.get('user', {}).get('first_name', '') }}
{{ session.get('user', {}).get('last_name', '') }}
</span>
</li>
<li class="nav-item">
<a class="nav-link text-danger" href="{{ url_for('logout') }}">
<i class="bi bi-box-arrow-right"></i> Déconnexion
</a>
</li>
</ul>
</div>
</div>
</nav>
{% endif %}
<!-- FLASH MESSAGES -->
<div class="container-fluid mt-3">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
{% if category == 'success' %}
<i class="bi bi-check-circle-fill"></i>
{% elif category == 'danger' %}
<i class="bi bi-exclamation-triangle-fill"></i>
{% elif category == 'warning' %}
<i class="bi bi-exclamation-circle-fill"></i>
{% else %}
<i class="bi bi-info-circle-fill"></i>
{% endif %}
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<!-- CONTENU PRINCIPAL -->
{% block content %}{% endblock %}
{% if session.get('token') %}
<footer class="footer">
<p>🐉 <strong>DragonBank</strong> &copy; 2024 — Votre banque de confiance |
Sécurisé par <a href="#">Dragon Shield™</a></p>
</footer>
{% endif %}
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>
@@ -0,0 +1,154 @@
{% extends "base.html" %}
{% block title %}Tableau de bord{% endblock %}
{% block content %}
<div class="main-content">
<!-- HEADER -->
<div class="page-header fade-in">
<h1>Bienvenue {{ user.get('first_name', '') }} 🐉</h1>
<p>Voici un aperçu de vos finances</p>
</div>
<!-- STATISTIQUES -->
<div class="row g-4 mb-4">
<div class="col-lg-3 col-md-6 fade-in delay-1">
<div class="stat-card primary">
<div class="stat-icon"><i class="bi bi-wallet2"></i></div>
<div class="stat-value">{{ "%.2f"|format(stats.get('total_balance', 0)) }} €</div>
<div class="stat-label">Solde total</div>
</div>
</div>
<div class="col-lg-3 col-md-6 fade-in delay-2">
<div class="stat-card success">
<div class="stat-icon"><i class="bi bi-credit-card"></i></div>
<div class="stat-value">{{ stats.get('total_accounts', 0) }}</div>
<div class="stat-label">Comptes actifs</div>
</div>
</div>
<div class="col-lg-3 col-md-6 fade-in delay-3">
<div class="stat-card info">
<div class="stat-icon"><i class="bi bi-arrow-left-right"></i></div>
<div class="stat-value">{{ stats.get('monthly_transactions', 0) }}</div>
<div class="stat-label">Transactions ce mois</div>
</div>
</div>
<div class="col-lg-3 col-md-6 fade-in delay-4">
<div class="stat-card warning">
<div class="stat-icon"><i class="bi bi-people"></i></div>
<div class="stat-value">{{ stats.get('total_beneficiaries', 0) }}</div>
<div class="stat-label">Bénéficiaires</div>
</div>
</div>
</div>
<div class="row g-4">
<!-- MES COMPTES -->
<div class="col-lg-7 fade-in delay-2">
<div class="section-card">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="section-title mb-0" style="border:none; padding:0;">
<i class="bi bi-wallet2"></i> Mes Comptes
</h3>
<a href="{{ url_for('open_account') }}" class="btn btn-sm btn-dragon">
<i class="bi bi-plus-circle"></i> Ouvrir un compte
</a>
</div>
{% if accounts %}
{% for account in accounts %}
<div class="account-card {{ account.account_type }}">
<div class="d-flex justify-content-between align-items-start">
<div>
<span class="account-type-badge {{ account.account_type }}">
{% if account.account_type == 'courant' %}
<i class="bi bi-credit-card"></i> Compte Courant
{% elif account.account_type == 'livret_a' %}
<i class="bi bi-piggy-bank"></i> Livret A
{% elif account.account_type == 'assurance_vie' %}
<i class="bi bi-shield-check"></i> Assurance Vie
{% endif %}
</span>
<div class="account-number mt-2">
{{ account.account_number }}
</div>
</div>
<div class="text-end">
<div class="account-balance">
{{ "%.2f"|format(account.balance) }} €
</div>
{% if account.interest_rate > 0 %}
<small class="text-success">
<i class="bi bi-graph-up-arrow"></i>
{{ "%.2f"|format(account.interest_rate * 100) }}% / jour
</small>
{% endif %}
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-4 text-muted">
<i class="bi bi-wallet" style="font-size: 3rem;"></i>
<p class="mt-2">Aucun compte trouvé</p>
</div>
{% endif %}
</div>
</div>
<!-- ACTIONS RAPIDES + DERNIÈRES TRANSACTIONS -->
<div class="col-lg-5 fade-in delay-3">
<!-- Actions rapides -->
<div class="section-card mb-4">
<h3 class="section-title">
<i class="bi bi-lightning-charge"></i> Actions rapides
</h3>
<div class="d-grid gap-2">
<a href="{{ url_for('transfer') }}" class="btn btn-dragon">
<i class="bi bi-arrow-left-right"></i> Faire un virement
</a>
<a href="{{ url_for('add_beneficiary') }}" class="btn btn-gold">
<i class="bi bi-person-plus"></i> Ajouter un bénéficiaire
</a>
<a href="{{ url_for('transfer_external') }}" class="btn btn-success-custom">
<i class="bi bi-bank"></i> Virement externe
</a>
</div>
</div>
<!-- Dernières transactions -->
<div class="section-card">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="section-title mb-0" style="border:none; padding:0;">
<i class="bi bi-clock-history"></i> Dernières opérations
</h3>
<a href="{{ url_for('transactions') }}" class="btn btn-sm btn-dragon-outline">
Voir tout
</a>
</div>
{% if recent_transactions %}
{% for tx in recent_transactions %}
<div class="d-flex justify-content-between align-items-center py-2 border-bottom">
<div>
<div class="fw-bold" style="font-size: 0.9rem;">
{{ tx.description[:40] }}{% if tx.description|length > 40 %}...{% endif %}
</div>
<small class="text-muted">
{{ tx.created_at[:10] if tx.created_at else '' }}
</small>
</div>
<div>
<span class="fw-bold" style="color: {% if tx.transaction_type in ['depot', 'interets'] %}var(--dragon-success){% else %}var(--dragon-accent){% endif %};">
{{ "%.2f"|format(tx.amount) }} €
</span>
</div>
</div>
{% endfor %}
{% else %}
<p class="text-center text-muted py-3">Aucune transaction récente</p>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
+68
View File
@@ -0,0 +1,68 @@
{% extends "base.html" %}
{% block title %}Connexion{% endblock %}
{% block content %}
<div class="login-page">
<div class="login-container">
<div class="login-header">
<span class="dragon-logo">🐉</span>
<h1>DragonBank</h1>
<p>Votre banque. Votre pouvoir.</p>
</div>
<div class="login-card fade-in">
<h2>Connexion</h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} mb-3">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('login') }}">
<div class="form-group">
<label class="form-label">
<i class="bi bi-envelope"></i> Email
</label>
<input type="email" name="email" class="form-control"
placeholder="votre@email.com" required>
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-lock"></i> Mot de passe
</label>
<input type="password" name="password" class="form-control"
placeholder="••••••••" required>
</div>
<button type="submit" class="btn btn-dragon w-100 mt-3">
<i class="bi bi-box-arrow-in-right"></i> Se connecter
</button>
</form>
<hr class="my-4">
<p class="text-center text-muted mb-2">Pas encore de compte ?</p>
<a href="{{ url_for('register') }}" class="btn btn-dragon-outline w-100">
<i class="bi bi-person-plus"></i> Créer un compte
</a>
<div class="text-center mt-4">
<small class="text-muted">
<i class="bi bi-shield-check"></i>
Connexion sécurisée par Dragon Shield™
</small>
</div>
</div>
<div class="text-center mt-3">
<small style="color: rgba(255,255,255,0.5);">
Compte test: jean.dupont@email.com / password123
</small>
</div>
</div>
</div>
{% endblock %}
+102
View File
@@ -0,0 +1,102 @@
{% extends "base.html" %}
{% block title %}Inscription{% endblock %}
{% block content %}
<div class="login-page">
<div class="login-container" style="max-width: 550px;">
<div class="login-header">
<span class="dragon-logo">🐉</span>
<h1>DragonBank</h1>
<p>Rejoignez le clan du Dragon</p>
</div>
<div class="login-card fade-in">
<h2>Créer un compte</h2>
<form method="POST" action="{{ url_for('register') }}">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="form-label">Prénom *</label>
<input type="text" name="first_name" class="form-control"
placeholder="Jean" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label">Nom *</label>
<input type="text" name="last_name" class="form-control"
placeholder="Dupont" required>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-envelope"></i> Email *
</label>
<input type="email" name="email" class="form-control"
placeholder="votre@email.com" required>
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-telephone"></i> Téléphone
</label>
<input type="tel" name="phone" class="form-control"
placeholder="+33 6 12 34 56 78">
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-geo-alt"></i> Adresse
</label>
<input type="text" name="address" class="form-control"
placeholder="12 Rue de la Paix, 75001 Paris">
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-calendar"></i> Date de naissance
</label>
<input type="date" name="date_of_birth" class="form-control">
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="form-label">
<i class="bi bi-lock"></i> Mot de passe *
</label>
<input type="password" name="password" class="form-control"
placeholder="Min. 6 caractères" minlength="6" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label">
<i class="bi bi-lock-fill"></i> Confirmer *
</label>
<input type="password" name="confirm_password" class="form-control"
placeholder="Confirmer" required>
</div>
</div>
</div>
<button type="submit" class="btn btn-dragon w-100 mt-3">
<i class="bi bi-person-plus"></i> Créer mon compte
</button>
</form>
<hr class="my-3">
<p class="text-center">
<a href="{{ url_for('login') }}" class="text-decoration-none"
style="color: var(--dragon-accent);">
<i class="bi bi-arrow-left"></i> Déjà un compte ? Se connecter
</a>
</p>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,98 @@
{% extends "base.html" %}
{% block title %}Virement Externe{% endblock %}
{% block content %}
<div class="main-content">
<div class="page-header">
<h1><i class="bi bi-bank"></i> Virement Externe</h1>
<p>Envoyer ou recevoir un virement depuis une autre banque</p>
</div>
<div class="row justify-content-center">
<div class="col-md-7">
<div class="section-card fade-in">
<form method="POST" action="{{ url_for('transfer_external') }}">
<!-- Direction -->
<div class="form-group">
<label class="form-label fw-bold">
<i class="bi bi-arrow-down-up"></i> Direction
</label>
<select name="direction" class="form-select" required>
<option value="">-- Choisir --</option>
<option value="incoming">Réception (depuis une autre banque)</option>
<option value="outgoing">Envoi (vers une autre banque)</option>
</select>
</div>
<!-- Mon compte -->
<div class="form-group">
<label class="form-label fw-bold">
<i class="bi bi-wallet2"></i> Mon compte DragonBank
</label>
<select name="account_id" class="form-select" required>
<option value="">-- Choisir --</option>
{% for account in accounts %}
<option value="{{ account.id }}">
{{ account.account_type | upper }} -
{{ account.account_number }}
({{ "%.2f"|format(account.balance) }} €)
</option>
{% endfor %}
</select>
</div>
<!-- Banque externe -->
<div class="form-group">
<label class="form-label fw-bold">
<i class="bi bi-building"></i> Nom de la banque externe
</label>
<input type="text" name="external_bank_name" class="form-control"
placeholder="Ex: BNP Paribas, Société Générale..." required>
</div>
<!-- Compte externe -->
<div class="form-group">
<label class="form-label fw-bold">
<i class="bi bi-hash"></i> Numéro de compte externe
</label>
<input type="text" name="external_account_number" class="form-control"
placeholder="IBAN ou numéro de compte" required>
</div>
<!-- Montant -->
<div class="form-group">
<label class="form-label fw-bold">
<i class="bi bi-currency-euro"></i> Montant (€)
</label>
<input type="number" name="amount" class="form-control"
placeholder="0.00" min="0.01" step="0.01" required>
</div>
<!-- Description -->
<div class="form-group">
<label class="form-label">
<i class="bi bi-chat-left-text"></i> Motif (optionnel)
</label>
<input type="text" name="description" class="form-control"
placeholder="Ex: Transfert épargne">
</div>
<div class="alert alert-info mt-3">
<i class="bi bi-info-circle"></i>
<strong>Note:</strong> Les virements externes sont simulés dans cette démo.
En production, ils seraient traités via le réseau bancaire SEPA.
</div>
<button type="submit" class="btn btn-dragon w-100 mt-2">
<i class="bi bi-send"></i> Effectuer le virement
</button>
<a href="{{ url_for('dashboard') }}" class="btn btn-dragon-outline w-100 mt-2">
<i class="bi bi-arrow-left"></i> Annuler
</a>
</form>
</div>
</div>
</div>
</div>
{% endblock %}