maj
This commit is contained in:
@@ -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"]
|
||||
@@ -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)
|
||||
@@ -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 %}
|
||||
@@ -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> © 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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user