package templates import ( "fmt" "veola/internal/models" ) type SettingsData struct { Page Values map[string]string // CredentialStatus maps each secret settings key to a human-readable // status ("Saved in settings", "Set in config.toml", "Not set"). Secret // values are never rendered into the form itself. CredentialStatus map[string]string IsAdmin bool Users []models.User TestNtfyOK string TestApifyOK string TestEbayOK string TestResendOK string EbayUsedToday int EbayDailyLimit int ApifyToday int ApifyMonth int ApifyCostPerCall float64 MonthlyBudget float64 PasswordMsg string PasswordError string UserMsg string UserError string EmailMsg string EmailError string } // ApifyMonthCost is the estimated month-to-date Apify spend. func (d SettingsData) ApifyMonthCost() float64 { return float64(d.ApifyMonth) * d.ApifyCostPerCall } // EbayLimitReached reports whether eBay polling is currently halted because // the daily call limit has been hit. func (d SettingsData) EbayLimitReached() bool { return d.EbayDailyLimit > 0 && d.EbayUsedToday >= d.EbayDailyLimit } // credStatus renders the "Saved in settings / Set in config.toml / Not set" // indicator for a secret field without ever printing the secret itself. templ credStatus(d SettingsData, key string) { if s := d.CredentialStatus[key]; s != "" {
Status: { s }
} } templ settingsBody(d SettingsData) {

Settings

Apify, eBay and Ntfy

@CSRFInput(d.CSRFToken)
@credStatus(d, "apify_api_key")
@credStatus(d, "ebay_client_id")
@credStatus(d, "ebay_client_secret")
eBay API calls today: if d.EbayDailyLimit > 0 { { fmt.Sprintf("%d / %d", d.EbayUsedToday, d.EbayDailyLimit) } } else { { fmt.Sprintf("%d (uncapped)", d.EbayUsedToday) } } if d.EbayLimitReached() { Limit reached. eBay polling halted until the next reset (midnight US Pacific). }
@credStatus(d, "ntfy_token")
@credStatus(d, "resend_api_key")
Used for deal-alert and weekly-digest email. Domain must be verified in Resend.
Apify does not report exact spend; this estimate drives the budget figures shown to everyone.
0 hides the budget bar. When set, the dashboard shows month-to-date spend against it.
if !d.IsAdmin {
Read-only for non-admin users.
} else {
}
if d.TestNtfyOK != "" {
{ d.TestNtfyOK }
} if d.TestApifyOK != "" {
{ d.TestApifyOK }
} if d.TestEbayOK != "" {
{ d.TestEbayOK }
} if d.TestResendOK != "" {
{ d.TestResendOK }
}
Apify calls this month: { fmt.Sprintf("%d", d.ApifyMonth) } if d.ApifyCostPerCall > 0 { { fmt.Sprintf("(~$%.2f est.)", d.ApifyMonthCost()) } } today: { fmt.Sprintf("%d", d.ApifyToday) }

Change Password

if d.PasswordError != "" {
{ d.PasswordError }
} if d.PasswordMsg != "" {
{ d.PasswordMsg }
}
@CSRFInput(d.CSRFToken)

Email Notifications

if d.EmailError != "" {
{ d.EmailError }
} if d.EmailMsg != "" {
{ d.EmailMsg }
}
@CSRFInput(d.CSRFToken)
if emailManagedExternally(d) {
Managed by Authentik. Change your email in your Authentik profile; it syncs here on next sign-in.
} else {
Where deal alerts and the weekly digest are sent. Requires Resend to be configured by an admin.
}
if d.IsAdmin {

Users

if d.UserError != "" {
{ d.UserError }
} if d.UserMsg != "" {
{ d.UserMsg }
} for _, u := range d.Users { }
UsernameRoleCreated
{ u.Username } { string(u.Role) } { u.CreatedAt.Format("2006-01-02") }
}
} // currentEmail and the two opt-in accessors read the signed-in user's saved // email preferences off Page.CurrentUser, guarding the nil case. func currentEmail(d SettingsData) string { if d.CurrentUser == nil { return "" } return d.CurrentUser.Email } func currentDealAlerts(d SettingsData) bool { return d.CurrentUser != nil && d.CurrentUser.EmailDealAlerts } func currentWeeklyDigest(d SettingsData) bool { return d.CurrentUser != nil && d.CurrentUser.EmailWeeklyDigest } // emailManagedExternally reports whether the signed-in user's email comes from // an identity provider (Authentik forward-auth) and so is read-only here. The // address is the IdP match key; letting them edit it would orphan the row. func emailManagedExternally(d SettingsData) bool { return d.CurrentUser != nil && d.CurrentUser.AuthSource == "forward" } templ Settings(d SettingsData) { @Layout(d.Page, settingsBody(d)) }