From 0bcc13e0cc1549d19dd8ac00745cd31b075b5799 Mon Sep 17 00:00:00 2001 From: alfianchii Date: Sun, 21 Dec 2025 13:14:20 +0700 Subject: [PATCH 1/4] feat: checkin detail button --- .../checkin/validators/index.ts | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/bot/events/interaction-create/checkin/validators/index.ts b/src/bot/events/interaction-create/checkin/validators/index.ts index b0c56ed..060fc28 100644 --- a/src/bot/events/interaction-create/checkin/validators/index.ts +++ b/src/bot/events/interaction-create/checkin/validators/index.ts @@ -18,6 +18,7 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, messageLink, PermissionsB import { CHECKIN_APPROVE_BUTTON_ID } from '../handlers/approve-button' import { CHECKIN_CUSTOM_BUTTON_ID } from '../handlers/custom-button' import { CheckinCustomButtonModalError } from '../handlers/custom-button-modal' +import { CHECKIN_DETAIL_BUTTON_ID } from '../handlers/detail-button' import { CheckinModalError } from '../handlers/modal' import { CHECKIN_REJECT_BUTTON_ID } from '../handlers/reject-button' import { CheckinMessage } from '../messages' @@ -100,6 +101,12 @@ export class Checkin extends CheckinMessage { } static generateButtons(guildId: string, checkinId: string): ActionRowBuilder { + const detailButtonId = getCustomId([CHECKIN_DETAIL_BUTTON_ID, encodeSnowflake(guildId), encodeSnowflake(checkinId)]) + const detailButton = new ButtonBuilder() + .setCustomId(detailButtonId) + .setLabel('πŸ” Detail') + .setStyle(ButtonStyle.Primary) + const approveButtonId = getCustomId([CHECKIN_APPROVE_BUTTON_ID, encodeSnowflake(guildId), encodeSnowflake(checkinId)]) const approveButton = new ButtonBuilder() .setCustomId(approveButtonId) @@ -116,9 +123,9 @@ export class Checkin extends CheckinMessage { const customButton = new ButtonBuilder() .setCustomId(customButtonId) .setLabel('βš™οΈ Review') - .setStyle(ButtonStyle.Primary) + .setStyle(ButtonStyle.Secondary) - return new ActionRowBuilder().addComponents(approveButton, rejectButton, customButton) + return new ActionRowBuilder().addComponents(detailButton, approveButton, rejectButton, customButton) } static getNewGrindRole(guild: Guild, streakCount: number) { @@ -323,6 +330,26 @@ export class Checkin extends CheckinMessage { }) as CheckinType } + static async getCheckin( + prisma: PrismaClient, + checkinId: number, + ): Promise { + const checkin = await prisma.checkin.findUnique({ + where: { id: checkinId }, + include: { + checkin_streak: true, + user: true, + }, + }) as CheckinType + + if (!checkin) + throw new SubmittedCheckinError(this.ERR.PlainMessage) + + await this.setAttachments(prisma, checkin) + + return checkin + } + static async createAttachments( prisma: PrismaClient, checkin: CheckinType, @@ -368,13 +395,8 @@ export class Checkin extends CheckinMessage { return prisma.$transaction(async (tx) => { const checkinStreak = await this.upsertStreak(tx, userId, lastCheckinStreak, decision) const checkin = await this.createCheckin(tx, userId, checkinStreak, description) - const prevCheckin = await this.getPrevCheckin(tx, userId, checkinStreak, checkin) - return { - checkinStreak, - checkin, - prevCheckin, - } + return { checkinStreak, checkin } }) } From 6b760c22b1d421e3d34a2710cde6caa12be3132e Mon Sep 17 00:00:00 2001 From: alfianchii Date: Sun, 21 Dec 2025 13:16:05 +0700 Subject: [PATCH 2/4] feat: checkin detail button handler --- .../checkin/handlers/detail-button.ts | 50 +++++++++++++++++++ .../checkin/handlers/modal.ts | 15 +----- .../checkin/messages/index.ts | 19 ++++--- 3 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 src/bot/events/interaction-create/checkin/handlers/detail-button.ts diff --git a/src/bot/events/interaction-create/checkin/handlers/detail-button.ts b/src/bot/events/interaction-create/checkin/handlers/detail-button.ts new file mode 100644 index 0000000..33dbb72 --- /dev/null +++ b/src/bot/events/interaction-create/checkin/handlers/detail-button.ts @@ -0,0 +1,50 @@ +import type { GuildMember, TextChannel } from 'discord.js' +import { EVENT_PATH } from '@events/index' +import { registerInteractionHandler } from '@events/interaction-create/registry' +import { generateCustomId } from '@utils/component' +import { sendReply } from '@utils/discord' +import { DiscordBaseError } from '@utils/discord/error' +import { getModuleName } from '@utils/io' +import { Checkin } from '../validators' + +export class CheckinDetailButtonError extends DiscordBaseError { + constructor(message: string, options?: { cause?: unknown }) { + super('CheckinDetailButtonError', message, options) + } +} + +const moduleName = getModuleName(EVENT_PATH, __filename) +export const CHECKIN_DETAIL_BUTTON_ID = `${generateCustomId(EVENT_PATH, __filename)}` + +registerInteractionHandler({ + desc: 'Handles displaying more details for a user check-in when the detail button is pressed.', + id: CHECKIN_DETAIL_BUTTON_ID, + errorTag: () => `${moduleName}: ${Checkin.ERR.UnexpectedButton}`, + async exec(client, interaction) { + if (!interaction.isButton()) + return + + try { + if (!interaction.inCachedGuild()) + throw new CheckinDetailButtonError(Checkin.ERR.NotGuild) + + const { checkinId } = Checkin.getButtonId(interaction, interaction.customId) + + const channel = interaction.channel as TextChannel + const member = interaction.member as GuildMember + Checkin.assertMissPerms(interaction.client.user, channel) + Checkin.assertMember(member) + Checkin.assertMemberGrindRoles(member) + + const checkin = await Checkin.getCheckin(client.prisma, checkinId) + const prevCheckin = await Checkin.getPrevCheckin(client.prisma, checkin.user!.id, checkin.checkin_streak!, checkin) + + await sendReply(interaction, Checkin.MSG.GrinderDetails(member, checkin, checkin.checkin_streak!.streak, prevCheckin)) + } + catch (err: any) { + if (err instanceof DiscordBaseError) + await sendReply(interaction, err.message) + else throw err + } + }, +}) diff --git a/src/bot/events/interaction-create/checkin/handlers/modal.ts b/src/bot/events/interaction-create/checkin/handlers/modal.ts index 2b1cb4f..aae08ed 100644 --- a/src/bot/events/interaction-create/checkin/handlers/modal.ts +++ b/src/bot/events/interaction-create/checkin/handlers/modal.ts @@ -42,23 +42,12 @@ registerInteractionHandler({ Checkin.assertMemberGrindRoles(member) Checkin.assertCheckinToday(user) - const { - checkinStreak, - checkin, - prevCheckin, - } = await Checkin.validateCheckinStreak(client.prisma, user.id, user.checkin_streaks?.[0], todo) - + const { checkin } = await Checkin.validateCheckinStreak(client.prisma, user.id, user.checkin_streaks?.[0], todo) const buttons = Checkin.generateButtons(interaction.guildId, checkin.id.toString()) const msg = await sendReply( interaction, - Checkin.MSG.CheckinSuccess( - member, - attachments, - checkinStreak.streak, - todo, - prevCheckin, - ), + Checkin.MSG.CheckinSuccess(todo), false, { files: attachments.length ? attachments : undefined, diff --git a/src/bot/events/interaction-create/checkin/messages/index.ts b/src/bot/events/interaction-create/checkin/messages/index.ts index 76263fe..d454ab8 100644 --- a/src/bot/events/interaction-create/checkin/messages/index.ts +++ b/src/bot/events/interaction-create/checkin/messages/index.ts @@ -1,5 +1,5 @@ import type { Checkin } from '@type/checkin' -import type { Attachment, GuildMember } from 'discord.js' +import type { GuildMember } from 'discord.js' import { FLAMEWARDEN_ROLE } from '@config/discord' import { getNow, getParsedNow } from '@utils/date' import { DiscordAssert } from '@utils/discord' @@ -17,21 +17,24 @@ export class CheckinMessage extends DiscordAssert { static override readonly MSG = { ...DiscordAssert.MSG, - CheckinSuccess: (member: GuildMember, checkinAttachments: Attachment[], streakCount: number, todo: string, lastCheckin?: Checkin) => ` + CheckinSuccess: (todo: string) => ` # βœ… Check-In Baru Terdeteksi! *Kindly take a look and do a review for this one, <@&${FLAMEWARDEN_ROLE}>* -βœ¨β”€β”€β”€β”€β”€βœ¨/βœ¨β”β”β”β”βœ¨ -🌟 **Grinder:** <@${member.id}> -πŸ“ **Attachment:** ${checkinAttachments.length > 0 ? 'βœ…' : '❌'} -πŸ•“ **Date:** ${getParsedNow()} -πŸ”₯ **Current Streak:** ${streakCount} day(s) -πŸ—“ **Last Check-In:** ${lastCheckin ? getParsedNow(getNow(lastCheckin.created_at)) : '-'} β‹†ο½‘Λš ☁︎ Λšο½‘β‹†ο½‘Λšβ˜½Λšο½‘β‹† ${todo} > ${DUMMY.FOOTER}`, + GrinderDetails: (member: GuildMember, checkin: Checkin, streakCount: number, lastCheckin?: Checkin) => ` +βœ¨β”€β”€β”€β”€β”€βœ¨/βœ¨β”β”β”β”βœ¨ +🌟 **Grinder:** <@${member.id}> +πŸ“ **Attachment:** ${checkin.attachments && checkin.attachments.length > 0 ? 'βœ…' : '❌'} +πŸ•“ **Date:** ${getParsedNow()} +πŸ”₯ **Current Streak:** ${streakCount} day(s) +πŸ—“ **Last Check-In:** ${lastCheckin ? `[${getParsedNow(getNow(lastCheckin.created_at))}](${lastCheckin.link})` : '-'} + `, + CheckinSuccessToMember: (checkin: Checkin) => ` Sebuah [check-in](${checkin.link}) baru telah Tuan/Nona serahkan dan kini menunggu pemeriksaan dari Flamewarden. πŸ†” **Check-In ID**: From a1a7ab793da920868e86a2d5b7f888fc4123f500 Mon Sep 17 00:00:00 2001 From: alfianchii Date: Sun, 21 Dec 2025 13:17:20 +0700 Subject: [PATCH 3/4] chore: remove unused update checkin message --- .../checkin/validators/index.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/bot/events/interaction-create/checkin/validators/index.ts b/src/bot/events/interaction-create/checkin/validators/index.ts index 060fc28..68eca66 100644 --- a/src/bot/events/interaction-create/checkin/validators/index.ts +++ b/src/bot/events/interaction-create/checkin/validators/index.ts @@ -414,7 +414,7 @@ export class Checkin extends CheckinMessage { const updatedCheckin = await this.updateCheckinStatus(prisma, flamewarden, checkin, checkinStatus, comment) as CheckinType await this.validateCheckinHandleToUser(guild, flamewarden, checkin.user!.discord_id, updatedCheckin) - await this.validateCheckinHandleSubmittedMsg(message, updatedCheckin, checkinStatus) + await message.react(this.REVERSED_EMOJI_STATUS[checkinStatus]) } static async validateCheckinHandleToUser(guild: Guild, flamewarden: GuildMember, userDiscordId: string, updatedCheckin: CheckinType) { @@ -425,11 +425,6 @@ export class Checkin extends CheckinMessage { await this.sendCheckinStatusToMember(flamewarden, member, updatedCheckin) } - static async validateCheckinHandleSubmittedMsg(message: Message, updatedCheckin: CheckinType, checkinStatus: CheckinStatusType) { - await this.updateSubmittedCheckin(message, updatedCheckin.checkin_streak!.streak) - await message.react(this.REVERSED_EMOJI_STATUS[checkinStatus]) - } - static async updateCheckinMsgLink(interaction: Interaction, prisma: PrismaClient, checkin: CheckinType, msg: Message): Promise { const msgLink = messageLink(interaction.channelId!, msg.id, interaction.guildId!) @@ -513,13 +508,4 @@ export class Checkin extends CheckinMessage { await member.send({ embeds: [embed] }) } - - static async updateSubmittedCheckin(message: Message, newStreak: number) { - await message.edit( - message.content.replace( - /πŸ”₯\s*\*\*Current Streak:\*\*\s*\d+\s*day\(s\)/i, - `πŸ”₯ **Current Streak:** ${newStreak} day(s)`, - ), - ) - } } From 1e2c25ac6734e019394752854a3a169779e8cdb4 Mon Sep 17 00:00:00 2001 From: alfianchii Date: Sun, 21 Dec 2025 13:18:49 +0700 Subject: [PATCH 4/4] chore: typography --- src/utils/component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/component.ts b/src/utils/component.ts index c137c18..cebe097 100644 --- a/src/utils/component.ts +++ b/src/utils/component.ts @@ -142,7 +142,7 @@ ${checkin.public_id} πŸ”₯ **Current Streak**: ${checkin.checkin_streak!.streak} day(s) ## Notulen Grinder ${checkin.description} -βœ°β‹†ο½‘:゚ο½₯*☽:゚ο½₯β‹†ο½‘βœ°β‹†ο½‘:゚`)) +β‹†ο½‘Λš ☁︎ Λšο½‘β‹†ο½‘Λšβ˜½Λšο½‘β‹†`)) .addTextDisplayComponents(textDisplay => textDisplay.setContent(DUMMY.MARKDOWN)) return modal