fix: currency validation, league mobile layout, settings 2-col, burnout ratio, add setusername

- Require both symbol+code in /setcurrency; add preset picker ($ USD,
  T Toman, IRR Rial, EUR, GBP) in settings inline menu
- League: compact 2-line per entry for mobile; sort buttons in 2x2 grid
- Settings: 2-column layout with grouped buttons (timezone+calendar,
  report+report time, break+accent, rpg+league, currency+rate)
- Burnout: cap break ratio at 100% to prevent nonsense values
- Add /setusername command (uses existing SanitizeDisplayName)
- Update /help with new/changed commands
This commit is contained in:
2026-06-25 01:46:07 +03:30
parent 344c615666
commit 605c41ab88
5 changed files with 136 additions and 66 deletions

View File

@@ -179,7 +179,7 @@ func (h *Handler) handleSetRateMsg(ctx context.Context, msg *models.Message) {
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Hourly rate set to %s%.2f", currencySymbol(st.Currency), st.HourlyRate))
}
// handleSetCurrencyMsg handles /setcurrency <symbol> [code].
// handleSetCurrencyMsg handles /setcurrency <symbol> <code>.
func (h *Handler) handleSetCurrencyMsg(ctx context.Context, msg *models.Message) {
args := strings.TrimSpace(strings.TrimPrefix(msg.Text, "/setcurrency"))
if args == "" {
@@ -189,18 +189,23 @@ func (h *Handler) handleSetCurrencyMsg(ctx context.Context, msg *models.Message)
return
}
st, _ := h.DB.GetOrCreateSettings(user.ID)
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Current currency: %s\nUsage: /setcurrency <symbol> [code]\nExamples:\n/setcurrency $ USD\n/setcurrency EUR\n/setcurrency Toman", st.Currency))
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Current currency: %s\nUsage: /setcurrency <symbol> <code>\nExamples:\n/setcurrency $ USD\n/setcurrency T Toman\n/setcurrency IRR Rial", st.Currency))
return
}
parts := strings.Fields(args)
if len(parts[0]) > 10 || strings.ContainsFunc(parts[0], func(r rune) bool { return r < 32 || r > 126 }) {
h.sendText(ctx, msg.Chat.ID, "Invalid currency symbol. Use a short printable string like $, €, £, or Toman.")
if len(parts) < 2 {
h.sendText(ctx, msg.Chat.ID, "Please provide both symbol and code.\nUsage: /setcurrency <symbol> <code>\nExamples:\n/setcurrency $ USD\n/setcurrency T Toman\n/setcurrency IRR Rial")
return
}
currency := parts[0]
if len(parts) >= 2 {
currency = parts[0] + " " + parts[1]
if len(parts[0]) > 10 || strings.ContainsFunc(parts[0], func(r rune) bool { return r < 32 || r > 126 }) {
h.sendText(ctx, msg.Chat.ID, "Invalid currency symbol. Use a short printable string like $, €, £.")
return
}
if len(parts[1]) > 10 || strings.ContainsFunc(parts[1], func(r rune) bool { return r < 32 || r > 126 }) {
h.sendText(ctx, msg.Chat.ID, "Invalid currency code.")
return
}
currency := parts[0] + " " + parts[1]
user, err := h.getOrCreateUser(msg.Chat.ID)
if err != nil {
h.sendText(ctx, msg.Chat.ID, "Error loading profile")
@@ -219,7 +224,7 @@ func (h *Handler) handleSetCurrencyMsg(ctx context.Context, msg *models.Message)
h.sendText(ctx, msg.Chat.ID, fmt.Sprintf("Currency set to: %s", currency))
}
// currencyCallback shows currency info (inline).
// currencyCallback shows the currency picker (inline).
func (h *Handler) currencyCallback(ctx context.Context, chatID int64, msgID int, callbackID string) {
defer h.answerCb(ctx, callbackID)
user, err := h.getOrCreateUser(chatID)
@@ -227,12 +232,46 @@ func (h *Handler) currencyCallback(ctx context.Context, chatID int64, msgID int,
return
}
st, _ := h.DB.GetOrCreateSettings(user.ID)
text := fmt.Sprintf("Currency: %s\n\nUse /setcurrency <symbol> [code] to change it.\nExamples:\n/setcurrency $ USD\n/setcurrency EUR\n/setcurrency Toman", st.Currency)
kb := models.InlineKeyboardMarkup{
InlineKeyboard: [][]models.InlineKeyboardButton{
{{Text: "Back to Settings", CallbackData: "back_settings"}},
},
text, kb := buildCurrencyKeyboard(st.Currency)
h.editText(ctx, chatID, msgID, text, &kb)
}
// buildCurrencyKeyboard returns the currency preset picker keyboard.
func buildCurrencyKeyboard(current string) (string, models.InlineKeyboardMarkup) {
presets := []string{"$ USD", "T Toman", "IRR Rial", "€ EUR", "£ GBP"}
rows := [][]models.InlineKeyboardButton{}
for _, p := range presets {
label := p
if p == current {
label = "> " + label
}
rows = append(rows, []models.InlineKeyboardButton{
{Text: label, CallbackData: "currency_" + p},
})
}
rows = append(rows, []models.InlineKeyboardButton{
{Text: "Back to Settings", CallbackData: "back_settings"},
})
return "Select currency:", models.InlineKeyboardMarkup{InlineKeyboard: rows}
}
// selectCurrency saves the chosen currency preset.
func (h *Handler) selectCurrency(ctx context.Context, chatID int64, msgID int, callbackID, currency string) {
defer h.answerCb(ctx, callbackID)
user, err := h.getOrCreateUser(chatID)
if err != nil {
return
}
st, err := h.DB.GetOrCreateSettings(user.ID)
if err != nil {
return
}
st.Currency = currency
if err := h.DB.UpdateSettings(st); err != nil {
return
}
bt := h.getTodayBreakThreshold(chatID)
text, kb := h.buildSettingsKeyboard(user, st, bt)
h.editText(ctx, chatID, msgID, text, &kb)
}