diff --git a/README.md b/README.md
index 4689c7f..b44bc67 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,13 @@

-bknd simplifies app development by providing fully functional backend for database management, authentication, media and workflows. Being lightweight and built on Web Standards, it can be deployed nearly anywhere, including running inside your framework of choice. No more deploying multiple separate services!
+
+
+bknd simplifies app development by providing a fully functional backend for database management, authentication, media and workflows. Being lightweight and built on Web Standards, it can be deployed nearly anywhere, including running inside your framework of choice. No more deploying multiple separate services!
**For documentation and examples, please visit https://docs.bknd.io.**
@@ -17,7 +23,7 @@ bknd simplifies app development by providing fully functional backend for databa


-The unpacked size on npm is misleading, as the `bknd` package includes the backend, the ui components as well as the whole backend bundled into the cli including static assets.
+The size on npm is misleading, as the `bknd` package includes the backend, the ui components as well as the whole backend bundled into the cli including static assets.
## Motivation
Creating digital products always requires developing both the backend (the logic) and the frontend (the appearance). Building a backend from scratch demands deep knowledge in areas such as authentication and database management. Using a backend framework can speed up initial development, but it still requires ongoing effort to work within its constraints (e.g., *"how to do X with Y?"*), which can quickly slow you down. Choosing a backend system is a tough decision, as you might not be aware of its limitations until you encounter them.
@@ -43,8 +49,8 @@ The package is mainly split into 4 parts, each serving a specific purpose:
| Import | Purpose |
|-----------------------------|------------------------------------------------------|
-| `bknd` `bknd/adapter/*` | Backend including all APIs |
-| `bknd/ui` | Admin UI components for react frameworks at |
+| `bknd` `bknd/adapter/*` | Backend including APIs and adapters |
+| `bknd/ui` | Admin UI components for react frameworks |
| `bknd/client` | TypeScript SDK and React hooks for the API endpoints |
| `bknd/elements` | React components for authentication and media |
@@ -110,6 +116,7 @@ export default function App() {
You don't have to figure out API details to include media uploads to your app. For an user avatar upload, this is all you need:
```tsx
import { Media } from "bknd/elements"
+import "bknd/dist/main.css"
export function UserAvatar() {
return ;
+
+ if (formOnly) {
+ if (loading) return null;
+ return Form;
+ }
return (
@@ -25,7 +38,7 @@ export function AuthScreen({ method = "POST", action = "login", logo, intro }: A
Enter your credentials below to get access.
)}
-
+ {Form}
)}
diff --git a/app/src/ui/elements/auth/index.ts b/app/src/ui/elements/auth/index.ts
index b73224a..68843bd 100644
--- a/app/src/ui/elements/auth/index.ts
+++ b/app/src/ui/elements/auth/index.ts
@@ -1,3 +1,4 @@
+import { useAuthStrategies } from "../hooks/use-auth";
import { AuthForm } from "./AuthForm";
import { AuthScreen } from "./AuthScreen";
import { SocialLink } from "./SocialLink";
@@ -7,3 +8,5 @@ export const Auth = {
Form: AuthForm,
SocialLink: SocialLink
};
+
+export { useAuthStrategies };
diff --git a/app/src/ui/elements/index.ts b/app/src/ui/elements/index.ts
index c2d2109..9072c1e 100644
--- a/app/src/ui/elements/index.ts
+++ b/app/src/ui/elements/index.ts
@@ -1,2 +1,2 @@
-export { Auth } from "./auth";
+export * from "./auth";
export * from "./media";
diff --git a/app/src/ui/elements/media/DropzoneContainer.tsx b/app/src/ui/elements/media/DropzoneContainer.tsx
index adca51d..fa41f90 100644
--- a/app/src/ui/elements/media/DropzoneContainer.tsx
+++ b/app/src/ui/elements/media/DropzoneContainer.tsx
@@ -1,31 +1,32 @@
-import type { RepoQuery, RepoQueryIn } from "data";
+import type { RepoQueryIn } from "data";
import type { MediaFieldSchema } from "media/AppMedia";
import type { TAppMediaConfig } from "media/media-schema";
-import { useId } from "react";
-import { useApi, useBaseUrl, useEntityQuery, useInvalidate } from "ui/client";
+import { type ReactNode, createContext, useContext, useId } from "react";
+import { useApi, useEntityQuery, useInvalidate } from "ui/client";
import { useEvent } from "ui/hooks/use-event";
import { Dropzone, type DropzoneProps, type DropzoneRenderProps, type FileState } from "./Dropzone";
import { mediaItemsToFileStates } from "./helper";
export type DropzoneContainerProps = {
- children?: (props: DropzoneRenderProps) => JSX.Element;
+ children?: ReactNode;
initialItems?: MediaFieldSchema[];
entity?: {
name: string;
id: number;
field: string;
};
+ media?: Pick;
query?: RepoQueryIn;
-} & Partial> &
- Partial;
+} & Omit, "children" | "initialItems">;
+
+const DropzoneContainerContext = createContext(undefined!);
export function DropzoneContainer({
initialItems,
- basepath = "/api/media",
- storage = {},
- entity_name = "media",
+ media,
entity,
query,
+ children,
...props
}: DropzoneContainerProps) {
const id = useId();
@@ -33,10 +34,11 @@ export function DropzoneContainer({
const baseUrl = api.baseUrl;
const invalidate = useInvalidate();
const limit = query?.limit ? query?.limit : props.maxItems ? props.maxItems : 50;
- console.log("dropzone:baseUrl", baseUrl);
+ const entity_name = (media?.entity_name ?? "media") as "media";
+ //console.log("dropzone:baseUrl", baseUrl);
const $q = useEntityQuery(
- entity_name as "media",
+ entity_name,
undefined,
{
...query,
@@ -89,6 +91,18 @@ export function DropzoneContainer({
autoUpload
initialItems={_initialItems}
{...props}
- />
+ >
+ {children
+ ? (props) => (
+
+ {children}
+
+ )
+ : undefined}
+
);
}
+
+export function useDropzone() {
+ return useContext(DropzoneContainerContext);
+}
diff --git a/app/src/ui/elements/media/index.ts b/app/src/ui/elements/media/index.ts
index 142d2a7..ff5c8f8 100644
--- a/app/src/ui/elements/media/index.ts
+++ b/app/src/ui/elements/media/index.ts
@@ -1,11 +1,14 @@
import { PreviewWrapperMemoized } from "./Dropzone";
-import { DropzoneContainer } from "./DropzoneContainer";
+import { DropzoneContainer, useDropzone } from "./DropzoneContainer";
export const Media = {
Dropzone: DropzoneContainer,
- Preview: PreviewWrapperMemoized
+ Preview: PreviewWrapperMemoized,
+ useDropzone: useDropzone
};
+export { useDropzone as useMediaDropzone };
+
export type {
PreviewComponentProps,
FileState,
diff --git a/app/src/ui/routes/test/tests/dropzone-element-test.tsx b/app/src/ui/routes/test/tests/dropzone-element-test.tsx
index 3abaebe..6e53d95 100644
--- a/app/src/ui/routes/test/tests/dropzone-element-test.tsx
+++ b/app/src/ui/routes/test/tests/dropzone-element-test.tsx
@@ -1,4 +1,4 @@
-import { type DropzoneRenderProps, Media } from "ui/elements";
+import { Media } from "ui/elements";
import { Scrollable } from "ui/layouts/AppShell/AppShell";
export default function DropzoneElementTest() {
@@ -12,7 +12,7 @@ export default function DropzoneElementTest() {
maxItems={1}
overwrite
>
- {(props) => }
+
@@ -49,12 +49,13 @@ export default function DropzoneElementTest() {
);
}
-function CustomUserAvatarDropzone({
- wrapperRef,
- inputProps,
- state: { files, isOver, isOverAccepted, showPlaceholder },
- actions: { openFileInput }
-}: DropzoneRenderProps) {
+function CustomUserAvatarDropzone() {
+ const {
+ wrapperRef,
+ inputProps,
+ state: { files, isOver, isOverAccepted, showPlaceholder },
+ actions: { openFileInput }
+ } = Media.useDropzone();
const file = files[0];
return (
diff --git a/docs/usage/elements.mdx b/docs/usage/elements.mdx
index 0a517fa..299ecd3 100644
--- a/docs/usage/elements.mdx
+++ b/docs/usage/elements.mdx
@@ -3,23 +3,134 @@ title: "React Elements"
description: "Speed up your frontend development"
---
-
- The documentation is currently a work in progress and not complete.
-
-
-
Not only creating and maintaing a backend is time-consuming, but also integrating it into your frontend can be a hassle. With `bknd/elements`, you can easily add media uploads and authentication forms to your app without having to figure out API details.
-## Media uploads
+
+ In order to use these exported elements, make sure to wrap your app inside `ClientProvider`. See the [React Setup](/usage/react#setup) for more information.
+
+
+# Media
+
+## Media.Dropzone
+The `Media.Dropzone` element allows retrieving from and uploading media items to your bknd instance. Without any properties specified, it will behave similar to your media library inside the bknd Admin UI. Here is how to get the last 10 items:
```tsx
import { Media } from "bknd/elements"
-export function UserAvatar() {
+export default function MediaGallery() {
+ return
+}
+```
+
+Since you can also upload media to a specific entity, you can also point that `Dropzone` to it. Here is an example of a single user avatar that gets overwritten on re-upload:
+
+```tsx
+import { Media } from "bknd/elements";
+
+export default function UserAvatar() {
return
+}
+```
+
+### Props
+- `initialItems?: xMediaFieldSchema[]`: Initial items to display, must be an array of media objects.
+- `entity?: { name: string; id: number; field: string }`: If given, the initial media items fetched will be from this entity.
+- `query?: RepoQueryIn`: Query to filter the media items.
+- `overwrite?: boolean`: If true, the media item will be overwritten on entity media uploads if limit was reached.
+- `maxItems?: number`: Maximum number of media items that can be uploaded.
+- `autoUpload?: boolean`: If true, the media items will be uploaded automatically.
+- `onRejected?: (files: FileWithPath[]) => void`: Callback when a file is rejected.
+- `onDeleted?: (file: FileState) => void`: Callback when a file is deleted.
+- `onUploaded?: (file: FileState) => void`: Callback when a file is uploaded.
+- `placeholder?: { show?: boolean; text?: string }`: Placeholder text to show when no media items are present.
+
+### Customize Rendering
+You can also customize the rendering of the media items and its uploading by passing a react element as a child. Here is an example of a custom `Media.Dropzone` that renders an user avatar (styled using tailwind):
+
+```tsx
+import { Media, useMediaDropzone } from "bknd/elements";
+
+export default function CustomUserAvatar() {
+ return
+
+
+}
+
+function CustomUserAvatar() {
+ const {
+ wrapperRef,
+ inputProps,
+ state: { files, isOver, isOverAccepted, showPlaceholder },
+ actions: { openFileInput }
+ } = useMediaDropzone();
+
+ const file = files[0];
+
+ return (
+
+ );
+}
+```
+
+# Auth
+Adding authentication to your app with bknd is as easy as adding a `` with an action pointing to the action (`login` or `register`) to the strategy you want to use, e.g. for the password strategy, use `/api/auth/password/login`. But to make it even easier, you can use the `Auth.*` elements.
+
+## `Auth.Screen`
+The `Auth.Screen` element is a wrapper around the `Auth.Form` element that provides a full page screen. The current layout is admittedly very basic, but there will be more customization options in the future.
+
+```tsx
+import { Auth } from "bknd/elements"
+
+export default function LoginScreen() {
+ return
+}
+```
+
+### Props
+Note that this component doesn't require any strategy-specific information, as it gathers it itself.
+
+- `action: "login" | "register"`: The action to perform.
+- `method?: "POST" | "GET"`: The method to use for the form.
+
+
+
+## `Auth.Form`
+If you only wish to render the form itself without the screen, you can use the `Auth.Form` element. Unlike the `Auth.Screen`, this element requires the `strategy` prop to be set to the strategy you want to use. You can either specify it manually, use use the exported hook `useAuthStrategies()` for fetch them from your bknd instance.
+
+```tsx
+import { Auth, useAuthStrategies } from "bknd/elements"
+
+export default function LoginForm() {
+ const { strategies, basepath, loading } = useAuthStrategies();
+ if (loading) return null;
+
+ return
}
```
\ No newline at end of file
diff --git a/docs/usage/react.mdx b/docs/usage/react.mdx
index 753e93b..1e45325 100644
--- a/docs/usage/react.mdx
+++ b/docs/usage/react.mdx
@@ -3,7 +3,7 @@ title: 'SDK (React)'
description: 'Use the bknd SDK for React'
---
-bknd exports 4 useful hooks to work with your backend:
+There are 4 useful hooks to work with your backend:
1. simple hooks which are solely based on the [API](/usage/sdk):
- [`useApi`](#useapi)
- [`useEntity`](#useentity)
@@ -11,6 +11,22 @@ bknd exports 4 useful hooks to work with your backend:
- [`useApiQuery`](#useapiquery)
- [`useEntityQuery`](#useentityquery)
+
+## Setup
+In order to use them, make sure you wrap your `` inside ``, so that these hooks point to your bknd instance:
+
+```tsx
+import { ClientProvider } from "bknd/client";
+
+export default function App() {
+ return
+ {/* your app */}
+
+}
+```
+
+For all other examples below, we'll assume that your app is wrapped inside the `ClientProvider`.
+
## `useApi()`
To use the simple hook that returns the Api, you can use:
```tsx