From 0cf1d8621373660f1b78c3b4d014bcba6199bb2c Mon Sep 17 00:00:00 2001 From: dswbx Date: Tue, 25 Nov 2025 16:16:10 +0100 Subject: [PATCH 1/2] fix client imports to prevent multiple react context's --- app/build.ts | 2 + app/src/ui/client/BkndProvider.tsx | 2 +- app/src/ui/client/ClientProvider.tsx | 11 ++++- app/src/ui/client/api/use-api.ts | 2 +- app/src/ui/client/api/use-entity.ts | 2 +- app/src/ui/client/index.ts | 1 + app/src/ui/client/schema/auth/use-auth.ts | 3 +- app/src/ui/elements/hooks/use-auth.ts | 6 ++- app/src/ui/elements/media/Dropzone.tsx | 2 +- .../ui/elements/media/DropzoneContainer.tsx | 41 +++++++++---------- app/src/ui/elements/media/DropzoneInner.tsx | 4 +- app/src/ui/layouts/AppShell/Header.tsx | 2 +- app/src/ui/modals/media/MediaInfoModal.tsx | 2 +- .../auth/hooks/use-create-user-modal.ts | 2 +- .../fields/EntityRelationalFormField.tsx | 2 +- app/src/ui/routes/auth/auth.index.tsx | 2 +- .../ui/routes/auth/auth.roles.edit.$role.tsx | 2 +- app/src/ui/routes/data/data.$entity.$id.tsx | 2 +- .../ui/routes/data/data.$entity.create.tsx | 2 +- app/src/ui/routes/data/data.$entity.index.tsx | 2 +- app/src/ui/routes/index.tsx | 2 +- app/src/ui/routes/media/media.index.tsx | 5 ++- app/src/ui/routes/root.tsx | 2 +- app/src/ui/routes/test/tests/swr-and-api.tsx | 2 +- app/tsconfig.json | 3 +- 25 files changed, 59 insertions(+), 49 deletions(-) diff --git a/app/build.ts b/app/build.ts index 3de4e2a..599ce82 100644 --- a/app/build.ts +++ b/app/build.ts @@ -186,6 +186,8 @@ async function buildUiElements() { outDir: "dist/ui/elements", external: [ "ui/client", + "bknd", + /^bknd\/.*/, "react", "react-dom", "react/jsx-runtime", diff --git a/app/src/ui/client/BkndProvider.tsx b/app/src/ui/client/BkndProvider.tsx index 3ac5d5d..056e52d 100644 --- a/app/src/ui/client/BkndProvider.tsx +++ b/app/src/ui/client/BkndProvider.tsx @@ -9,7 +9,7 @@ import { useState, type ReactNode, } from "react"; -import { useApi } from "ui/client"; +import { useApi } from "bknd/client"; import { type TSchemaActions, getSchemaActions } from "./schema/actions"; import { AppReduced } from "./utils/AppReduced"; import { Message } from "ui/components/display/Message"; diff --git a/app/src/ui/client/ClientProvider.tsx b/app/src/ui/client/ClientProvider.tsx index 88a54c1..21866c3 100644 --- a/app/src/ui/client/ClientProvider.tsx +++ b/app/src/ui/client/ClientProvider.tsx @@ -14,18 +14,20 @@ const ClientContext = createContext(undefined!); export type ClientProviderProps = { children?: ReactNode; baseUrl?: string; + api?: Api; } & ApiOptions; export const ClientProvider = ({ children, host, baseUrl: _baseUrl = host, + api: _api, ...props }: ClientProviderProps) => { const winCtx = useBkndWindowContext(); const _ctx = useClientContext(); let actualBaseUrl = _baseUrl ?? _ctx?.baseUrl ?? ""; - let user: any = undefined; + let user: any; if (winCtx) { user = winCtx.user; @@ -40,6 +42,7 @@ export const ClientProvider = ({ const apiProps = { user, ...props, host: actualBaseUrl }; const api = useMemo( () => + _api ?? new Api({ ...apiProps, verbose: isDebug(), @@ -50,7 +53,7 @@ export const ClientProvider = ({ } }, }), - [JSON.stringify(apiProps)], + [_api, JSON.stringify(apiProps)], ); const [authState, setAuthState] = useState | undefined>(api.getAuthState()); @@ -64,6 +67,10 @@ export const ClientProvider = ({ export const useApi = (host?: ApiOptions["host"]): Api => { const context = useContext(ClientContext); + if (!context) { + throw new Error("useApi must be used within a ClientProvider"); + } + if (!context?.api || (host && host.length > 0 && host !== context.baseUrl)) { return new Api({ host: host ?? "" }); } diff --git a/app/src/ui/client/api/use-api.ts b/app/src/ui/client/api/use-api.ts index 573b990..a93aaf2 100644 --- a/app/src/ui/client/api/use-api.ts +++ b/app/src/ui/client/api/use-api.ts @@ -2,7 +2,7 @@ import type { Api } from "Api"; import { FetchPromise, type ModuleApi, type ResponseObject } from "modules/ModuleApi"; import useSWR, { type SWRConfiguration, useSWRConfig, type Middleware, type SWRHook } from "swr"; import useSWRInfinite from "swr/infinite"; -import { useApi } from "ui/client"; +import { useApi } from "../ClientProvider"; import { useState } from "react"; export const useApiQuery = < diff --git a/app/src/ui/client/api/use-entity.ts b/app/src/ui/client/api/use-entity.ts index 1042344..7edeaf1 100644 --- a/app/src/ui/client/api/use-entity.ts +++ b/app/src/ui/client/api/use-entity.ts @@ -10,7 +10,7 @@ import type { import { objectTransform, encodeSearch } from "bknd/utils"; import type { Insertable, Selectable, Updateable, Generated } from "kysely"; import useSWR, { type SWRConfiguration, type SWRResponse, mutate } from "swr"; -import { type Api, useApi } from "ui/client"; +import { type Api, useApi } from "bknd/client"; export class UseEntityApiError extends Error { constructor( diff --git a/app/src/ui/client/index.ts b/app/src/ui/client/index.ts index 2fb520e..f611752 100644 --- a/app/src/ui/client/index.ts +++ b/app/src/ui/client/index.ts @@ -4,6 +4,7 @@ export { type ClientProviderProps, useApi, useBaseUrl, + useClientContext } from "./ClientProvider"; export * from "./api/use-api"; diff --git a/app/src/ui/client/schema/auth/use-auth.ts b/app/src/ui/client/schema/auth/use-auth.ts index 291c963..3db502d 100644 --- a/app/src/ui/client/schema/auth/use-auth.ts +++ b/app/src/ui/client/schema/auth/use-auth.ts @@ -1,7 +1,6 @@ import type { AuthState } from "Api"; import type { AuthResponse } from "bknd"; -import { useApi, useInvalidate } from "ui/client"; -import { useClientContext } from "ui/client/ClientProvider"; +import { useApi, useInvalidate, useClientContext } from "bknd/client"; type LoginData = { email: string; diff --git a/app/src/ui/elements/hooks/use-auth.ts b/app/src/ui/elements/hooks/use-auth.ts index 5907cf6..9bcdf57 100644 --- a/app/src/ui/elements/hooks/use-auth.ts +++ b/app/src/ui/elements/hooks/use-auth.ts @@ -1,9 +1,11 @@ import type { AppAuthSchema } from "auth/auth-schema"; import { useEffect, useState } from "react"; -import { useApi } from "ui/client"; +import { useApi } from "bknd/client"; type AuthStrategyData = Pick; -export const useAuthStrategies = (options?: { baseUrl?: string }): Partial & { +export const useAuthStrategies = (options?: { + baseUrl?: string; +}): Partial & { loading: boolean; } => { const [data, setData] = useState(); diff --git a/app/src/ui/elements/media/Dropzone.tsx b/app/src/ui/elements/media/Dropzone.tsx index b7cb384..896807f 100644 --- a/app/src/ui/elements/media/Dropzone.tsx +++ b/app/src/ui/elements/media/Dropzone.tsx @@ -14,7 +14,7 @@ import { isFileAccepted } from "bknd/utils"; import { type FileWithPath, useDropzone } from "./use-dropzone"; import { checkMaxReached } from "./helper"; import { DropzoneInner } from "./DropzoneInner"; -import { createDropzoneStore } from "ui/elements/media/dropzone-state"; +import { createDropzoneStore } from "./dropzone-state"; import { useStore } from "zustand"; export type FileState = { diff --git a/app/src/ui/elements/media/DropzoneContainer.tsx b/app/src/ui/elements/media/DropzoneContainer.tsx index 2a99a5f..bd412dc 100644 --- a/app/src/ui/elements/media/DropzoneContainer.tsx +++ b/app/src/ui/elements/media/DropzoneContainer.tsx @@ -1,9 +1,8 @@ -import type { Api } from "bknd/client"; import type { PrimaryFieldType, RepoQueryIn } from "bknd"; import type { MediaFieldSchema } from "media/AppMedia"; import type { TAppMediaConfig } from "media/media-schema"; import { useId, useEffect, useRef, useState } from "react"; -import { useApi, useApiInfiniteQuery, useApiQuery, useInvalidate } from "bknd/client"; +import { type Api, useApi, useApiInfiniteQuery, useApiQuery, useInvalidate } from "bknd/client"; import { useEvent } from "ui/hooks/use-event"; import { Dropzone, type DropzoneProps } from "./Dropzone"; import { mediaItemsToFileStates } from "./helper"; @@ -132,26 +131,24 @@ export function DropzoneContainer({ } return ( - <> - $q.setSize($q.size + 1)} - /> - ) - } - {...props} - /> - + $q.setSize($q.size + 1)} + /> + ) + } + {...props} + /> ); } diff --git a/app/src/ui/elements/media/DropzoneInner.tsx b/app/src/ui/elements/media/DropzoneInner.tsx index 6c3cb87..4e7927c 100644 --- a/app/src/ui/elements/media/DropzoneInner.tsx +++ b/app/src/ui/elements/media/DropzoneInner.tsx @@ -19,8 +19,8 @@ import { } from "react-icons/tb"; import { Dropdown, type DropdownItem } from "ui/components/overlay/Dropdown"; import { IconButton } from "ui/components/buttons/IconButton"; -import { formatNumber } from "core/utils"; -import type { DropzoneRenderProps, FileState } from "ui/elements"; +import { formatNumber } from "bknd/utils"; +import type { DropzoneRenderProps, FileState } from "./Dropzone"; import { useDropzoneFileState, useDropzoneState } from "./Dropzone"; function handleUploadError(e: unknown) { diff --git a/app/src/ui/layouts/AppShell/Header.tsx b/app/src/ui/layouts/AppShell/Header.tsx index f53beb3..5d978c8 100644 --- a/app/src/ui/layouts/AppShell/Header.tsx +++ b/app/src/ui/layouts/AppShell/Header.tsx @@ -10,7 +10,7 @@ import { TbUser, TbX, } from "react-icons/tb"; -import { useAuth, useBkndWindowContext } from "ui/client"; +import { useAuth, useBkndWindowContext } from "bknd/client"; import { useBknd } from "ui/client/bknd"; import { useTheme } from "ui/client/use-theme"; import { Button } from "ui/components/buttons/Button"; diff --git a/app/src/ui/modals/media/MediaInfoModal.tsx b/app/src/ui/modals/media/MediaInfoModal.tsx index bfce4f1..4ed787c 100644 --- a/app/src/ui/modals/media/MediaInfoModal.tsx +++ b/app/src/ui/modals/media/MediaInfoModal.tsx @@ -1,6 +1,6 @@ import type { ContextModalProps } from "@mantine/modals"; import { type ReactNode, useEffect, useMemo, useState } from "react"; -import { useEntityQuery } from "ui/client"; +import { useEntityQuery } from "bknd/client"; import { type FileState, Media } from "ui/elements"; import { autoFormatString, datetimeStringLocal, formatNumber } from "core/utils"; import { twMerge } from "tailwind-merge"; diff --git a/app/src/ui/modules/auth/hooks/use-create-user-modal.ts b/app/src/ui/modules/auth/hooks/use-create-user-modal.ts index f3d20ec..c0062b6 100644 --- a/app/src/ui/modules/auth/hooks/use-create-user-modal.ts +++ b/app/src/ui/modules/auth/hooks/use-create-user-modal.ts @@ -1,4 +1,4 @@ -import { useApi, useInvalidate } from "ui/client"; +import { useApi, useInvalidate } from "bknd/client"; import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth"; import { routes, useNavigate } from "ui/lib/routes"; import { bkndModals } from "ui/modals"; diff --git a/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx b/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx index aa331bd..1a4300e 100644 --- a/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx +++ b/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx @@ -4,7 +4,7 @@ import type { EntityData } from "bknd"; import type { RelationField } from "data/relations"; import { useEffect, useRef, useState } from "react"; import { TbEye } from "react-icons/tb"; -import { useEntityQuery } from "ui/client"; +import { useEntityQuery } from "bknd/client"; import { useBknd } from "ui/client/bknd"; import { Button } from "ui/components/buttons/Button"; import * as Formy from "ui/components/form/Formy"; diff --git a/app/src/ui/routes/auth/auth.index.tsx b/app/src/ui/routes/auth/auth.index.tsx index 9379606..e7c5bfc 100644 --- a/app/src/ui/routes/auth/auth.index.tsx +++ b/app/src/ui/routes/auth/auth.index.tsx @@ -1,6 +1,6 @@ import clsx from "clsx"; import { TbArrowRight, TbCircle, TbCircleCheckFilled, TbFingerprint } from "react-icons/tb"; -import { useApiQuery } from "ui/client"; +import { useApiQuery } from "bknd/client"; import { useBknd } from "ui/client/bknd"; import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth"; import { ButtonLink, type ButtonLinkProps } from "ui/components/buttons/Button"; diff --git a/app/src/ui/routes/auth/auth.roles.edit.$role.tsx b/app/src/ui/routes/auth/auth.roles.edit.$role.tsx index d24ac9b..477ca9e 100644 --- a/app/src/ui/routes/auth/auth.roles.edit.$role.tsx +++ b/app/src/ui/routes/auth/auth.roles.edit.$role.tsx @@ -35,7 +35,7 @@ import { SegmentedControl, Tooltip } from "@mantine/core"; import { Popover } from "ui/components/overlay/Popover"; import { cn } from "ui/lib/utils"; import { JsonViewer } from "ui/components/code/JsonViewer"; -import { mountOnce, useApiQuery } from "ui/client"; +import { mountOnce, useApiQuery } from "bknd/client"; import { CodePreview } from "ui/components/code/CodePreview"; import type { JsonError } from "json-schema-library"; import { Alert } from "ui/components/display/Alert"; diff --git a/app/src/ui/routes/data/data.$entity.$id.tsx b/app/src/ui/routes/data/data.$entity.$id.tsx index 30669e9..204c41b 100644 --- a/app/src/ui/routes/data/data.$entity.$id.tsx +++ b/app/src/ui/routes/data/data.$entity.$id.tsx @@ -3,7 +3,7 @@ import { ucFirst } from "bknd/utils"; import type { Entity, EntityData, EntityRelation } from "bknd"; import { Fragment, useState } from "react"; import { TbDots } from "react-icons/tb"; -import { useApiQuery, useEntityQuery } from "ui/client"; +import { useApiQuery, useEntityQuery } from "bknd/client"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { Button } from "ui/components/buttons/Button"; import { IconButton } from "ui/components/buttons/IconButton"; diff --git a/app/src/ui/routes/data/data.$entity.create.tsx b/app/src/ui/routes/data/data.$entity.create.tsx index 20b67ca..a1c3bfd 100644 --- a/app/src/ui/routes/data/data.$entity.create.tsx +++ b/app/src/ui/routes/data/data.$entity.create.tsx @@ -1,6 +1,6 @@ import type { EntityData } from "bknd"; import { useState } from "react"; -import { useEntityMutate } from "ui/client"; +import { useEntityMutate } from "bknd/client"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { Button } from "ui/components/buttons/Button"; import { Message } from "ui/components/display/Message"; diff --git a/app/src/ui/routes/data/data.$entity.index.tsx b/app/src/ui/routes/data/data.$entity.index.tsx index 5945f37..91cfbd9 100644 --- a/app/src/ui/routes/data/data.$entity.index.tsx +++ b/app/src/ui/routes/data/data.$entity.index.tsx @@ -2,7 +2,7 @@ import type { Entity } from "bknd"; import { repoQuery } from "data/server/query"; import { Fragment } from "react"; import { TbDots } from "react-icons/tb"; -import { useApiQuery } from "ui/client"; +import { useApiQuery } from "bknd/client"; import { useBknd } from "ui/client/bknd"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { Button } from "ui/components/buttons/Button"; diff --git a/app/src/ui/routes/index.tsx b/app/src/ui/routes/index.tsx index fde6887..6d959b4 100644 --- a/app/src/ui/routes/index.tsx +++ b/app/src/ui/routes/index.tsx @@ -11,7 +11,7 @@ import SettingsRoutes from "./settings"; import { FlashMessage } from "ui/modules/server/FlashMessage"; import { AuthRegister } from "ui/routes/auth/auth.register"; import { BkndModalsProvider } from "ui/modals"; -import { useBkndWindowContext } from "ui/client"; +import { useBkndWindowContext } from "bknd/client"; import ToolsRoutes from "./tools"; // @ts-ignore diff --git a/app/src/ui/routes/media/media.index.tsx b/app/src/ui/routes/media/media.index.tsx index 5b05fb6..65067e8 100644 --- a/app/src/ui/routes/media/media.index.tsx +++ b/app/src/ui/routes/media/media.index.tsx @@ -1,11 +1,12 @@ import { IconPhoto } from "@tabler/icons-react"; import { useBknd } from "ui/client/BkndProvider"; import { Empty } from "ui/components/display/Empty"; -import { type FileState, Media } from "ui/elements"; import { useBrowserTitle } from "ui/hooks/use-browser-title"; import * as AppShell from "ui/layouts/AppShell/AppShell"; import { useLocation } from "wouter"; import { bkndModals } from "ui/modals"; +import { DropzoneContainer } from "ui/elements/media/DropzoneContainer"; +import type { FileState } from "ui/elements/media/Dropzone"; export function MediaIndex() { const { config } = useBknd(); @@ -35,7 +36,7 @@ export function MediaIndex() { return (
- +
); diff --git a/app/src/ui/routes/root.tsx b/app/src/ui/routes/root.tsx index 184c903..4438362 100644 --- a/app/src/ui/routes/root.tsx +++ b/app/src/ui/routes/root.tsx @@ -1,6 +1,6 @@ import { IconHome } from "@tabler/icons-react"; import { useEffect } from "react"; -import { useAuth } from "ui/client"; +import { useAuth } from "bknd/client"; import { useEffectOnce } from "ui/hooks/use-effect"; import { Empty } from "../components/display/Empty"; import { useBrowserTitle } from "../hooks/use-browser-title"; diff --git a/app/src/ui/routes/test/tests/swr-and-api.tsx b/app/src/ui/routes/test/tests/swr-and-api.tsx index 0f20428..92b0b10 100644 --- a/app/src/ui/routes/test/tests/swr-and-api.tsx +++ b/app/src/ui/routes/test/tests/swr-and-api.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { useApi, useApiQuery } from "ui/client"; +import { useApi, useApiQuery } from "bknd/client"; import { Scrollable } from "ui/layouts/AppShell/AppShell"; function Bla() { diff --git a/app/tsconfig.json b/app/tsconfig.json index 10260b4..d6060d9 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -35,7 +35,8 @@ "bknd/adapter": ["./src/adapter/index.ts"], "bknd/adapter/*": ["./src/adapter/*/index.ts"], "bknd/client": ["./src/ui/client/index.ts"], - "bknd/modes": ["./src/modes/index.ts"] + "bknd/modes": ["./src/modes/index.ts"], + "bknd/elements": ["./src/ui/elements/index.ts"] } }, "include": [ From ab1fa4c895a7a8019a8fb9b54447839bc7107875 Mon Sep 17 00:00:00 2001 From: dswbx Date: Tue, 2 Dec 2025 09:46:09 +0100 Subject: [PATCH 2/2] fix: add infinite back to dropzone container --- app/src/ui/routes/media/media.index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/ui/routes/media/media.index.tsx b/app/src/ui/routes/media/media.index.tsx index 65067e8..9858c77 100644 --- a/app/src/ui/routes/media/media.index.tsx +++ b/app/src/ui/routes/media/media.index.tsx @@ -36,7 +36,7 @@ export function MediaIndex() { return (
- +
);