billing frontend for mariadb. setup as otb_billing for outsidethebox.top accounting. also involved with outsidethedb
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

272 lines
8.1 KiB

cd /home/def/otb_billing || exit 1
set -e
STAMP="$(date +%Y%m%d-%H%M%S)"
cp templates/portal_dashboard.html "templates/portal_dashboard.html.bak.${STAMP}"
cp templates/portal/services_here.html "templates/portal/services_here.html.bak.${STAMP}"
cat > templates/portal_base.html <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Portal - OutsideTheBox{% endblock %}</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/brand.css">
<link rel="icon" type="image/png" href="/static/favicon.png">
{% block head_extra %}{% endblock %}
</head>
<body>
{% include "includes/site_nav.html" %}
<div class="portal-shell">
<div class="portal-wrap">
{% block portal_content %}{% endblock %}
</div>
</div>
{% block scripts %}{% endblock %}
{% include "includes/otb_footer.html" %}
</body>
</html>
EOF
cat > templates/portal_dashboard.html <<'EOF'
{% extends "portal_base.html" %}
{% block title %}Client Dashboard - OutsideTheBox{% endblock %}
{% block portal_content %}
<div class="portal-page-header">
<div>
<h1 class="portal-page-title">Client Dashboard</h1>
<p class="portal-client-name">{{ client.company_name or client.contact_name or client.email }}</p>
<p class="portal-page-subtitle">Invoices, balances, and account activity in one place.</p>
</div>
<div class="portal-toolbar" style="display:flex;flex-direction:column;align-items:flex-end;gap:10px;">
<div style="display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;">
<a class="portal-btn primary" href="/portal/services">Services Here</a>
<a class="portal-btn primary" href="/portal/invoices/download-all">Download All Invoices</a>
<a class="portal-btn" href="mailto:support@outsidethebox.top?subject=Customer%20Support">Customer Support</a>
<a class="portal-btn" href="/portal/logout">Logout</a>
</div>
<div style="text-align:right;font-size:14px;opacity:0.95;">
<div>Logged in as: <strong>{{ client.contact_name or client.company_name or client.email }}</strong></div>
{% if client_credit_balance and client_credit_balance != "0.00" %}
<div style="margin-top:6px;">
<span style="display:inline-block;padding:4px 10px;border-radius:999px;background:#0f2f21;color:#86efac;font-weight:700;">
🏦 Credit: ${{ client_credit_balance }}
</span>
</div>
{% endif %}
</div>
</div>
</div>
<div class="summary-grid">
<div class="summary-card">
<h3>Total Invoices</h3>
<div class="summary-value">{{ invoice_count }}</div>
<div class="summary-sub">Invoices currently visible in your portal</div>
</div>
<div class="summary-card">
<h3>Total Outstanding</h3>
<div class="summary-value">{{ total_outstanding }}</div>
<div class="summary-sub">Current unpaid balance</div>
</div>
<div class="summary-card">
<h3>Total Paid</h3>
<div class="summary-value">{{ total_paid }}</div>
<div class="summary-sub">Payments already applied</div>
</div>
</div>
<h2 class="section-title">Invoices</h2>
<div class="table-card">
<table class="portal-table">
<thead>
<tr>
<th>Invoice</th>
<th>Status</th>
<th>Created</th>
<th>Total</th>
<th>Paid</th>
<th>Outstanding</th>
</tr>
</thead>
<tbody>
{% for row in invoices %}
<tr>
<td>
<a class="invoice-link" href="/portal/invoice/{{ row.id }}">
{{ row.invoice_number or ("INV-" ~ row.id) }}
</a>
</td>
<td>
{% set s = (row.status or "")|lower %}
{% if s == "paid" %}
<span class="status-badge status-paid">{{ row.status }}</span>
{% if row.payment_method_label %}
<div class="payment-method{% if row.payment_method_label == "Square" %} payment-square{% elif row.payment_method_label == "e-Transfer" %} payment-etransfer{% elif row.payment_method_label == "ETHO" %} payment-etho{% elif row.payment_method_label == "ETI" or row.payment_method_label == "EGAZ" %} payment-etica{% elif row.payment_method_label == "ALT" %} payment-alt{% elif row.payment_method_label == "CAD" %} payment-cad{% endif %}">
{{ row.payment_method_label }}
</div>
{% endif %}
{% elif s == "pending" %}
<span class="status-badge status-pending">{{ row.status }}</span>
{% elif s == "overdue" %}
<span class="status-badge status-overdue">{{ row.status }}</span>
{% else %}
<span class="status-badge status-other">{{ row.status }}</span>
{% endif %}
</td>
<td>{{ row.created_at }}</td>
<td>{{ row.total_amount }}</td>
<td>{{ row.amount_paid }}</td>
<td>{{ row.outstanding }}</td>
</tr>
{% else %}
<tr>
<td colspan="6">No invoices available.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
{% block scripts %}
<script>
(function() {
setTimeout(function() { window.location.reload(); }, 20000);
})();
</script>
{% endblock %}
EOF
cat > templates/portal/services_here.html <<'EOF'
{% extends "portal_base.html" %}
{% block title %}Services - OutsideTheBox{% endblock %}
{% block portal_content %}
<div class="portal-page-header">
<div>
<h1 class="portal-page-title">Services</h1>
<p class="portal-client-name">{{ client_name }}</p>
<p class="portal-page-subtitle">Launch available OTB services from one place.</p>
</div>
<div class="portal-toolbar" style="display:flex;flex-direction:column;align-items:flex-end;gap:10px;">
<div style="display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;">
<a class="portal-btn primary" href="/portal/dashboard">Back to Dashboard</a>
<a class="portal-btn" href="/portal/logout">Logout</a>
</div>
<div style="text-align:right;font-size:14px;opacity:0.95;">
<div>Logged in as: <strong>{{ client_name }}</strong></div>
</div>
</div>
</div>
<section class="services-grid">
{% for service in services %}
<article class="service-card status-{{ service.status }}">
<div class="service-card-header">
<div>
<h2>{{ service.name }}</h2>
<p>{{ service.summary }}</p>
</div>
<div>
{% if service.status == 'beta' %}
<span class="service-badge service-badge-beta">Beta</span>
{% elif service.status == 'coming_soon' %}
<span class="service-badge service-badge-soon">Coming Soon</span>
{% else %}
<span class="service-badge">Available</span>
{% endif %}
</div>
</div>
<div class="service-card-actions">
{% if service.enabled %}
<a class="portal-btn primary" href="{{ service.href }}">{{ service.button_text }}</a>
{% else %}
<button class="portal-btn btn-disabled" disabled>{{ service.button_text }}</button>
{% endif %}
</div>
</article>
{% endfor %}
</section>
<style>
.services-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
}
.service-card {
border-radius: 18px;
padding: 22px;
border: 1px solid rgba(255,255,255,0.10);
background: rgba(8, 18, 37, 0.72);
box-shadow: 0 8px 24px rgba(0,0,0,0.18);
}
.service-card-header {
display: flex;
justify-content: space-between;
gap: 16px;
align-items: flex-start;
}
.service-card h2 {
margin: 0 0 10px 0;
font-size: 1.3rem;
}
.service-card p {
margin: 0;
line-height: 1.5;
opacity: 0.92;
}
.service-card-actions {
margin-top: 22px;
}
.service-badge {
display: inline-block;
padding: 6px 10px;
border-radius: 999px;
font-size: 0.82rem;
font-weight: 700;
background: rgba(255,255,255,0.10);
}
.service-badge-beta {
background: rgba(73, 192, 255, 0.18);
}
.service-badge-soon {
background: rgba(255, 196, 73, 0.18);
}
.btn-disabled {
opacity: 0.65;
cursor: not-allowed;
}
</style>
{% endblock %}
EOF
sudo systemctl restart otb_billing.service
sudo systemctl status otb_billing.service --no-pager -l | sed -n '1,30p'