diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index f72ac2855..d4ebb0212 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -76,6 +76,24 @@ "Failed to import pages": "Failed to import pages", "Failed to load page. An error occurred.": "Failed to load page. An error occurred.", "Failed to update data": "Failed to update data", + "Failed to create base": "Failed to create base", + "Failed to update base": "Failed to update base", + "Failed to delete base": "Failed to delete base", + "Failed to create property": "Failed to create property", + "Failed to update property": "Failed to update property", + "Failed to delete property": "Failed to delete property", + "Failed to reorder property": "Failed to reorder property", + "Failed to create view": "Failed to create view", + "Failed to update view": "Failed to update view", + "Failed to delete view": "Failed to delete view", + "Failed to create row": "Failed to create row", + "Failed to update row": "Failed to update row", + "Failed to delete row": "Failed to delete row", + "Failed to delete rows": "Failed to delete rows", + "Failed to reorder row": "Failed to reorder row", + "Failed to move card": "Failed to move card", + "Failed to add card": "Failed to add card", + "Failed to export CSV": "Failed to export CSV", "Favorite spaces": "Favorite spaces", "Favorite spaces appear here": "Favorite spaces appear here", "Favorites": "Favorites", diff --git a/apps/client/src/ee/base/components/base-toolbar.tsx b/apps/client/src/ee/base/components/base-toolbar.tsx index 578eaa574..adde1cdcd 100644 --- a/apps/client/src/ee/base/components/base-toolbar.tsx +++ b/apps/client/src/ee/base/components/base-toolbar.tsx @@ -20,6 +20,7 @@ import { FilterGroup, } from "@/ee/base/types/base.types"; import { exportBaseToCsv } from "@/ee/base/services/base-service"; +import { getApiErrorMessage } from "@/lib/api-error"; import { useBaseEditable } from "@/ee/base/context/base-editable"; import { ViewTabs } from "@/ee/base/components/views/view-tabs"; import { ViewSortConfigPopover } from "@/ee/base/components/views/view-sort-config"; @@ -78,7 +79,7 @@ export function BaseToolbar({ } catch (err) { notifications.show({ color: "red", - message: t("Failed to export CSV"), + message: getApiErrorMessage(err, t("Failed to export CSV")), }); } finally { setExporting(false); diff --git a/apps/client/src/ee/base/components/kanban/kanban-card-properties.tsx b/apps/client/src/ee/base/components/kanban/kanban-card-properties.tsx index cd3021f93..c52bd57c1 100644 --- a/apps/client/src/ee/base/components/kanban/kanban-card-properties.tsx +++ b/apps/client/src/ee/base/components/kanban/kanban-card-properties.tsx @@ -4,7 +4,7 @@ import { IconGripVertical, type IconLetterT } from "@tabler/icons-react"; import { useTranslation } from "react-i18next"; import { IBase, IBaseProperty, IBaseView } from "@/ee/base/types/base.types"; import { useUpdateViewMutation } from "@/ee/base/queries/base-view-query"; -import { propertyTypes } from "@/ee/base/components/property/property-type-picker"; +import { propertyTypes } from "@/ee/base/property-types/property-type.registry"; import { BaseDropEdgeIndicator } from "@/ee/base/components/grid/base-drop-edge-indicator"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { diff --git a/apps/client/src/ee/base/components/property/create-property-popover.tsx b/apps/client/src/ee/base/components/property/create-property-popover.tsx index 4c4394f51..93d01fb34 100644 --- a/apps/client/src/ee/base/components/property/create-property-popover.tsx +++ b/apps/client/src/ee/base/components/property/create-property-popover.tsx @@ -18,11 +18,12 @@ import { TypeOptions, } from "@/ee/base/types/base.types"; import { useCreatePropertyMutation } from "@/ee/base/queries/base-property-query"; -import { PropertyTypePicker, propertyTypes } from "./property-type-picker"; +import { PropertyTypePicker } from "./property-type-picker"; import { PropertyOptions } from "./property-options"; import { getDescriptor, defaultTypeOptionsFor, + propertyTypes, } from "@/ee/base/property-types/property-type.registry"; import { FormulaEditor } from "../formula/formula-editor"; import classes from "@/ee/base/styles/grid.module.css"; diff --git a/apps/client/src/ee/base/components/property/property-menu.tsx b/apps/client/src/ee/base/components/property/property-menu.tsx index 1dba1a722..a863046cd 100644 --- a/apps/client/src/ee/base/components/property/property-menu.tsx +++ b/apps/client/src/ee/base/components/property/property-menu.tsx @@ -28,7 +28,7 @@ import { useUpdatePropertyMutation, useDeletePropertyMutation, } from "@/ee/base/queries/base-property-query"; -import { PropertyTypePicker, propertyTypes } from "./property-type-picker"; +import { PropertyTypePicker } from "./property-type-picker"; import { PropertyOptions } from "./property-options"; import { conversionWarning, @@ -36,7 +36,7 @@ import { NON_USER_TARGET_TYPES, } from "./conversion-warning"; import { useTranslation } from "react-i18next"; -import { isSystemPropertyType } from "@/ee/base/property-types/property-type.registry"; +import { isSystemPropertyType, propertyTypes } from "@/ee/base/property-types/property-type.registry"; import cellClasses from "@/ee/base/styles/cells.module.css"; import classes from "@/ee/base/styles/property.module.css"; diff --git a/apps/client/src/ee/base/components/property/property-type-picker.tsx b/apps/client/src/ee/base/components/property/property-type-picker.tsx index cb2e650da..ee23301cd 100644 --- a/apps/client/src/ee/base/components/property/property-type-picker.tsx +++ b/apps/client/src/ee/base/components/property/property-type-picker.tsx @@ -1,19 +1,11 @@ import { UnstyledButton, Group, Text, TextInput } from "@mantine/core"; import { IconCheck, IconSearch } from "@tabler/icons-react"; import { BasePropertyType } from "@/ee/base/types/base.types"; -import { - PROPERTY_PICKER_ORDER, - getDescriptor, -} from "@/ee/base/property-types/property-type.registry"; +import { propertyTypes } from "@/ee/base/property-types/property-type.registry"; import { useTranslation } from "react-i18next"; import { useState, useRef, useEffect } from "react"; import classes from "@/ee/base/styles/cells.module.css"; -const propertyTypes = PROPERTY_PICKER_ORDER.map((type) => { - const d = getDescriptor(type)!; - return { type, icon: d.icon, labelKey: d.labelKey }; -}); - type PropertyTypePickerProps = { onSelect: (type: BasePropertyType) => void; currentType?: BasePropertyType; @@ -77,5 +69,3 @@ export function PropertyTypePicker({ ); } - -export { propertyTypes }; diff --git a/apps/client/src/ee/base/components/row-detail-modal/row-detail-modal.tsx b/apps/client/src/ee/base/components/row-detail-modal/row-detail-modal.tsx index 32a8d8440..7187d50ce 100644 --- a/apps/client/src/ee/base/components/row-detail-modal/row-detail-modal.tsx +++ b/apps/client/src/ee/base/components/row-detail-modal/row-detail-modal.tsx @@ -292,6 +292,7 @@ export function RowDetailModal({ row={row} primaryProperty={primaryProperty} canEdit={canEdit} + onClose={onClose} onCommit={(value) => { if (!primaryProperty) return; updateRowMutation.mutate({ diff --git a/apps/client/src/ee/base/components/row-detail-modal/row-detail-title.tsx b/apps/client/src/ee/base/components/row-detail-modal/row-detail-title.tsx index 71d8402bc..b04c0a339 100644 --- a/apps/client/src/ee/base/components/row-detail-modal/row-detail-title.tsx +++ b/apps/client/src/ee/base/components/row-detail-modal/row-detail-title.tsx @@ -9,6 +9,7 @@ type RowDetailTitleProps = { primaryProperty: IBaseProperty | undefined; canEdit: boolean; onCommit: (value: string) => void; + onClose: () => void; }; export function RowDetailTitle({ @@ -16,6 +17,7 @@ export function RowDetailTitle({ primaryProperty, canEdit, onCommit, + onClose, }: RowDetailTitleProps) { const { t } = useTranslation(); const initial = primaryProperty @@ -57,6 +59,10 @@ export function RowDetailTitle({ if (e.key === "Enter") { e.preventDefault(); (e.currentTarget as HTMLInputElement).blur(); + } else if (e.key === "Escape") { + e.preventDefault(); + (e.currentTarget as HTMLInputElement).blur(); + onClose(); } }} /> diff --git a/apps/client/src/ee/base/components/views/view-property-visibility.tsx b/apps/client/src/ee/base/components/views/view-property-visibility.tsx index 96af8410e..4fb7db7bb 100644 --- a/apps/client/src/ee/base/components/views/view-property-visibility.tsx +++ b/apps/client/src/ee/base/components/views/view-property-visibility.tsx @@ -2,7 +2,7 @@ import { useMemo, useCallback } from "react"; import { Popover, Switch, Stack, Text, Group, Divider, UnstyledButton } from "@mantine/core"; import { Table } from "@tanstack/react-table"; import { IBaseRow, IBaseProperty } from "@/ee/base/types/base.types"; -import { propertyTypes } from "@/ee/base/components/property/property-type-picker"; +import { propertyTypes } from "@/ee/base/property-types/property-type.registry"; import { useTranslation } from "react-i18next"; import cellClasses from "@/ee/base/styles/cells.module.css"; import viewClasses from "@/ee/base/styles/views.module.css"; diff --git a/apps/client/src/ee/base/property-types/property-type.registry.tsx b/apps/client/src/ee/base/property-types/property-type.registry.tsx index 7536177d5..071dab21c 100644 --- a/apps/client/src/ee/base/property-types/property-type.registry.tsx +++ b/apps/client/src/ee/base/property-types/property-type.registry.tsx @@ -96,7 +96,11 @@ export const PROPERTY_TYPE_REGISTRY: Record< hasOptions: true, defaultTypeOptions: () => { const choices = defaultStatusChoices(); - return { choices, choiceOrder: choices.map((c) => c.id) }; + return { + choices, + choiceOrder: choices.map((c) => c.id), + defaultValue: choices[0].id, + }; }, }, multiSelect: { @@ -244,6 +248,11 @@ export const PROPERTY_PICKER_ORDER: BasePropertyType[] = [ "createdAt", "lastEditedAt", "lastEditedBy", ]; +export const propertyTypes = PROPERTY_PICKER_ORDER.map((type) => { + const d = getDescriptor(type)!; + return { type, icon: d.icon, labelKey: d.labelKey }; +}); + export function systemAccessorFor(type: string) { return getDescriptor(type)?.systemAccessor; } diff --git a/apps/client/src/ee/base/queries/base-property-query.ts b/apps/client/src/ee/base/queries/base-property-query.ts index dd0ba85bf..a2c4fe224 100644 --- a/apps/client/src/ee/base/queries/base-property-query.ts +++ b/apps/client/src/ee/base/queries/base-property-query.ts @@ -18,6 +18,7 @@ import { import { notifications } from "@mantine/notifications"; import { queryClient } from "@/main"; import { useTranslation } from "react-i18next"; +import { getApiErrorMessage } from "@/lib/api-error"; import { IPagination } from "@/lib/types"; export function useCreatePropertyMutation() { @@ -36,9 +37,9 @@ export function useCreatePropertyMutation() { }, ); }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to create property"), + message: getApiErrorMessage(error, t("Failed to create property")), color: "red", }); }, @@ -69,9 +70,9 @@ export function useUpdatePropertyMutation() { }); } }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to update property"), + message: getApiErrorMessage(error, t("Failed to update property")), color: "red", }); }, @@ -114,9 +115,9 @@ export function useDeletePropertyMutation() { }, ); }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to delete property"), + message: getApiErrorMessage(error, t("Failed to delete property")), color: "red", }); }, @@ -154,7 +155,7 @@ export function useReorderPropertyMutation() { return { previous }; }, - onError: (_, variables, context) => { + onError: (error, variables, context) => { if (context?.previous) { queryClient.setQueryData( ["bases", variables.pageId], @@ -162,7 +163,7 @@ export function useReorderPropertyMutation() { ); } notifications.show({ - message: t("Failed to reorder property"), + message: getApiErrorMessage(error, t("Failed to reorder property")), color: "red", }); }, diff --git a/apps/client/src/ee/base/queries/base-query.ts b/apps/client/src/ee/base/queries/base-query.ts index d45af1f49..ae1335bbf 100644 --- a/apps/client/src/ee/base/queries/base-query.ts +++ b/apps/client/src/ee/base/queries/base-query.ts @@ -18,6 +18,7 @@ import { import { notifications } from "@mantine/notifications"; import { queryClient } from "@/main"; import { useTranslation } from "react-i18next"; +import { getApiErrorMessage } from "@/lib/api-error"; import { useAtom } from "jotai"; import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom"; import { treeModel } from "@/features/page/tree/model/tree-model"; @@ -44,9 +45,9 @@ export function useCreateBaseMutation() { queryKey: ["bases", "list", data.spaceId], }); }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to create base"), + message: getApiErrorMessage(error, t("Failed to create base")), color: "red", }); }, @@ -77,9 +78,9 @@ export function useConvertPageToBaseMutation() { payload: { isBase: true }, }); }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to create base"), + message: getApiErrorMessage(error, t("Failed to create base")), color: "red", }); }, @@ -96,9 +97,9 @@ export function useUpdateBaseMutation() { return { ...old, ...data }; }); }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to update base"), + message: getApiErrorMessage(error, t("Failed to update base")), color: "red", }); }, @@ -116,9 +117,9 @@ export function useDeleteBaseMutation() { }); notifications.show({ message: t("Base deleted") }); }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to delete base"), + message: getApiErrorMessage(error, t("Failed to delete base")), color: "red", }); }, diff --git a/apps/client/src/ee/base/queries/base-row-query.ts b/apps/client/src/ee/base/queries/base-row-query.ts index 0df2781e1..c6ec66e1b 100644 --- a/apps/client/src/ee/base/queries/base-row-query.ts +++ b/apps/client/src/ee/base/queries/base-row-query.ts @@ -31,6 +31,7 @@ import { import { notifications } from "@mantine/notifications"; import { queryClient } from "@/main"; import { useTranslation } from "react-i18next"; +import { getApiErrorMessage } from "@/lib/api-error"; import { useHydrateReferences } from "@/ee/base/reference/reference-store"; import { markRequestIdOutbound } from "@/ee/base/hooks/use-base-socket"; import { v7 as uuid7 } from "uuid"; @@ -149,9 +150,9 @@ export function useCreateRowMutation() { invalidateBaseRows(newRow.pageId); } }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to create row"), + message: getApiErrorMessage(error, t("Failed to create row")), color: "red", }); }, @@ -221,7 +222,7 @@ export function useUpdateRowMutation() { return { snapshots }; }, - onError: (_, variables, context) => { + onError: (error, variables, context) => { if (context?.snapshots) { for (const [key, data] of context.snapshots) { queryClient.setQueryData(key, data); @@ -231,7 +232,7 @@ export function useUpdateRowMutation() { queryKey: ["base-row", variables.pageId, variables.rowId], }); notifications.show({ - message: t("Failed to update row"), + message: getApiErrorMessage(error, t("Failed to update row")), color: "red", }); }, @@ -305,14 +306,14 @@ export function useDeleteRowMutation() { return { snapshots }; }, - onError: (_, variables, context) => { + onError: (error, variables, context) => { if (context?.snapshots) { for (const [key, data] of context.snapshots) { queryClient.setQueryData(key, data); } } notifications.show({ - message: t("Failed to delete row"), + message: getApiErrorMessage(error, t("Failed to delete row")), color: "red", }); }, @@ -349,14 +350,14 @@ export function useDeleteRowsMutation() { return { snapshots }; }, - onError: (_, __, context) => { + onError: (error, __, context) => { if (context?.snapshots) { for (const [key, data] of context.snapshots) { queryClient.setQueryData(key, data); } } notifications.show({ - message: t("Failed to delete rows"), + message: getApiErrorMessage(error, t("Failed to delete rows")), color: "red", }); }, @@ -399,14 +400,14 @@ export function useReorderRowMutation() { return { snapshots }; }, - onError: (_, variables, context) => { + onError: (error, variables, context) => { if (context?.snapshots) { for (const [key, data] of context.snapshots) { queryClient.setQueryData(key, data); } } notifications.show({ - message: t("Failed to reorder row"), + message: getApiErrorMessage(error, t("Failed to reorder row")), color: "red", }); }, @@ -504,14 +505,14 @@ export function useKanbanMoveCardMutation() { return { snapshots }; }, - onError: (_, __, context) => { + onError: (error, __, context) => { if (context?.snapshots) { for (const [key, data] of context.snapshots) { queryClient.setQueryData(key, data); } } notifications.show({ - message: t("Failed to move card"), + message: getApiErrorMessage(error, t("Failed to move card")), color: "red", }); }, @@ -552,9 +553,9 @@ export function useKanbanCreateCardMutation() { }; }); }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to add card"), + message: getApiErrorMessage(error, t("Failed to add card")), color: "red", }); }, diff --git a/apps/client/src/ee/base/queries/base-view-query.ts b/apps/client/src/ee/base/queries/base-view-query.ts index c00b4792d..f78d90b4f 100644 --- a/apps/client/src/ee/base/queries/base-view-query.ts +++ b/apps/client/src/ee/base/queries/base-view-query.ts @@ -28,6 +28,7 @@ function applyConfigPatch( import { notifications } from "@mantine/notifications"; import { queryClient } from "@/main"; import { useTranslation } from "react-i18next"; +import { getApiErrorMessage } from "@/lib/api-error"; export function useCreateViewMutation() { const { t } = useTranslation(); @@ -45,9 +46,9 @@ export function useCreateViewMutation() { }, ); }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to create view"), + message: getApiErrorMessage(error, t("Failed to create view")), color: "red", }); }, @@ -99,7 +100,7 @@ export function useUpdateViewMutation() { return { previous }; }, - onError: (_, variables, context) => { + onError: (error, variables, context) => { if (context?.previous) { queryClient.setQueryData( ["bases", variables.pageId], @@ -107,7 +108,7 @@ export function useUpdateViewMutation() { ); } notifications.show({ - message: t("Failed to update view"), + message: getApiErrorMessage(error, t("Failed to update view")), color: "red", }); }, @@ -144,9 +145,9 @@ export function useDeleteViewMutation() { }, ); }, - onError: () => { + onError: (error) => { notifications.show({ - message: t("Failed to delete view"), + message: getApiErrorMessage(error, t("Failed to delete view")), color: "red", }); }, diff --git a/apps/client/src/features/editor/components/slash-menu/menu-items.ts b/apps/client/src/features/editor/components/slash-menu/menu-items.ts index 0c8a72127..a5a3c13d0 100644 --- a/apps/client/src/features/editor/components/slash-menu/menu-items.ts +++ b/apps/client/src/features/editor/components/slash-menu/menu-items.ts @@ -447,7 +447,7 @@ const CommandGroups: SlashMenuGroupedItemsType = { { title: "Kanban", description: "Insert a kanban board on this page", - searchTerms: ["kanban", "board", "cards", "status", "task"], + searchTerms: ["kanban", "board", "cards", "status", "task", "database"], icon: IconLayoutKanban, command: async ({ editor, range }: CommandProps) => { // @ts-ignore diff --git a/apps/client/src/lib/api-error.ts b/apps/client/src/lib/api-error.ts new file mode 100644 index 000000000..cd2c4d19a --- /dev/null +++ b/apps/client/src/lib/api-error.ts @@ -0,0 +1,17 @@ +import { isAxiosError } from "axios"; + +export function getApiErrorMessage( + error: unknown, + fallback = "An error occurred", +): string { + if (isAxiosError(error)) { + const message = error.response?.data?.message; + if (Array.isArray(message)) { + const joined = message.filter(Boolean).join(", "); + if (joined) return joined; + } else if (typeof message === "string" && message.trim()) { + return message; + } + } + return fallback; +} diff --git a/apps/client/src/lib/index.ts b/apps/client/src/lib/index.ts index 178cd64f8..7c9a3a744 100644 --- a/apps/client/src/lib/index.ts +++ b/apps/client/src/lib/index.ts @@ -1 +1,2 @@ export * from "./utils"; +export * from "./api-error"; diff --git a/apps/server/src/ee b/apps/server/src/ee index fa7e54ab4..aed638e89 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit fa7e54ab41e59dac40a26587f2f145dc758837a3 +Subproject commit aed638e8959196072f6d01c2690cac6c8c5043e6