From 234f8c2d69315c626e0bdbff8ebf517a42709caa Mon Sep 17 00:00:00 2001 From: CarelessInternet <59174259+CarelessInternet@users.noreply.github.com> Date: Sat, 9 Mar 2024 22:16:48 +0100 Subject: [PATCH] refactor(thread-ticketing): skip to ticket modal if one category (#377) --- .devcontainer/devcontainer.json | 5 +- .devcontainer/docker-compose.yaml | 8 +- .../staff/configuration-ticket-threads.ts | 2 +- .../thread-ticketing/proxy-ticket-chat.ts | 51 +++-- .../thread-ticketing/proxy-ticket-user.ts | 51 +++-- .../src/commands/thread-ticketing/ticket.ts | 178 ++++++++++-------- .../utils/thread-ticketing/categoryList.ts | 23 ++- apps/bot/src/utils/thread-ticketing/index.ts | 1 + .../src/utils/thread-ticketing/ticketModal.ts | 47 +++++ apps/website/package.json | 2 +- .../website/src/app/docs/development/page.tsx | 1 - .../src/app/docs/self-hosting/page.tsx | 7 +- compose.yaml | 2 + packages/database/package.json | 2 +- .../decorators/RequiredChannelPermissions.ts | 6 +- .../decorators/RequiredGlobalPermissions.ts | 4 +- pnpm-lock.yaml | 25 +-- 17 files changed, 270 insertions(+), 145 deletions(-) create mode 100644 apps/bot/src/utils/thread-ticketing/ticketModal.ts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 50718e9..3bc7c5a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,10 +5,7 @@ // Update the 'dockerComposeFile' list if you have more compose files or use different names. // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. - "dockerComposeFile": [ - "../base-compose.yaml", - "docker-compose.yaml" - ], + "dockerComposeFile": ["docker-compose.yaml"], // The 'service' property is the name of the service for the container that VS Code should // use. Update this value and .devcontainer/docker-compose.yml to the real service name. diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml index 534de26..88d7622 100644 --- a/.devcontainer/docker-compose.yaml +++ b/.devcontainer/docker-compose.yaml @@ -3,6 +3,9 @@ version: '3.8' services: database: container_name: ticketer-development-database + extends: + file: ../base-compose.yaml + service: database networks: - ticketer-development-database-network volumes: @@ -22,9 +25,12 @@ services: # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile" # array). The sample below assumes your primary file is in the root of your project. container_name: ticketer-development-bot + extends: + file: ../base-compose.yaml + service: bot build: context: . - dockerfile: .devcontainer/bot/Dockerfile + dockerfile: ./bot/Dockerfile image: ticketer-bot:development depends_on: database: diff --git a/apps/bot/src/commands/staff/configuration-ticket-threads.ts b/apps/bot/src/commands/staff/configuration-ticket-threads.ts index 660314f..96eb336 100644 --- a/apps/bot/src/commands/staff/configuration-ticket-threads.ts +++ b/apps/bot/src/commands/staff/configuration-ticket-threads.ts @@ -469,7 +469,7 @@ export default class extends Command.Interaction { .delete(ticketThreadsCategories) .where(sql`${ticketThreadsCategories.id} = ${id} RETURNING ${ticketThreadsCategories.categoryTitle}`); - const title = (query[0] as unknown as Pick[]).at( + const title = (query.at(0) as unknown as Pick[]).at( 0, )?.categoryTitle; diff --git a/apps/bot/src/commands/thread-ticketing/proxy-ticket-chat.ts b/apps/bot/src/commands/thread-ticketing/proxy-ticket-chat.ts index 9081577..cd8bff2 100644 --- a/apps/bot/src/commands/thread-ticketing/proxy-ticket-chat.ts +++ b/apps/bot/src/commands/thread-ticketing/proxy-ticket-chat.ts @@ -1,4 +1,4 @@ -import { Command, DeferReply } from '@ticketer/djs-framework'; +import { Command } from '@ticketer/djs-framework'; import { PermissionFlagsBits } from 'discord.js'; import { ThreadTicketing } from '@/utils'; import { translate } from '@/i18n'; @@ -16,29 +16,50 @@ export default class extends Command.Interaction { .setRequired(true), ); - @DeferReply({ ephemeral: true }) public async execute({ interaction }: Command.Context<'chat'>) { const user = interaction.options.getUser('member', true); - const list = await ThreadTicketing.categoryList({ - customId: super.customId('ticket_threads_categories_create_list_proxy', user.id), + const categories = await ThreadTicketing.categoryList({ filterManagerIds: [...interaction.member.roles.cache.keys()], guildId: interaction.guildId, - locale: interaction.locale, }); - if (!list) { + if (categories.length === 0) { const translations = translate(interaction.locale).tickets.threads.categories.createTicket.errors.noCategories; - return interaction.editReply({ - embeds: [ - super - .userEmbedError(interaction.user) - .setTitle(translations.title()) - .setDescription(translations.description()), - ], - }); + return interaction + .reply({ + embeds: [ + super + .userEmbedError(interaction.user) + .setTitle(translations.title()) + .setDescription(translations.description()), + ], + ephemeral: true, + }) + .catch(() => false); } - return interaction.editReply({ components: [list] }); + if (categories.length === 1) { + void interaction.showModal( + ThreadTicketing.ticketModal.call(this, { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + categoryId: categories.at(0)!.id, + locale: interaction.locale, + }), + ); + } else { + return interaction + .reply({ + components: [ + ThreadTicketing.categoryListSelectMenu({ + categories, + customId: super.customId('ticket_threads_categories_create_list_proxy', user.id), + locale: interaction.locale, + }), + ], + ephemeral: true, + }) + .catch(() => false); + } } } diff --git a/apps/bot/src/commands/thread-ticketing/proxy-ticket-user.ts b/apps/bot/src/commands/thread-ticketing/proxy-ticket-user.ts index 1b180c2..585d6f2 100644 --- a/apps/bot/src/commands/thread-ticketing/proxy-ticket-user.ts +++ b/apps/bot/src/commands/thread-ticketing/proxy-ticket-user.ts @@ -1,4 +1,4 @@ -import { Command, DeferReply } from '@ticketer/djs-framework'; +import { Command } from '@ticketer/djs-framework'; import { PermissionFlagsBits } from 'discord.js'; import { ThreadTicketing } from '@/utils'; import { translate } from '@/i18n'; @@ -10,29 +10,50 @@ export default class extends Command.Interaction { PermissionFlagsBits.ManageThreads, ); - @DeferReply({ ephemeral: true }) public async execute({ interaction }: Command.Context<'user'>) { const user = interaction.targetUser; - const list = await ThreadTicketing.categoryList({ - customId: super.customId('ticket_threads_categories_create_list_proxy', user.id), + const categories = await ThreadTicketing.categoryList({ filterManagerIds: [...interaction.member.roles.cache.keys()], guildId: interaction.guildId, - locale: interaction.locale, }); - if (!list) { + if (categories.length === 0) { const translations = translate(interaction.locale).tickets.threads.categories.createTicket.errors.noCategories; - return interaction.editReply({ - embeds: [ - super - .userEmbedError(interaction.user) - .setTitle(translations.title()) - .setDescription(translations.description()), - ], - }); + return interaction + .reply({ + embeds: [ + super + .userEmbedError(interaction.user) + .setTitle(translations.title()) + .setDescription(translations.description()), + ], + ephemeral: true, + }) + .catch(() => false); } - return interaction.editReply({ components: [list] }); + if (categories.length === 1) { + void interaction.showModal( + ThreadTicketing.ticketModal.call(this, { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + categoryId: categories.at(0)!.id, + locale: interaction.locale, + }), + ); + } else { + return interaction + .reply({ + components: [ + ThreadTicketing.categoryListSelectMenu({ + categories, + customId: super.customId('ticket_threads_categories_create_list_proxy', user.id), + locale: interaction.locale, + }), + ], + ephemeral: true, + }) + .catch(() => false); + } } } diff --git a/apps/bot/src/commands/thread-ticketing/ticket.ts b/apps/bot/src/commands/thread-ticketing/ticket.ts index a2dd394..708b132 100644 --- a/apps/bot/src/commands/thread-ticketing/ticket.ts +++ b/apps/bot/src/commands/thread-ticketing/ticket.ts @@ -1,19 +1,15 @@ import { - ActionRowBuilder, ChannelType, Colors, MessageFlags, MessageType, - ModalBuilder, PermissionFlagsBits, type Snowflake, - TextInputBuilder, - TextInputStyle, ThreadAutoArchiveDuration, inlineCode, roleMention, } from 'discord.js'; -import { Command, Component, DeferReply, DeferUpdate, Modal } from '@ticketer/djs-framework'; +import { Command, Component, DeferReply, Modal } from '@ticketer/djs-framework'; import { ThreadTicketing, parseInteger, ticketButtons, ticketThreadsOpeningMessageEmbed } from '@/utils'; import { and, @@ -34,34 +30,53 @@ export default class extends Command.Interaction { .setDescription(dataTranslations.description()) .setDescriptionLocalizations(getTranslations('commands.ticket.data.description')); - @DeferReply({ ephemeral: true }) public async execute({ interaction }: Command.Context) { - const list = await ThreadTicketing.categoryList({ - customId: super.customId('ticket_threads_categories_create_list_command'), - guildId: interaction.guildId, - locale: interaction.locale, - }); + const categories = await ThreadTicketing.categoryList({ guildId: interaction.guildId }); - if (!list) { + if (categories.length === 0) { const translations = translate(interaction.locale).tickets.threads.categories.createTicket.errors.noCategories; - return interaction.editReply({ - embeds: [ - super - .userEmbedError(interaction.user) - .setTitle(translations.title()) - .setDescription(translations.description()), - ], - }); + return interaction + .reply({ + embeds: [ + super + .userEmbedError(interaction.user) + .setTitle(translations.title()) + .setDescription(translations.description()), + ], + ephemeral: true, + }) + .catch(() => false); } - return interaction.editReply({ components: [list] }); + if (categories.length === 1) { + void interaction.showModal( + ThreadTicketing.ticketModal.call(this, { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + categoryId: categories.at(0)!.id, + locale: interaction.locale, + }), + ); + } else { + return interaction + .reply({ + components: [ + ThreadTicketing.categoryListSelectMenu({ + categories, + customId: super.customId('ticket_threads_categories_create_list'), + locale: interaction.locale, + }), + ], + ephemeral: true, + }) + .catch(() => false); + } } } export class ComponentInteraction extends Component.Interaction { public readonly customIds = [ - super.customId('ticket_threads_categories_create_list_command'), + super.customId('ticket_threads_categories_create_list'), super.dynamicCustomId('ticket_threads_categories_create_list_proxy'), super.customId('ticket_threads_categories_create_list_panel'), super.customId('ticket_threads_category_create_rename_title'), @@ -75,15 +90,14 @@ export class ComponentInteraction extends Component.Interaction { const { customId, dynamicValue } = super.extractCustomId(context.interaction.customId); switch (customId) { - case super.customId('ticket_threads_categories_create_list_command'): + case super.customId('ticket_threads_categories_create_list'): case super.dynamicCustomId('ticket_threads_categories_create_list_proxy'): { - return ( - context.interaction.isStringSelectMenu() && - this.ticketModal({ interaction: context.interaction }, dynamicValue) - ); + context.interaction.isStringSelectMenu() && + this.ticketModal({ interaction: context.interaction }, dynamicValue); + return; } case super.customId('ticket_threads_categories_create_list_panel'): { - return context.interaction.isButton() && this.panelTicketModal(context); + return context.interaction.isButton() && this.panelTicket(context); } case super.customId('ticket_threads_category_create_rename_title'): { void ThreadTicketing.renameTitleModal.call(this, context); @@ -120,59 +134,57 @@ export class ComponentInteraction extends Component.Interaction { } private ticketModal({ interaction }: Component.Context<'string'>, userId?: Snowflake) { - const translations = translate(interaction.locale).tickets.threads.categories.createModal; - const id = interaction.values.at(0); - - const titleInput = new TextInputBuilder() - .setCustomId(super.customId('title')) - .setLabel(translations.title.label()) - .setRequired(true) - .setMinLength(1) - .setMaxLength(100) - .setStyle(TextInputStyle.Short) - .setPlaceholder(translations.title.placeholder()); - const descriptonInput = new TextInputBuilder() - .setCustomId(super.customId('description')) - .setLabel(translations.description.label()) - .setRequired(true) - .setMinLength(1) - .setMaxLength(2000) - .setStyle(TextInputStyle.Paragraph) - .setPlaceholder(translations.description.placeholder()); - - const titleRow = new ActionRowBuilder().setComponents(titleInput); - const descriptionRow = new ActionRowBuilder().setComponents(descriptonInput); - - const modal = new ModalBuilder() - .setCustomId(super.customId('ticket_threads_categories_create_ticket', userId ? `${id}_${userId}` : id)) - .setTitle(translations.modalTitle()) - .setComponents(titleRow, descriptionRow); - - return interaction.showModal(modal); + void interaction.showModal( + ThreadTicketing.ticketModal.call(this, { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + categoryId: interaction.values.at(0)!, + locale: interaction.locale, + userId, + }), + ); } - @DeferReply({ ephemeral: true }) - private async panelTicketModal({ interaction }: Component.Context) { - const list = await ThreadTicketing.categoryList({ - customId: super.customId('ticket_threads_categories_create_list_command'), - guildId: interaction.guildId, - locale: interaction.locale, - }); + private async panelTicket({ interaction }: Component.Context) { + const categories = await ThreadTicketing.categoryList({ guildId: interaction.guildId }); - if (!list) { + if (categories.length === 0) { const translations = translate(interaction.locale).tickets.threads.categories.createTicket.errors.noCategories; - return interaction.editReply({ - embeds: [ - super - .userEmbedError(interaction.user) - .setTitle(translations.title()) - .setDescription(translations.description()), - ], - }); + return interaction + .reply({ + embeds: [ + super + .userEmbedError(interaction.user) + .setTitle(translations.title()) + .setDescription(translations.description()), + ], + ephemeral: true, + }) + .catch(() => false); } - return interaction.editReply({ components: [list] }); + if (categories.length === 1) { + void interaction.showModal( + ThreadTicketing.ticketModal.call(this, { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + categoryId: categories.at(0)!.id, + locale: interaction.locale, + }), + ); + } else { + return interaction + .reply({ + components: [ + ThreadTicketing.categoryListSelectMenu({ + categories, + customId: super.customId('ticket_threads_categories_create_list'), + locale: interaction.locale, + }), + ], + ephemeral: true, + }) + .catch(() => false); + } } @DeferReply({ ephemeral: true }) @@ -224,8 +236,24 @@ export class ModalInteraction extends Modal.Interaction { } } - @DeferUpdate private async ticketCreation({ interaction }: Modal.Context) { + // If the interaction has been replied to or the interaction message has the category select menu, + // then defer the update instead of deferring the reply. + interaction.replied || + interaction.message?.components.find((row) => + row.components.find((component) => { + if (!component.customId) return false; + + const { customId } = super.extractCustomId(component.customId); + return ( + customId === super.customId('ticket_threads_categories_create_list') || + customId === super.dynamicCustomId('ticket_threads_categories_create_list_proxy') + ); + }), + ) + ? await interaction.deferUpdate() + : await interaction.deferReply({ ephemeral: true }); + const { client, customId, fields, guild, guildId, guildLocale, locale, user: interactionUser } = interaction; const { dynamicValue } = super.extractCustomId(customId, true); const dynamicValues = dynamicValue.split('_'); diff --git a/apps/bot/src/utils/thread-ticketing/categoryList.ts b/apps/bot/src/utils/thread-ticketing/categoryList.ts index 265b5ff..83c2f33 100644 --- a/apps/bot/src/utils/thread-ticketing/categoryList.ts +++ b/apps/bot/src/utils/thread-ticketing/categoryList.ts @@ -10,27 +10,30 @@ import type { BaseInteraction } from '@ticketer/djs-framework'; import { translate } from '@/i18n'; interface CategoryListOptions { - customId: BaseInteraction.CustomIds[number]; - guildId: Snowflake; - locale: Locale; filterManagerIds?: Snowflake[]; + guildId: Snowflake; } -export async function categoryList({ customId, filterManagerIds, locale, guildId }: CategoryListOptions) { +export async function categoryList({ filterManagerIds, guildId }: CategoryListOptions) { const rawCategories = await database .select() .from(ticketThreadsCategories) .where(eq(ticketThreadsCategories.guildId, guildId)) - // Limit to 25 because that's the maximum amount of select menu options. + // Limited at 25 because that iss the maximum amount of select menu options. .limit(25); - const categories = - filterManagerIds && filterManagerIds.length > 0 - ? rawCategories.filter((category) => category.managers.some((id) => filterManagerIds.includes(id))) - : rawCategories; + return filterManagerIds && filterManagerIds.length > 0 + ? rawCategories.filter((category) => category.managers.some((id) => filterManagerIds.includes(id))) + : rawCategories; +} - if (categories.length <= 0) return; +interface CategoryListSelectMenuOptions { + categories: Awaited>; + customId: BaseInteraction.CustomIds[number]; + locale: Locale; +} +export function categoryListSelectMenu({ categories, customId, locale }: CategoryListSelectMenuOptions) { const translations = translate(locale).tickets.threads.categories.categoryList; const options = categories.map((category) => (category.categoryEmoji diff --git a/apps/bot/src/utils/thread-ticketing/index.ts b/apps/bot/src/utils/thread-ticketing/index.ts index 430e2e1..93beb5f 100644 --- a/apps/bot/src/utils/thread-ticketing/index.ts +++ b/apps/bot/src/utils/thread-ticketing/index.ts @@ -3,6 +3,7 @@ export * from './closeTicket'; export * from './deleteTicket'; export * from './lockTicket'; export * from './renameTitleModal'; +export * from './ticketModal'; export * from './ticketState'; export * from './titleAndEmoji'; export * from './viewUserTickets'; diff --git a/apps/bot/src/utils/thread-ticketing/ticketModal.ts b/apps/bot/src/utils/thread-ticketing/ticketModal.ts new file mode 100644 index 0000000..c170153 --- /dev/null +++ b/apps/bot/src/utils/thread-ticketing/ticketModal.ts @@ -0,0 +1,47 @@ +import { + ActionRowBuilder, + type Locale, + ModalBuilder, + type Snowflake, + TextInputBuilder, + TextInputStyle, +} from 'discord.js'; +import type { BaseInteraction } from '@ticketer/djs-framework'; +import type { ticketThreadsCategories } from '@ticketer/database'; +import { translate } from '@/i18n'; + +interface TicketModalOptions { + categoryId: typeof ticketThreadsCategories.$inferSelect.id | string; + locale: Locale; + userId?: Snowflake; +} + +export function ticketModal(this: BaseInteraction.Interaction, { categoryId, locale, userId }: TicketModalOptions) { + const translations = translate(locale).tickets.threads.categories.createModal; + const titleInput = new TextInputBuilder() + .setCustomId(this.customId('title')) + .setLabel(translations.title.label()) + .setRequired(true) + .setMinLength(1) + .setMaxLength(100) + .setStyle(TextInputStyle.Short) + .setPlaceholder(translations.title.placeholder()); + const descriptonInput = new TextInputBuilder() + .setCustomId(this.customId('description')) + .setLabel(translations.description.label()) + .setRequired(true) + .setMinLength(1) + .setMaxLength(2000) + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder(translations.description.placeholder()); + + const titleRow = new ActionRowBuilder().setComponents(titleInput); + const descriptionRow = new ActionRowBuilder().setComponents(descriptonInput); + + return new ModalBuilder() + .setCustomId( + this.customId('ticket_threads_categories_create_ticket', userId ? `${categoryId}_${userId}` : categoryId), + ) + .setTitle(translations.modalTitle()) + .setComponents(titleRow, descriptionRow); +} diff --git a/apps/website/package.json b/apps/website/package.json index 93fa227..4ca8689 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -18,7 +18,7 @@ "@vercel/analytics": "^1.2.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", - "lucide-react": "^0.350.0", + "lucide-react": "^0.354.0", "next": "14.1.3", "next-themes": "^0.2.1", "react": "^18.2.0", diff --git a/apps/website/src/app/docs/development/page.tsx b/apps/website/src/app/docs/development/page.tsx index 81f9c2b..f44c584 100644 --- a/apps/website/src/app/docs/development/page.tsx +++ b/apps/website/src/app/docs/development/page.tsx @@ -17,7 +17,6 @@ export default function Page() { The software which you will need installed is{' '} - Git and{' '} Docker Desktop. Docker Engine should also work fine if you do not want to use Docker Desktop. diff --git a/apps/website/src/app/docs/self-hosting/page.tsx b/apps/website/src/app/docs/self-hosting/page.tsx index c51cac3..f73283e 100644 --- a/apps/website/src/app/docs/self-hosting/page.tsx +++ b/apps/website/src/app/docs/self-hosting/page.tsx @@ -117,13 +117,10 @@ export default function Page() { Now it is time to run the bot! Run the following command to start the database and bot (this may take some time): - + docker - - compose --env-file ./.env.database.production.local --env-file ./.env.bot.production.local -f compose.yaml - up -d - + compose --env-file ./.env.database.production.local -f compose.yaml up -d To deploy the application commands of the bot, run the following line: diff --git a/compose.yaml b/compose.yaml index f067d40..6a8076b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,4 +1,6 @@ version: '3.8' +# https://stackoverflow.com/a/73715984 +name: 'ticketer' # Comments are provided throughout this file to help you get started. # If you need more help, visit the Docker compose reference guide at diff --git a/packages/database/package.json b/packages/database/package.json index 1573c01..48426ab 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@ticketer/env": "workspace:*", - "drizzle-orm": "^0.30.0", + "drizzle-orm": "^0.30.1", "mysql2": "^3.9.2" }, "devDependencies": { diff --git a/packages/djs-framework/src/decorators/RequiredChannelPermissions.ts b/packages/djs-framework/src/decorators/RequiredChannelPermissions.ts index 933debc..f5645bf 100644 --- a/packages/djs-framework/src/decorators/RequiredChannelPermissions.ts +++ b/packages/djs-framework/src/decorators/RequiredChannelPermissions.ts @@ -6,11 +6,9 @@ export function RequiredChannelPermissions(...permissions: PermissionFlagsValues const original = descriptor.value as () => void; descriptor.value = async function (this: BaseInteraction.Interaction, { interaction }: BaseInteraction.Context) { - if (!interaction.isRepliable() || !interaction.channel) return; + if (!interaction.isRepliable()) return; - const me = await interaction.guild.members.fetchMe(); - - if (!interaction.channel.permissionsFor(me).has(permissions)) { + if (!interaction.appPermissions.has(permissions)) { const allPermissions = permissions.map((permission) => getPermissionByValue(permission)); // Changes the PascalCase strings to Split Pascal Case for better user readability. const permissionsAsString = allPermissions diff --git a/packages/djs-framework/src/decorators/RequiredGlobalPermissions.ts b/packages/djs-framework/src/decorators/RequiredGlobalPermissions.ts index 192d2ff..2f50202 100644 --- a/packages/djs-framework/src/decorators/RequiredGlobalPermissions.ts +++ b/packages/djs-framework/src/decorators/RequiredGlobalPermissions.ts @@ -8,7 +8,9 @@ export function RequiredGlobalPermissions(...permissions: PermissionFlagsValues[ descriptor.value = async function (this: BaseInteraction.Interaction, { interaction }: BaseInteraction.Context) { if (!interaction.isRepliable()) return; - if (!interaction.appPermissions?.has(permissions)) { + const me = await interaction.guild.members.fetchMe(); + + if (!me.permissions.has(permissions)) { const allPermissions = permissions.map((permission) => getPermissionByValue(permission)); // Changes the PascalCase strings to Split Pascal Case for better user readability. const permissionsAsString = allPermissions diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4dcb05a..063985f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -82,8 +82,8 @@ importers: specifier: ^2.1.0 version: 2.1.0 lucide-react: - specifier: ^0.350.0 - version: 0.350.0(react@18.2.0) + specifier: ^0.354.0 + version: 0.354.0(react@18.2.0) next: specifier: 14.1.3 version: 14.1.3(react-dom@18.2.0)(react@18.2.0) @@ -140,8 +140,8 @@ importers: specifier: workspace:* version: link:../env drizzle-orm: - specifier: ^0.30.0 - version: 0.30.0(mysql2@3.9.2) + specifier: ^0.30.1 + version: 0.30.1(mysql2@3.9.2) mysql2: specifier: ^3.9.2 version: 3.9.2 @@ -2177,7 +2177,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001596 - electron-to-chromium: 1.4.696 + electron-to-chromium: 1.4.699 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) dev: true @@ -2529,13 +2529,14 @@ packages: - supports-color dev: true - /drizzle-orm@0.30.0(mysql2@3.9.2): - resolution: {integrity: sha512-SupNouCdvosjzQpLinqnyIu7bBfsUKiaWXfUZ0IyDtwK8rWa2zQ4AOWCda86VJVxgb8xZ1l8vKvlLHyTBalB/g==} + /drizzle-orm@0.30.1(mysql2@3.9.2): + resolution: {integrity: sha512-5P6CXl4XyWtDDiYOX/jYOJp1HTUmBlXRAwaq+muUOgaSykMEy5sJesCxceMT0oCGvxeWkKfSXo5owLnfKwCIdw==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' '@libsql/client': '*' '@neondatabase/serverless': '>=0.1' + '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 '@planetscale/database': '>=1' '@types/better-sqlite3': '*' @@ -2563,6 +2564,8 @@ packages: optional: true '@neondatabase/serverless': optional: true + '@op-engineering/op-sqlite': + optional: true '@opentelemetry/api': optional: true '@planetscale/database': @@ -2606,8 +2609,8 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - /electron-to-chromium@1.4.696: - resolution: {integrity: sha512-SOr0bHP52OvYg2chCsz/0+FUSMGFm8L8HKwPpx3cbwRY24EOemVJtbgTm+IFO8LzhcnPy+hXmTq7ZcZ8uUuaYg==} + /electron-to-chromium@1.4.699: + resolution: {integrity: sha512-I7q3BbQi6e4tJJN5CRcyvxhK0iJb34TV8eJQcgh+fR2fQ8miMgZcEInckCo1U9exDHbfz7DLDnFn8oqH/VcRKw==} dev: true /emoji-regex@8.0.0: @@ -4012,8 +4015,8 @@ packages: es5-ext: 0.10.64 dev: true - /lucide-react@0.350.0(react@18.2.0): - resolution: {integrity: sha512-5IZVKsxxG8Nn81gpsz4XLNgCAXkppCh0Y0P0GLO39h5iVD2WEaB9of6cPkLtzys1GuSfxJxmwsDh487y7LAf/g==} + /lucide-react@0.354.0(react@18.2.0): + resolution: {integrity: sha512-qI0x/hhcuHieaUUxFejesm5T/ditszQ2x1gUkqKnTMZRAOdxT2nGzbOFYrhc0wRjuA2MN1o5JCrcRPYcHH3l8A==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 dependencies: