Skip to main content

Wire up tips UI: CSS, settings toggle, dashboard display - Tip button CSS (tip-section, tip-form, tip-toggle, tip-submit) - Settings toggle for tips_enabled and notify_tip in notification preferences - Dashboard payments tab shows tips received with total, count, and message - TipReceived view type for template rendering

  • Co-Authored-ByClaude Opus 4.6 (1M context) <noreply@anthropic.com>

Author: Max J. <87768334+MaxJMath@users.noreply.github.com> - 2026-04-23 03:22 UTC

Commit:

0a038d802aa26aaf9eae82c829cb6a01e30d20f2

Parent:

99c743d

6 files changed,

+161 insertions,

-0 deletions

OldNewLine
@@ -1172,6 +1172,14 @@
1172
1172
}
1173
1173
1174
1174
.centered-page .tagline {
1175
margin-bottom: 0.25rem;
1176
}
1177
1178
.subtagline {
1179
font-family: var(--font-mono);
1180
color: var(--text-muted);
1181
text-align: center;
1182
font-size: 0.9rem;
1175
1183
margin-bottom: 1rem;
1176
1184
}
1177
1185
@@ -3966,3 +3974,86 @@
3966
3974
opacity: 0.8;
3967
3975
border-top: 1px solid var(--border-color, #ccc);
3968
3976
}
3977
3978
/* ── Tips ── */
3979
3980
.tip-section {
3981
margin-top: 1rem;
3982
text-align: center;
3983
}
3984
3985
.tip-toggle {
3986
font-family: var(--font-mono);
3987
font-size: 0.85rem;
3988
padding: 0.5rem 1.5rem;
3989
background: none;
3990
border: 1px solid var(--text);
3991
color: var(--text);
3992
cursor: pointer;
3993
text-decoration: none;
3994
display: inline-block;
3995
}
3996
3997
.tip-toggle:hover {
3998
background: var(--text);
3999
color: var(--background);
4000
}
4001
4002
.tip-form {
4003
margin-top: 1rem;
4004
display: flex;
4005
flex-direction: column;
4006
gap: 0.75rem;
4007
max-width: 300px;
4008
margin-left: auto;
4009
margin-right: auto;
4010
text-align: left;
4011
}
4012
4013
.tip-form.hidden {
4014
display: none;
4015
}
4016
4017
.tip-amount-row {
4018
display: flex;
4019
align-items: center;
4020
gap: 0.25rem;
4021
}
4022
4023
.tip-amount-row .pricing-currency {
4024
font-size: 1.2rem;
4025
opacity: 0.7;
4026
}
4027
4028
.tip-amount-input {
4029
width: 80px;
4030
font-size: 1.2rem;
4031
padding: 0.4rem 0.5rem;
4032
border: 1px solid var(--border);
4033
background: var(--light-background);
4034
font-family: var(--font-mono);
4035
}
4036
4037
.tip-message-input {
4038
width: 100%;
4039
font-size: 0.85rem;
4040
padding: 0.5rem;
4041
border: 1px solid var(--border);
4042
background: var(--light-background);
4043
font-family: var(--font-body);
4044
resize: vertical;
4045
}
4046
4047
.tip-submit {
4048
font-family: var(--font-mono);
4049
font-size: 0.85rem;
4050
padding: 0.5rem 1rem;
4051
background: var(--text);
4052
color: var(--background);
4053
border: none;
4054
cursor: pointer;
4055
}
4056
4057
.tip-submit:hover {
4058
opacity: 0.85;
4059
}
OldNewLine
@@ -189,6 +189,9 @@
189
189
pub user: User,
190
190
pub payout_summary: Option<PayoutSummary>,
191
191
pub transactions: Vec<Transaction>,
192
pub tips_received: Vec<TipReceived>,
193
pub tips_total: String,
194
pub tips_count: i64,
192
195
}
193
196
194
197
#[derive(Template)]
OldNewLine
@@ -363,6 +363,15 @@
363
363
pub details: String,
364
364
}
365
365
366
/// Tip received for payments tab
367
#[derive(Clone)]
368
pub struct TipReceived {
369
pub date: String,
370
pub tipper_name: String,
371
pub amount: String,
372
pub message: Option<String>,
373
}
374
366
375
/// Project card for dashboard
367
376
#[derive(Clone)]
368
377
pub struct ProjectCard {
OldNewLine
@@ -315,6 +315,16 @@
315
315
{% if user.notify_issues %}checked{% endif %}>
316
316
<label for="notify-issues">Email me about new issues and comments on my repos</label>
317
317
</div>
318
<div class="checkbox-group" style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--border);">
319
<input type="checkbox" id="tips-enabled" name="tips_enabled" value="on"
320
{% if user.tips_enabled %}checked{% endif %}>
321
<label for="tips-enabled">Accept tips on my profile and project pages</label>
322
</div>
323
<div class="checkbox-group">
324
<input type="checkbox" id="notify-tip" name="notify_tip" value="on"
325
{% if user.notify_tip %}checked{% endif %}>
326
<label for="notify-tip">Email me when I receive a tip</label>
327
</div>
318
328
{% endif %}
319
329
<div id="preferences-result"></div>
320
330
<button type="submit" class="primary" style="margin-top: 1rem;">Save Preferences</button>
OldNewLine
@@ -202,6 +202,36 @@
202
202
</div>
203
203
{% endif %}
204
204
205
{% if tips_count > 0 %}
206
<details class="form-section" open>
207
<summary><h2>Tips Received ({{ tips_count }})</h2></summary>
208
<div style="background: var(--surface-muted); padding: 1.25rem; margin-bottom: 1.5rem;">
209
<div style="font-size: 1.25rem; font-weight: bold; margin-bottom: 0.5rem;">{{ tips_total }} total</div>
210
<div style="font-size: 0.85rem; opacity: 0.7;">{{ tips_count }} tips received</div>
211
</div>
212
<table class="data-table">
213
<thead>
214
<tr>
215
<th>Date</th>
216
<th>From</th>
217
<th>Amount</th>
218
<th>Message</th>
219
</tr>
220
</thead>
221
<tbody>
222
{% for tip in tips_received %}
223
<tr>
224
<td>{{ tip.date }}</td>
225
<td>{{ tip.tipper_name }}</td>
226
<td>{{ tip.amount }}</td>
227
<td style="max-width: 300px; overflow: hidden; text-overflow: ellipsis;">{% if let Some(msg) = tip.message %}{{ msg }}{% else %}<span style="opacity: 0.4;">-</span>{% endif %}</td>
228
</tr>
229
{% endfor %}
230
</tbody>
231
</table>
232
</details>
233
{% endif %}
234
205
235
<div class="filter-controls">
206
236
<div class="filter-group">
207
237
<select name="type"
OldNewLine
@@ -150,10 +150,28 @@
150
150
.await?;
151
151
let transactions = super::super::collect_transactions(incoming_txs, outgoing_txs);
152
152
153
// Tips
154
let db_tips = db::tips::get_tips_received(&state.db, session_user.id, 20, 0).await?;
155
let tips_total_cents = db::tips::total_tips_received(&state.db, session_user.id).await?;
156
let tips_count = db::tips::count_tips_received(&state.db, session_user.id).await?;
157
let tips_received: Vec<TipReceived> = db_tips
158
.iter()
159
.map(|t| TipReceived {
160
date: t.created_at.format("%Y-%m-%d").to_string(),
161
tipper_name: t.tipper_display_name.clone()
162
.unwrap_or_else(|| t.tipper_username.clone()),
163
amount: helpers::format_price(t.amount_cents),
164
message: t.message.clone(),
165
})
166
.collect();
167
153
168
Ok(UserPaymentsTabTemplate {
154
169
user,
155
170
payout_summary,
156
171
transactions,
172
tips_received,
173
tips_total: helpers::format_revenue(tips_total_cents),
174
tips_count,
157
175
})
158
176
}
159
177