Initial Commit

This commit is contained in:
Ben Mosley
2026-08-09 22:36:10 -05:00
commit 2bbf79dcd2
15 changed files with 664 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}{{ babystep.title }} · Superior Finance{% endblock %}
{% block content %}
<h1>{{ babystep.title }}</h1>
<p class="muted">{{ babystep.description }}</p>
<p class="muted">Toward goal: <a href="{{ url_for('view_goal', slug=babystep.goal.slug) }}">{{ babystep.goal.title }}</a></p>
<div class="card">
<div class="meter-label">
<span>${{ babystep.saved_amount }} of ${{ babystep.monthly_amount }} / mo</span>
<span>{{ babystep.progress_percent }}%</span>
</div>
<div class="meter">
<div class="meter-fill {% if babystep.completed %}complete{% endif %}" style="width: {{ babystep.progress_percent }}%"></div>
</div>
</div>
<h2 style="margin-top:2rem;">Payments applied here</h2>
<p><a href="{{ url_for('new_payment') }}">+ Add a payment</a></p>
{% if not babystep.payments %}
<p class="empty">No payments logged yet.</p>
{% else %}
<ul class="plain">
{% for payment in babystep.payments|sort(attribute='id', reverse=True) %}
<li>{{ payment.description }} — ${{ payment.amount }} <span class="muted">({{ payment.received_at }})</span></li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

22
templates/babysteps.html Normal file
View File

@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}Babysteps · Superior Finance{% endblock %}
{% block content %}
<h1>Babysteps</h1>
{% if not babysteps %}
<p class="empty">No babysteps yet. <a href="{{ url_for('new_babystep') }}">Add one</a>.</p>
{% else %}
{% for step in babysteps %}
<div class="card">
<h2><a href="{{ url_for('view_babystep', slug=step.slug) }}">{{ step.title }}</a></h2>
<p class="muted">Toward <a href="{{ url_for('view_goal', slug=step.goal.slug) }}">{{ step.goal.title }}</a></p>
<div class="meter-label">
<span>${{ step.saved_amount }} of ${{ step.monthly_amount }} / mo</span>
<span>{{ step.progress_percent }}%</span>
</div>
<div class="meter">
<div class="meter-fill {% if step.completed %}complete{% endif %}" style="width: {{ step.progress_percent }}%"></div>
</div>
</div>
{% endfor %}
{% endif %}
{% endblock %}

164
templates/base.html Normal file
View File

@@ -0,0 +1,164 @@
<!doctype html>
<html lang="en" data-theme="">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Superior Finance{% endblock %}</title>
<style>
:root {
color-scheme: light;
--surface-1: #fcfcfb;
--page-plane: #f9f9f7;
--text-primary: #0b0b0b;
--text-secondary: #52514e;
--text-muted: #898781;
--gridline: #e1e0d9;
--border: rgba(11,11,11,0.10);
--series-1: #2a78d6;
--status-good: #0ca30c;
}
@media (prefers-color-scheme: dark) {
:root:where(:not([data-theme="light"])) {
color-scheme: dark;
--surface-1: #1a1a19;
--page-plane: #0d0d0d;
--text-primary: #ffffff;
--text-secondary: #c3c2b7;
--text-muted: #898781;
--gridline: #2c2c2a;
--border: rgba(255,255,255,0.10);
--series-1: #3987e5;
--status-good: #0ca30c;
}
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
background: var(--page-plane);
color: var(--text-primary);
}
nav {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 0.9rem 1.5rem;
background: var(--surface-1);
border-bottom: 1px solid var(--border);
}
nav a {
color: var(--text-secondary);
text-decoration: none;
font-size: 0.92rem;
}
nav a:hover { color: var(--text-primary); }
nav a.brand { color: var(--text-primary); font-weight: 600; margin-right: auto; }
main {
max-width: 880px;
margin: 0 auto;
padding: 1.5rem;
}
.card {
background: var(--surface-1);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1.25rem;
margin-bottom: 1rem;
}
.card h2 { margin-top: 0; }
.card h2 a { color: inherit; text-decoration: none; }
.card h2 a:hover { text-decoration: underline; }
.muted { color: var(--text-muted); font-size: 0.88rem; }
.flash {
background: var(--surface-1);
border: 1px solid var(--border);
border-left: 3px solid var(--series-1);
border-radius: 6px;
padding: 0.6rem 0.9rem;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.meter {
height: 8px;
border-radius: 4px;
background: var(--gridline);
overflow: hidden;
margin: 0.5rem 0;
}
.meter-fill {
height: 100%;
background: var(--series-1);
border-radius: 4px;
}
.meter-fill.complete { background: var(--status-good); }
.meter-label {
display: flex;
justify-content: space-between;
font-size: 0.82rem;
color: var(--text-secondary);
}
form {
display: flex;
flex-direction: column;
gap: 0.9rem;
max-width: 480px;
}
label {
font-size: 0.85rem;
color: var(--text-secondary);
display: flex;
flex-direction: column;
gap: 0.3rem;
}
input[type="text"], input[type="number"], input[type="file"], select, textarea {
font: inherit;
padding: 0.5rem 0.6rem;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--surface-1);
color: var(--text-primary);
}
.checkbox-row {
flex-direction: row;
align-items: center;
gap: 0.5rem;
}
button {
font: inherit;
padding: 0.55rem 1.1rem;
border: none;
border-radius: 6px;
background: var(--series-1);
color: white;
cursor: pointer;
align-self: flex-start;
}
button:hover { opacity: 0.92; }
.empty { color: var(--text-muted); font-size: 0.9rem; }
ul.plain { list-style: none; padding: 0; margin: 0; }
ul.plain li { padding: 0.5rem 0; border-bottom: 1px solid var(--gridline); }
ul.plain li:last-child { border-bottom: none; }
</style>
</head>
<body>
<nav>
<a class="brand" href="{{ url_for('index') }}">Superior Finance</a>
<a href="{{ url_for('view_goals') }}">Goals</a>
<a href="{{ url_for('view_babysteps') }}">Babysteps</a>
<a href="{{ url_for('view_payments') }}">Payments</a>
<a href="{{ url_for('new_goal') }}">+ Goal</a>
<a href="{{ url_for('new_babystep') }}">+ Babystep</a>
<a href="{{ url_for('new_payment') }}">+ Payment</a>
</nav>
<main>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
</body>
</html>

View File

@@ -0,0 +1,43 @@
{% extends "base.html" %}
{% block title %}{{ goal.title }} · Superior Finance{% endblock %}
{% block content %}
<h1>{{ goal.title }}</h1>
<p class="muted">{{ goal.description }}</p>
{% if goal.image_list %}
<div style="display:flex; gap:0.5rem; flex-wrap:wrap; margin-bottom:1rem;">
{% for image in goal.image_list %}
<img src="{{ url_for('static', filename='uploads/' ~ image) }}" alt="" style="width:120px; height:120px; object-fit:cover; border-radius:8px; border:1px solid var(--border);">
{% endfor %}
</div>
{% endif %}
<div class="card">
<div class="meter-label">
<span>${{ goal.saved_amount }} of ${{ goal.goal_amount }}</span>
<span>{{ goal.progress_percent }}%</span>
</div>
<div class="meter">
<div class="meter-fill {% if goal.completed %}complete{% endif %}" style="width: {{ goal.progress_percent }}%"></div>
</div>
</div>
<h2 style="margin-top:2rem;">Babysteps toward this goal</h2>
<p><a href="{{ url_for('new_babystep') }}">+ Add a babystep</a></p>
{% if not goal.babysteps %}
<p class="empty">No babysteps yet.</p>
{% else %}
{% for step in goal.babysteps %}
<div class="card">
<h2><a href="{{ url_for('view_babystep', slug=step.slug) }}">{{ step.title }}</a></h2>
<div class="meter-label">
<span>${{ step.saved_amount }} of ${{ step.monthly_amount }} / mo</span>
<span>{{ step.progress_percent }}%</span>
</div>
<div class="meter">
<div class="meter-fill {% if step.completed %}complete{% endif %}" style="width: {{ step.progress_percent }}%"></div>
</div>
</div>
{% endfor %}
{% endif %}
{% endblock %}

22
templates/goals.html Normal file
View File

@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}Goals · Superior Finance{% endblock %}
{% block content %}
<h1>Goals</h1>
{% if not goals %}
<p class="empty">No goals yet. <a href="{{ url_for('new_goal') }}">Create your first goal</a>.</p>
{% else %}
{% for goal in goals %}
<div class="card">
<h2><a href="{{ url_for('view_goal', slug=goal.slug) }}">{{ goal.title }}</a></h2>
<p class="muted">{{ goal.description }}</p>
<div class="meter-label">
<span>${{ goal.saved_amount }} of ${{ goal.goal_amount }}</span>
<span>{{ goal.progress_percent }}%</span>
</div>
<div class="meter">
<div class="meter-fill {% if goal.completed %}complete{% endif %}" style="width: {{ goal.progress_percent }}%"></div>
</div>
</div>
{% endfor %}
{% endif %}
{% endblock %}

22
templates/index.html Normal file
View File

@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block title %}Dashboard · Superior Finance{% endblock %}
{% block content %}
<h1>Your Goals</h1>
{% if not goals %}
<p class="empty">No goals yet. <a href="{{ url_for('new_goal') }}">Create your first goal</a>.</p>
{% else %}
{% for goal in goals %}
<div class="card">
<h2><a href="{{ url_for('view_goal', slug=goal.slug) }}">{{ goal.title }}</a></h2>
<div class="meter-label">
<span>${{ goal.saved_amount }} of ${{ goal.goal_amount }}</span>
<span>{{ goal.progress_percent }}%</span>
</div>
<div class="meter">
<div class="meter-fill {% if goal.completed %}complete{% endif %}" style="width: {{ goal.progress_percent }}%"></div>
</div>
<p class="muted">{{ goal.babysteps|length }} babystep(s)</p>
</div>
{% endfor %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block title %}New Babystep · Superior Finance{% endblock %}
{% block content %}
<h1>New Babystep</h1>
{% if not goals %}
<p class="empty">You need a goal before you can add a babystep. <a href="{{ url_for('new_goal') }}">Create one first</a>.</p>
{% else %}
<form method="post">
<label>Goal
<select name="goal_id" required>
{% for goal in goals %}
<option value="{{ goal.id }}">{{ goal.title }}</option>
{% endfor %}
</select>
</label>
<label>Title
<input type="text" name="title" required>
</label>
<label>Description
<textarea name="description" rows="3"></textarea>
</label>
<label>Monthly amount ($)
<input type="number" name="monthly_amount" min="1" required>
</label>
<label class="checkbox-row">
<input type="checkbox" name="completed">
Already achieved
</label>
<button type="submit">Create Babystep</button>
</form>
{% endif %}
{% endblock %}

24
templates/new_goal.html Normal file
View File

@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block title %}New Goal · Superior Finance{% endblock %}
{% block content %}
<h1>New Goal</h1>
<form method="post" enctype="multipart/form-data">
<label>Title
<input type="text" name="title" required>
</label>
<label>Description
<textarea name="description" rows="3"></textarea>
</label>
<label>Target amount ($)
<input type="number" name="goal_amount" min="1" required>
</label>
<label>Images
<input type="file" name="images" multiple accept="image/*">
</label>
<label class="checkbox-row">
<input type="checkbox" name="completed">
Already achieved
</label>
<button type="submit">Create Goal</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}New Payment · Superior Finance{% endblock %}
{% block content %}
<h1>New Payment</h1>
{% if not babysteps %}
<p class="empty">You need a babystep before you can log a payment. <a href="{{ url_for('new_babystep') }}">Create one first</a>.</p>
{% else %}
<form method="post">
<label>Apply to babystep
<select name="babystep_id" required>
{% for step in babysteps %}
<option value="{{ step.id }}">{{ step.title }} ({{ step.goal.title }})</option>
{% endfor %}
</select>
</label>
<label>Description
<input type="text" name="description" placeholder="e.g. Paycheck 8/9" required>
</label>
<label>Amount ($)
<input type="number" name="amount" min="1" required>
</label>
<button type="submit">Log Payment</button>
</form>
{% endif %}
{% endblock %}

17
templates/payments.html Normal file
View File

@@ -0,0 +1,17 @@
{% extends "base.html" %}
{% block title %}Payments · Superior Finance{% endblock %}
{% block content %}
<h1>Payments</h1>
{% if not payments %}
<p class="empty">No payments logged yet. <a href="{{ url_for('new_payment') }}">Log one</a>.</p>
{% else %}
<ul class="plain">
{% for payment in payments %}
<li>
{{ payment.description }} — ${{ payment.amount }}
<span class="muted"><a href="{{ url_for('view_babystep', slug=payment.babystep.slug) }}">{{ payment.babystep.title }}</a> ({{ payment.received_at }})</span>
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}