fix(economy): improve daily cooldown message and consolidate UserError class

This commit is contained in:
syntaxbullet
2026-01-14 16:26:27 +01:00
parent 4af2690bab
commit 915f1bc4ad
18 changed files with 52 additions and 41 deletions

View File

@@ -0,0 +1,28 @@
### Context & Goal
Enhance the user experience by standardizing the look and feel of Discord embeds. Adding consistent branding like a custom footer (with version info) and using the bot's accent color will make the bot feel more professional.
### Dependencies
- None
### Affected Files
- `bot/lib/embeds.ts`: Update standard embed creators.
- `shared/lib/constants.ts`: Add branding-related constants (colors, footer text).
### Technical Constraints & Strategy
- Implementation: Update `createBaseEmbed` and other helpers to automatically include footers and standard colors.
- Use info from `package.json` for versioning in the footer.
- Ensure the changes don't break existing layouts where custom colors might be needed.
### Definition of Done (Binary)
- [x] All standard embeds now include a consistent footer.
- [x] Embeds use a predefined brand color by default.
- [x] Version number is automatically pulled for the footer.
### New Test Files
- None.

View File

@@ -10,7 +10,7 @@ import {
} from "discord.js";
import { inventoryService } from "@shared/modules/inventory/inventory.service";
import { createSuccessEmbed, createErrorEmbed, createBaseEmbed } from "@lib/embeds";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
import { items } from "@db/schema";
import { ilike, isNotNull, and } from "drizzle-orm";
import { DrizzleClient } from "@shared/db/DrizzleClient";
@@ -65,10 +65,10 @@ export const listing = createCommand({
await interaction.editReply({ content: `✅ Listing for **${item.name}** posted in ${targetChannel}.` });
} catch (error: any) {
if (error instanceof UserError) {
await interaction.reply({ embeds: [createErrorEmbed(error.message)], ephemeral: true });
await interaction.editReply({ embeds: [createErrorEmbed(error.message)] });
} else {
console.error("Error creating listing:", error);
await interaction.reply({ embeds: [createErrorEmbed("An unexpected error occurred.")], ephemeral: true });
await interaction.editReply({ embeds: [createErrorEmbed("An unexpected error occurred.")] });
}
}
},

View File

@@ -3,13 +3,14 @@ import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder } from "discord.js";
import { economyService } from "@shared/modules/economy/economy.service";
import { createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
export const daily = createCommand({
data: new SlashCommandBuilder()
.setName("daily")
.setDescription("Claim your daily reward"),
execute: async (interaction) => {
await interaction.deferReply();
try {
const result = await economyService.claimDaily(interaction.user.id);
@@ -21,14 +22,14 @@ export const daily = createCommand({
)
.setColor("Gold");
await interaction.reply({ embeds: [embed] });
await interaction.editReply({ embeds: [embed] });
} catch (error: any) {
if (error instanceof UserError) {
await interaction.reply({ embeds: [createErrorEmbed(error.message)], ephemeral: true });
await interaction.editReply({ embeds: [createErrorEmbed(error.message)] });
} else {
console.error("Error claiming daily:", error);
await interaction.reply({ embeds: [createErrorEmbed("An unexpected error occurred.")], ephemeral: true });
await interaction.editReply({ embeds: [createErrorEmbed("An unexpected error occurred.")] });
}
}
}

View File

@@ -2,7 +2,7 @@ import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder } from "discord.js";
import { userService } from "@shared/modules/user/user.service";
import { createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
import { userTimers, users } from "@db/schema";
import { eq, and, sql } from "drizzle-orm";
import { DrizzleClient } from "@shared/db/DrizzleClient";
@@ -195,10 +195,10 @@ export const exam = createCommand({
} catch (error: any) {
if (error instanceof UserError) {
await interaction.reply({ embeds: [createErrorEmbed(error.message)], ephemeral: true });
await interaction.editReply({ embeds: [createErrorEmbed(error.message)] });
} else {
console.error("Error in exam command:", error);
await interaction.reply({ embeds: [createErrorEmbed("An unexpected error occurred.")], ephemeral: true });
await interaction.editReply({ embeds: [createErrorEmbed("An unexpected error occurred.")] });
}
}
}

View File

@@ -5,7 +5,7 @@ import { economyService } from "@shared/modules/economy/economy.service";
import { userService } from "@shared/modules/user/user.service";
import { config } from "@shared/lib/config";
import { createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
export const pay = createCommand({
data: new SlashCommandBuilder()

View File

@@ -3,7 +3,7 @@ import { SlashCommandBuilder } from "discord.js";
import { triviaService } from "@shared/modules/trivia/trivia.service";
import { getTriviaQuestionView } from "@/modules/trivia/trivia.view";
import { createErrorEmbed } from "@lib/embeds";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
import { config } from "@shared/lib/config";
import { TriviaCategory } from "@shared/lib/constants";

View File

@@ -5,7 +5,7 @@ import { userService } from "@shared/modules/user/user.service";
import { createErrorEmbed } from "@lib/embeds";
import { getItemUseResultEmbed } from "@/modules/inventory/inventory.view";
import type { ItemUsageData } from "@shared/lib/types";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
import { config } from "@shared/lib/config";
export const use = createCommand({

View File

@@ -1,18 +0,0 @@
export class ApplicationError extends Error {
constructor(message: string) {
super(message);
this.name = this.constructor.name;
}
}
export class UserError extends ApplicationError {
constructor(message: string) {
super(message);
}
}
export class SystemError extends ApplicationError {
constructor(message: string) {
super(message);
}
}

View File

@@ -1,6 +1,6 @@
import { ButtonInteraction, StringSelectMenuInteraction, ModalSubmitInteraction, MessageFlags } from "discord.js";
import { UserError } from "@lib/errors";
import { UserError } from "@shared/lib/errors";
import { createErrorEmbed } from "@lib/embeds";
type ComponentInteraction = ButtonInteraction | StringSelectMenuInteraction | ModalSubmitInteraction;

View File

@@ -1,6 +1,6 @@
import { ButtonInteraction } from "discord.js";
import { lootdropService } from "@shared/modules/economy/lootdrop.service";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
import { getLootdropClaimedMessage } from "./lootdrop.view";
export async function handleLootdropInteraction(interaction: ButtonInteraction) {

View File

@@ -1,7 +1,7 @@
import { ButtonInteraction, MessageFlags } from "discord.js";
import { inventoryService } from "@shared/modules/inventory/inventory.service";
import { userService } from "@shared/modules/user/user.service";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
export async function handleShopInteraction(interaction: ButtonInteraction) {
if (!interaction.customId.startsWith("shop_buy_")) return;

View File

@@ -4,7 +4,7 @@ import { config } from "@shared/lib/config";
import { AuroraClient } from "@/lib/BotClient";
import { buildFeedbackMessage, getFeedbackModal } from "./feedback.view";
import { FEEDBACK_CUSTOM_IDS, type FeedbackType, type FeedbackData } from "./feedback.types";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
export const handleFeedbackInteraction = async (interaction: Interaction) => {
// Handle select menu for choosing feedback type

View File

@@ -10,7 +10,7 @@ import {
import { tradeService } from "@shared/modules/trade/trade.service";
import { inventoryService } from "@shared/modules/inventory/inventory.service";
import { createErrorEmbed, createWarningEmbed, createSuccessEmbed, createInfoEmbed } from "@lib/embeds";
import { UserError } from "@lib/errors";
import { UserError } from "@shared/lib/errors";
import { getTradeDashboard, getTradeMoneyModal, getItemSelectMenu, getTradeCompletedEmbed } from "./trade.view";

View File

@@ -1,7 +1,7 @@
import { ButtonInteraction } from "discord.js";
import { triviaService } from "@shared/modules/trivia/trivia.service";
import { getTriviaResultView, getTriviaTimeoutView } from "./trivia.view";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
export async function handleTriviaInteraction(interaction: ButtonInteraction) {
const parts = interaction.customId.split('_');

View File

@@ -3,7 +3,7 @@ import { config } from "@shared/lib/config";
import { getEnrollmentSuccessMessage } from "./enrollment.view";
import { classService } from "@shared/modules/class/class.service";
import { userService } from "@shared/modules/user/user.service";
import { UserError } from "@/lib/errors";
import { UserError } from "@shared/lib/errors";
import { sendWebhookMessage } from "@/lib/webhookUtils";
export async function handleEnrollmentInteraction(interaction: ButtonInteraction) {

View File

@@ -1,6 +1,6 @@
{
"name": "app",
"version": "1.0.0",
"version": "1.1.3",
"module": "bot/index.ts",
"type": "module",
"private": true,

View File

@@ -88,6 +88,6 @@ export enum TriviaCategory {
export const BRANDING = {
COLOR: 0x00d4ff as const,
FOOTER_TEXT: 'AuroraBot' as const,
FOOTER_TEXT: 'Aurora' as const,
};

View File

@@ -87,7 +87,7 @@ export const economyService = {
});
if (cooldown && cooldown.expiresAt > now) {
throw new UserError(`Daily already claimed today. Next claim <t:${Math.floor(cooldown.expiresAt.getTime() / 1000)}:F>`);
throw new UserError(`You have already claimed your daily reward today.\nNext claim available: <t:${Math.floor(cooldown.expiresAt.getTime() / 1000)}:F> (<t:${Math.floor(cooldown.expiresAt.getTime() / 1000)}:R>)`);
}
// Get user for streak logic