@pylonsync/client 0.3.292 → 0.3.293
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AcceptInvite.d.ts +32 -0
- package/dist/components/ChatBot.d.ts +44 -0
- package/dist/components/ConnectAccount.d.ts +36 -0
- package/dist/components/EnsureGuest.d.ts +29 -0
- package/dist/components/EntityForm.d.ts +64 -0
- package/dist/components/EntityList.d.ts +50 -0
- package/dist/components/FileUpload.d.ts +37 -0
- package/dist/components/Gates.d.ts +73 -0
- package/dist/components/InviteMembers.d.ts +26 -0
- package/dist/components/OrganizationSwitcher.d.ts +39 -0
- package/dist/components/PasswordReset.d.ts +25 -0
- package/dist/components/SignIn.d.ts +22 -0
- package/dist/components/SignOutButton.d.ts +12 -0
- package/dist/components/UserButton.d.ts +21 -0
- package/dist/components/UserProfile.d.ts +20 -0
- package/dist/hooks/useAuth.d.ts +19 -0
- package/dist/index.d.ts +38 -0
- package/dist/lib/api.d.ts +164 -0
- package/dist/lib/cn.d.ts +2 -0
- package/dist/router/Router.d.ts +50 -0
- package/dist/router/context.d.ts +19 -0
- package/dist/router/match.d.ts +35 -0
- package/dist/router/useRouter.d.ts +19 -0
- package/package.json +14 -8
- package/tsconfig.json +0 -6
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface AcceptInviteProps {
|
|
4
|
+
/**
|
|
5
|
+
* The invite token. Pulled from the email link's `?token=` param
|
|
6
|
+
* by the consuming page. Falls back to `window.location.search`
|
|
7
|
+
* when omitted.
|
|
8
|
+
*/
|
|
9
|
+
token?: string;
|
|
10
|
+
/** Where to send the user after a successful accept. */
|
|
11
|
+
afterAcceptUrl?: string;
|
|
12
|
+
/** Where to send a signed-out user so they can sign in / sign up first. */
|
|
13
|
+
signInUrl?: string;
|
|
14
|
+
/** Optional callback once accept lands. Receives `{org_id, role}`. */
|
|
15
|
+
onAccepted?: (result: {
|
|
16
|
+
org_id: string;
|
|
17
|
+
role: string;
|
|
18
|
+
}) => void;
|
|
19
|
+
/** Override the headline copy. */
|
|
20
|
+
title?: ReactNode;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Drop-in landing surface for `/accept-invite?token=…`. Reads the token
|
|
25
|
+
* from props or the URL, calls `POST /api/auth/invites/:token/accept`,
|
|
26
|
+
* and lights up org membership when the request succeeds.
|
|
27
|
+
*
|
|
28
|
+
* Signed-out users see a "sign in to accept" prompt that bounces through
|
|
29
|
+
* `signInUrl` with the current href as `?next=` so they end up back on
|
|
30
|
+
* this page with the token preserved.
|
|
31
|
+
*/
|
|
32
|
+
export declare function AcceptInvite({ token: tokenProp, afterAcceptUrl, signInUrl, onAccepted, title, className, }: AcceptInviteProps): React.JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface ChatMessage {
|
|
4
|
+
role: "user" | "assistant" | "system";
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ChatBotProps {
|
|
8
|
+
/**
|
|
9
|
+
* Name of the server-side function that drives the model. It runs
|
|
10
|
+
* `ctx.llm.*` and emits its response as SSE chunks via
|
|
11
|
+
* `db.streamFn`. The handler receives `{ messages, ...extra }`.
|
|
12
|
+
*/
|
|
13
|
+
fn: string;
|
|
14
|
+
/** Extra args merged into every call (e.g., `{model: "haiku"}`). */
|
|
15
|
+
extraArgs?: Record<string, unknown>;
|
|
16
|
+
/** System prompt prepended to every turn. */
|
|
17
|
+
systemPrompt?: string;
|
|
18
|
+
/** Initial messages to display (e.g., a welcome from the assistant). */
|
|
19
|
+
initialMessages?: ChatMessage[];
|
|
20
|
+
/** Placeholder shown in the input. */
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
/** Header copy. */
|
|
23
|
+
title?: ReactNode;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Drop-in chat UI for a server-side LLM handler. Wires the assistant
|
|
28
|
+
* turn to `db.streamFn(fn, { messages })`; each yielded chunk is parsed
|
|
29
|
+
* as a JSON event of the shape `{type: "text", delta: string}` (the
|
|
30
|
+
* default emitted by the framework's streaming helpers). Plain string
|
|
31
|
+
* chunks are also accepted so this works with apps that just yield
|
|
32
|
+
* `event.delta` directly.
|
|
33
|
+
*
|
|
34
|
+
* Server-side authoring (5 lines):
|
|
35
|
+
* ```ts
|
|
36
|
+
* export default action({
|
|
37
|
+
* args: { messages: v.array(v.object({ role: v.string(), content: v.string() })) },
|
|
38
|
+
* async handler(ctx, { messages }) {
|
|
39
|
+
* return ctx.llm.streamMessage({ model: "claude-haiku", messages });
|
|
40
|
+
* },
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function ChatBot({ fn, extraArgs, systemPrompt, initialMessages, placeholder, title, className, }: ChatBotProps): React.JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface ConnectAccountProps {
|
|
4
|
+
/**
|
|
5
|
+
* Name of the connection in the manifest (matches the `name` you
|
|
6
|
+
* passed to `defineConnection({...})`). NOT the OAuth provider id.
|
|
7
|
+
*/
|
|
8
|
+
name: string;
|
|
9
|
+
/**
|
|
10
|
+
* URL the OAuth provider sends the user back to after consent.
|
|
11
|
+
* Defaults to the current page so the user lands on the same screen
|
|
12
|
+
* with the new connection already in place.
|
|
13
|
+
*/
|
|
14
|
+
postRedirect?: string;
|
|
15
|
+
/** Optional label override. Default: "Connect <name>". */
|
|
16
|
+
label?: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* If true, render an "outline" style button instead of the solid
|
|
19
|
+
* primary. Useful when the connect-button sits next to a primary
|
|
20
|
+
* CTA.
|
|
21
|
+
*/
|
|
22
|
+
variant?: "primary" | "outline";
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Drop-in button that starts an OAuth flow for a connection defined via
|
|
27
|
+
* `defineConnection(...)` in the manifest. Hits
|
|
28
|
+
* `POST /api/connections/<name>/auth-url` and navigates the browser to
|
|
29
|
+
* the returned URL; the provider redirects back to
|
|
30
|
+
* `/api/connections/<name>/callback`, which stores the encrypted token
|
|
31
|
+
* row and bounces to `postRedirect` (default: current page).
|
|
32
|
+
*
|
|
33
|
+
* Renders nothing for signed-out users — connections need an
|
|
34
|
+
* authenticated identity to bind the access token to.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ConnectAccount({ name, postRedirect, label, variant, className, }: ConnectAccountProps): React.JSX.Element | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface EnsureGuestProps {
|
|
4
|
+
/**
|
|
5
|
+
* Render while the guest session is being minted. Defaults to
|
|
6
|
+
* nothing — the bootstrap is usually fast enough that a flash is
|
|
7
|
+
* worse than empty space. Pass a spinner / skeleton if you care.
|
|
8
|
+
*/
|
|
9
|
+
fallback?: ReactNode;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Drop-in wrapper that ensures the browser has a Pylon session before
|
|
14
|
+
* rendering children. If a token is already cached, renders
|
|
15
|
+
* immediately; otherwise calls `POST /api/auth/guest` and renders
|
|
16
|
+
* once the session token lands.
|
|
17
|
+
*
|
|
18
|
+
* Use this for zero-auth demos where every visitor implicitly becomes
|
|
19
|
+
* their own user — recordings, todos, scratch surfaces. Apps with
|
|
20
|
+
* real users should pair `<SignedOut><SignIn /></SignedOut>` with
|
|
21
|
+
* `<SignedIn>` instead.
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <EnsureGuest>
|
|
25
|
+
* <TodoList />
|
|
26
|
+
* </EnsureGuest>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function EnsureGuest({ fallback, children }: EnsureGuestProps): React.JSX.Element;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { type Row } from "@pylonsync/react";
|
|
4
|
+
export interface EntityFormField {
|
|
5
|
+
/** Field key on the row. */
|
|
6
|
+
field: string;
|
|
7
|
+
/** Display label. Defaults to a humanized `field` name. */
|
|
8
|
+
label?: ReactNode;
|
|
9
|
+
/** Input type hint. Default: "text". */
|
|
10
|
+
type?: "text" | "email" | "url" | "password" | "number" | "textarea" | "checkbox" | "select";
|
|
11
|
+
/** For `type: "select"`. */
|
|
12
|
+
options?: Array<{
|
|
13
|
+
value: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** Required flag. */
|
|
17
|
+
required?: boolean;
|
|
18
|
+
/** Placeholder. */
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
/** Helper text shown under the input. */
|
|
21
|
+
help?: ReactNode;
|
|
22
|
+
/** Default value when creating. */
|
|
23
|
+
defaultValue?: unknown;
|
|
24
|
+
}
|
|
25
|
+
export interface EntityFormProps<T extends Row = Row> {
|
|
26
|
+
/** Entity to write into. */
|
|
27
|
+
entity: string;
|
|
28
|
+
/** Field descriptors. */
|
|
29
|
+
fields: EntityFormField[];
|
|
30
|
+
/**
|
|
31
|
+
* If set, the form edits this existing row. Omit to create a new
|
|
32
|
+
* row (the default mode).
|
|
33
|
+
*/
|
|
34
|
+
row?: T;
|
|
35
|
+
/** Override the submit label. */
|
|
36
|
+
submitLabel?: ReactNode;
|
|
37
|
+
/** Optional cancel button — calls this handler when clicked. */
|
|
38
|
+
onCancel?: () => void;
|
|
39
|
+
/** Called after a successful create/update. */
|
|
40
|
+
onSubmitted?: (row: Row) => void;
|
|
41
|
+
/** Headline shown above the form. */
|
|
42
|
+
title?: ReactNode;
|
|
43
|
+
className?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Drop-in create / edit form for any entity. Uses `db.useEntity` for
|
|
47
|
+
* optimistic CRUD — the local replica gets the change immediately and
|
|
48
|
+
* the server reconciles via the sync push. No `onSubmit` boilerplate.
|
|
49
|
+
*
|
|
50
|
+
* ```tsx
|
|
51
|
+
* <EntityForm
|
|
52
|
+
* entity="Post"
|
|
53
|
+
* fields={[
|
|
54
|
+
* { field: "title", required: true },
|
|
55
|
+
* { field: "body", type: "textarea" },
|
|
56
|
+
* { field: "status", type: "select", options: [
|
|
57
|
+
* { value: "draft" }, { value: "published" },
|
|
58
|
+
* ]},
|
|
59
|
+
* ]}
|
|
60
|
+
* onSubmitted={(row) => router.push(`/posts/${row.id}`)}
|
|
61
|
+
* />
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function EntityForm<T extends Row = Row>({ entity, fields, row, submitLabel, onCancel, onSubmitted, title, className, }: EntityFormProps<T>): React.JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { type Row } from "@pylonsync/react";
|
|
4
|
+
export interface EntityListColumn<T extends Row = Row> {
|
|
5
|
+
/** Field key on the row. */
|
|
6
|
+
field: keyof T & string;
|
|
7
|
+
/** Column header. Defaults to a humanized `field` name. */
|
|
8
|
+
label?: ReactNode;
|
|
9
|
+
/** Custom cell renderer. Default: stringified value. */
|
|
10
|
+
render?: (value: unknown, row: T) => ReactNode;
|
|
11
|
+
/** Tailwind / utility classes applied to the `<td>`. */
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface EntityListProps<T extends Row = Row> {
|
|
15
|
+
/** Entity name (matches your schema declaration). */
|
|
16
|
+
entity: string;
|
|
17
|
+
/** Columns. When omitted, infers from the first row's keys. */
|
|
18
|
+
columns?: Array<EntityListColumn<T> | string>;
|
|
19
|
+
/** Live filter clause passed straight to `db.useQuery`. */
|
|
20
|
+
where?: Record<string, unknown>;
|
|
21
|
+
/** Server-side ordering. */
|
|
22
|
+
orderBy?: Record<string, "asc" | "desc">;
|
|
23
|
+
/** Max rows. */
|
|
24
|
+
limit?: number;
|
|
25
|
+
/** Click handler — gets the row. */
|
|
26
|
+
onRowClick?: (row: T) => void;
|
|
27
|
+
/** Render when there are zero rows. */
|
|
28
|
+
emptyState?: ReactNode;
|
|
29
|
+
/** Optional caption shown above the table (e.g., "Posts"). */
|
|
30
|
+
title?: ReactNode;
|
|
31
|
+
/** Render arbitrary actions in the top-right (e.g., a "+ New" button). */
|
|
32
|
+
actions?: ReactNode;
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Reactive table for any entity. Wraps `db.useQuery` so rows re-render
|
|
37
|
+
* live when the underlying data changes (insert / update / delete from
|
|
38
|
+
* any peer). Columns are explicit; inference is a fallback for "just
|
|
39
|
+
* give me something" demos.
|
|
40
|
+
*
|
|
41
|
+
* ```tsx
|
|
42
|
+
* <EntityList
|
|
43
|
+
* entity="Post"
|
|
44
|
+
* columns={["title", "author", { field: "createdAt", render: relative }]}
|
|
45
|
+
* where={{ status: "published" }}
|
|
46
|
+
* onRowClick={(post) => router.push(`/posts/${post.id}`)}
|
|
47
|
+
* />
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function EntityList<T extends Row = Row>({ entity, columns, where, orderBy, limit, onRowClick, emptyState, title, actions, className, }: EntityListProps<T>): React.JSX.Element;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { type UploadedFile } from "@pylonsync/react";
|
|
4
|
+
export interface FileUploadProps {
|
|
5
|
+
/** Called once for each successfully uploaded file. */
|
|
6
|
+
onUploaded?: (file: UploadedFile, source: File) => void;
|
|
7
|
+
/** Called once with the full batch after all files finish (errors and all). */
|
|
8
|
+
onComplete?: (results: FileUploadResult[]) => void;
|
|
9
|
+
/** MIME / extension hint passed to `<input accept=...>`. */
|
|
10
|
+
accept?: string;
|
|
11
|
+
/** Allow multi-file selection / drop. Default: false. */
|
|
12
|
+
multiple?: boolean;
|
|
13
|
+
/** Max file size in bytes — files exceeding this are rejected client-side. */
|
|
14
|
+
maxSizeBytes?: number;
|
|
15
|
+
/** Override the headline. */
|
|
16
|
+
label?: ReactNode;
|
|
17
|
+
/** Override the helper text. */
|
|
18
|
+
helperText?: ReactNode;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface FileUploadResult {
|
|
22
|
+
file: File;
|
|
23
|
+
status: "ok" | "error";
|
|
24
|
+
uploaded?: UploadedFile;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Drop-in drag-and-drop file picker. Streams each file to
|
|
29
|
+
* `/api/files/upload` via the existing `uploadFile` helper, surfaces
|
|
30
|
+
* per-file progress + errors, and emits `onUploaded` as each file
|
|
31
|
+
* lands.
|
|
32
|
+
*
|
|
33
|
+
* Pure UI shell — no provider configuration of its own. Pylon's file
|
|
34
|
+
* router handles where bytes actually go (local disk for self-host,
|
|
35
|
+
* Stack0 / S3 for cloud).
|
|
36
|
+
*/
|
|
37
|
+
export declare function FileUpload({ onUploaded, onComplete, accept, multiple, maxSizeBytes, label, helperText, className, }: FileUploadProps): React.JSX.Element;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { useAuth } from "../hooks/useAuth";
|
|
4
|
+
/**
|
|
5
|
+
* Render children only when the user is signed in. Drop-in match for
|
|
6
|
+
* Clerk's `<SignedIn>`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function SignedIn({ children }: {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}): React.JSX.Element | null;
|
|
11
|
+
/**
|
|
12
|
+
* Render children only when the user is signed out.
|
|
13
|
+
*/
|
|
14
|
+
export declare function SignedOut({ children }: {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}): React.JSX.Element | null;
|
|
17
|
+
export interface ProtectProps {
|
|
18
|
+
/** Show fallback (or null) when the predicate fails. */
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
/** What to render when the user isn't authorized. Defaults to nothing. */
|
|
21
|
+
fallback?: ReactNode;
|
|
22
|
+
/** Require admin. Equivalent to passing `predicate={(a) => a.isAdmin}`. */
|
|
23
|
+
admin?: boolean;
|
|
24
|
+
/** Custom gate. Receives the full `useAuth()` shape. */
|
|
25
|
+
predicate?: (auth: ReturnType<typeof useAuth>) => boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Gate children behind a predicate. Defaults to "must be signed in";
|
|
29
|
+
* pass `admin` or a custom `predicate` for finer control. Server-side
|
|
30
|
+
* authorization is still enforced by the policy layer — this is purely a
|
|
31
|
+
* UX convenience to avoid flashing forbidden content.
|
|
32
|
+
*/
|
|
33
|
+
export declare function Protect({ children, fallback, admin, predicate, }: ProtectProps): React.JSX.Element;
|
|
34
|
+
export interface HasRoleProps {
|
|
35
|
+
/** Role string (e.g., "owner", "admin", "member"). Matches against the
|
|
36
|
+
* `roles` array on the resolved session, which the framework populates
|
|
37
|
+
* with the user's org role when an org is active. */
|
|
38
|
+
role: string | string[];
|
|
39
|
+
/** Match ANY (default) or ALL listed roles. */
|
|
40
|
+
mode?: "any" | "all";
|
|
41
|
+
children: ReactNode;
|
|
42
|
+
fallback?: ReactNode;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Render children only when the resolved session has one (or all) of the
|
|
46
|
+
* given roles. Server-side authorization is still the source of truth —
|
|
47
|
+
* this is purely a UX gate to hide buttons the API would refuse anyway.
|
|
48
|
+
*/
|
|
49
|
+
export declare function HasRole({ role, mode, children, fallback, }: HasRoleProps): React.JSX.Element;
|
|
50
|
+
/** Render children only when the user has an active org selected. */
|
|
51
|
+
export declare function InOrg({ children, fallback, }: {
|
|
52
|
+
children: ReactNode;
|
|
53
|
+
fallback?: ReactNode;
|
|
54
|
+
}): React.JSX.Element;
|
|
55
|
+
/** Render children only when no org is selected (personal mode). */
|
|
56
|
+
export declare function NoOrg({ children, fallback, }: {
|
|
57
|
+
children: ReactNode;
|
|
58
|
+
fallback?: ReactNode;
|
|
59
|
+
}): React.JSX.Element;
|
|
60
|
+
export interface RedirectToSignInProps {
|
|
61
|
+
/** Sign-in route (default: "/sign-in"). */
|
|
62
|
+
signInUrl?: string;
|
|
63
|
+
/** Encode the current URL into a `next` param so the sign-in page can
|
|
64
|
+
* bounce the user back. Default: true. */
|
|
65
|
+
preserveLocation?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Imperative client-side redirect — drop inside `<SignedOut>` to send an
|
|
69
|
+
* anonymous visitor to the sign-in page. Uses `window.location.assign`
|
|
70
|
+
* (full nav) since we don't assume the consumer's router shape; apps
|
|
71
|
+
* that want a soft navigation should write their own using `useAuth`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function RedirectToSignIn({ signInUrl, preserveLocation, }: RedirectToSignInProps): null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type InviteResult } from "../lib/api";
|
|
3
|
+
export interface InviteMembersProps {
|
|
4
|
+
/** Org to manage. Defaults to the user's active tenant. */
|
|
5
|
+
orgId?: string;
|
|
6
|
+
/** Roles offered in the role picker. Default: ["member", "admin"]. */
|
|
7
|
+
roles?: string[];
|
|
8
|
+
/** Hide the members roster (focus solely on the invite form + pending list). */
|
|
9
|
+
hideMembers?: boolean;
|
|
10
|
+
/** Hide the pending invites list. */
|
|
11
|
+
hidePending?: boolean;
|
|
12
|
+
/** Optional callback after a new invite is sent. */
|
|
13
|
+
onInvited?: (invite: InviteResult) => void;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Drop-in members + invites surface. Owner / admin only — non-managers
|
|
18
|
+
* see a forbidden notice. Composes three concerns:
|
|
19
|
+
*
|
|
20
|
+
* - Invite form (POST /api/auth/orgs/:id/invites)
|
|
21
|
+
* - Pending invites with revoke (GET + DELETE on the same path)
|
|
22
|
+
* - Member roster with role change + remove (GET/PUT/DELETE on members)
|
|
23
|
+
*
|
|
24
|
+
* For a focused subset, set `hideMembers` or `hidePending`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function InviteMembers({ orgId: orgIdOverride, roles, hideMembers, hidePending, onInvited, className, }: InviteMembersProps): React.JSX.Element | null;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { type OrgSummary } from "../lib/api";
|
|
4
|
+
export interface OrganizationSwitcherProps {
|
|
5
|
+
/** Hide the "Personal account" entry (no active org). Default: visible. */
|
|
6
|
+
hidePersonal?: boolean;
|
|
7
|
+
/** Disable the "Create organization" menu entry. */
|
|
8
|
+
hideCreate?: boolean;
|
|
9
|
+
/** Called after a switch lands. Useful for analytics / route changes. */
|
|
10
|
+
onSwitched?: (orgId: string | null) => void;
|
|
11
|
+
/** Called after a new org is created (post-creation, pre-switch). */
|
|
12
|
+
onCreated?: (org: OrgSummary) => void | Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Active org name resolved on the server (e.g. `serverData.get("Org", …)`
|
|
15
|
+
* in an SSR layout). Seeds the trigger label so it renders the real name on
|
|
16
|
+
* the first paint — no "Personal" → real-name flash, and no pop-in waiting
|
|
17
|
+
* for `useAuth()`/`listOrgs()` to hydrate. The dropdown list still loads
|
|
18
|
+
* client-side, but it's hidden until opened.
|
|
19
|
+
*/
|
|
20
|
+
initialActiveName?: string;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Drop-in org switcher. Lists the signed-in user's orgs from
|
|
25
|
+
* `/api/auth/orgs`, lets them switch via `selectOrg` from useSession,
|
|
26
|
+
* and inline-creates a new org via `/api/auth/orgs` POST.
|
|
27
|
+
*
|
|
28
|
+
* Renders nothing for signed-out users — wrap in <SignedIn> if you
|
|
29
|
+
* need that. Apps that don't use the framework's built-in Org / OrgMember
|
|
30
|
+
* entities will see an empty list (and just the create form, if enabled).
|
|
31
|
+
*/
|
|
32
|
+
export declare function OrganizationSwitcher({ hidePersonal, hideCreate, onSwitched, onCreated, initialActiveName, className, }: OrganizationSwitcherProps): React.JSX.Element | null;
|
|
33
|
+
export interface CreateOrganizationProps {
|
|
34
|
+
onCreated?: (org: OrgSummary) => void | Promise<void>;
|
|
35
|
+
/** Title shown above the form. */
|
|
36
|
+
title?: ReactNode;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function CreateOrganization({ onCreated, title, className, }: CreateOrganizationProps): React.JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface ForgotPasswordProps {
|
|
4
|
+
/** Return route shown as "back to sign-in" link. */
|
|
5
|
+
signInUrl?: string;
|
|
6
|
+
/** Override headline. */
|
|
7
|
+
title?: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* "Forgot password" entry point. POSTs `/api/auth/password/reset/request`
|
|
12
|
+
* and always shows a generic success screen — the server returns 200
|
|
13
|
+
* regardless of whether the email is registered (account-enumeration
|
|
14
|
+
* defense), so we mirror that here.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ForgotPassword({ signInUrl, title, className, }: ForgotPasswordProps): React.JSX.Element;
|
|
17
|
+
export interface ResetPasswordProps {
|
|
18
|
+
/** Token from the reset email. Falls back to `?token=` in URL. */
|
|
19
|
+
token?: string;
|
|
20
|
+
/** Where to send the user after a successful reset. Default: `/sign-in`. */
|
|
21
|
+
afterResetUrl?: string;
|
|
22
|
+
title?: ReactNode;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function ResetPassword({ token: tokenProp, afterResetUrl, title, className, }: ResetPasswordProps): React.JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface SignInProps {
|
|
4
|
+
/** Auth method to lead with. Default: "magic" (most secure default). */
|
|
5
|
+
method?: "magic" | "password";
|
|
6
|
+
/** Where the dashboard sends the user after sign-in. */
|
|
7
|
+
afterSignInUrl?: string;
|
|
8
|
+
/** Optional callback once a session is minted. */
|
|
9
|
+
onSignedIn?: () => void;
|
|
10
|
+
/** Forgot-password landing route shown when the password tab is active. */
|
|
11
|
+
forgotPasswordUrl?: string;
|
|
12
|
+
/** Text shown above the form. */
|
|
13
|
+
title?: ReactNode;
|
|
14
|
+
/** Subtitle / call-to-action. */
|
|
15
|
+
subtitle?: ReactNode;
|
|
16
|
+
/** Tailwind class merged onto the card. */
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function SignIn({ method, afterSignInUrl, onSignedIn, forgotPasswordUrl, title, subtitle, className, }: SignInProps): React.JSX.Element;
|
|
20
|
+
export interface SignUpProps extends SignInProps {
|
|
21
|
+
}
|
|
22
|
+
export declare function SignUp({ afterSignInUrl, onSignedIn, title, subtitle, className, }: SignUpProps): React.JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface SignOutButtonProps {
|
|
4
|
+
afterSignOutUrl?: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Headless-friendly sign-out button. Pass children to fully customize
|
|
10
|
+
* the rendered label/UI; default renders a plain "Sign out" button.
|
|
11
|
+
*/
|
|
12
|
+
export declare function SignOutButton({ afterSignOutUrl, className, children, }: SignOutButtonProps): React.JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface UserButtonProps {
|
|
4
|
+
/** Where to send the user after sign-out. Default: current page reload. */
|
|
5
|
+
afterSignOutUrl?: string;
|
|
6
|
+
/** Show the user's name next to the avatar (Clerk's `showName`). */
|
|
7
|
+
showName?: boolean;
|
|
8
|
+
/** Extra menu items rendered above the built-in entries. */
|
|
9
|
+
menuItems?: Array<{
|
|
10
|
+
label: ReactNode;
|
|
11
|
+
onClick?: () => void;
|
|
12
|
+
href?: string;
|
|
13
|
+
}>;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Avatar dropdown with profile/sign-out, mirroring Clerk's `<UserButton />`.
|
|
18
|
+
* Renders nothing when the user isn't signed in — wrap the call site in
|
|
19
|
+
* `<SignedIn>` or check `useAuth()` if you want a sign-in CTA instead.
|
|
20
|
+
*/
|
|
21
|
+
export declare function UserButton({ afterSignOutUrl, showName, menuItems, className, }: UserButtonProps): React.JSX.Element | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface UserProfileProps {
|
|
3
|
+
/** Hide sections you don't need. */
|
|
4
|
+
hidePassword?: boolean;
|
|
5
|
+
hideSessions?: boolean;
|
|
6
|
+
hideApiKeys?: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Drop-in account management surface — Clerk's `<UserProfile />` shape.
|
|
11
|
+
* Sections:
|
|
12
|
+
* - Identity card (user_id, active org)
|
|
13
|
+
* - Password change (skipped for OAuth-only accounts)
|
|
14
|
+
* - Active sessions + "Sign out other devices"
|
|
15
|
+
* - API keys (create / list / revoke)
|
|
16
|
+
*
|
|
17
|
+
* Each section can be hidden via props if the consuming app handles it
|
|
18
|
+
* elsewhere. Renders nothing for signed-out users.
|
|
19
|
+
*/
|
|
20
|
+
export declare function UserProfile({ hidePassword, hideSessions, hideApiKeys, className, }: UserProfileProps): React.JSX.Element | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop-in hook that wraps `useSession(db.sync)` so components in
|
|
3
|
+
* `@pylonsync/client` don't have to thread a SyncEngine through props.
|
|
4
|
+
* Apps that init() the global engine (the common case) get a Clerk-style
|
|
5
|
+
* `const { isSignedIn, user } = useAuth()` surface.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useAuth(): {
|
|
8
|
+
isSignedIn: boolean;
|
|
9
|
+
isLoaded: boolean;
|
|
10
|
+
userId: string | null;
|
|
11
|
+
tenantId: string | null;
|
|
12
|
+
isAdmin: boolean;
|
|
13
|
+
session: import("@pylonsync/sync").ResolvedSession;
|
|
14
|
+
signOut: () => Promise<void>;
|
|
15
|
+
selectOrg: (orgId: string) => Promise<void>;
|
|
16
|
+
clearOrg: () => Promise<void>;
|
|
17
|
+
refresh: () => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
export type UseAuthReturn = ReturnType<typeof useAuth>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export { SignIn, SignUp } from "./components/SignIn";
|
|
2
|
+
export type { SignInProps, SignUpProps } from "./components/SignIn";
|
|
3
|
+
export { SignedIn, SignedOut, Protect, HasRole, InOrg, NoOrg, RedirectToSignIn, } from "./components/Gates";
|
|
4
|
+
export type { ProtectProps, HasRoleProps, RedirectToSignInProps, } from "./components/Gates";
|
|
5
|
+
export { UserButton } from "./components/UserButton";
|
|
6
|
+
export type { UserButtonProps } from "./components/UserButton";
|
|
7
|
+
export { SignOutButton } from "./components/SignOutButton";
|
|
8
|
+
export type { SignOutButtonProps } from "./components/SignOutButton";
|
|
9
|
+
export { EnsureGuest } from "./components/EnsureGuest";
|
|
10
|
+
export type { EnsureGuestProps } from "./components/EnsureGuest";
|
|
11
|
+
export { OrganizationSwitcher, CreateOrganization, } from "./components/OrganizationSwitcher";
|
|
12
|
+
export type { OrganizationSwitcherProps, CreateOrganizationProps, } from "./components/OrganizationSwitcher";
|
|
13
|
+
export { InviteMembers } from "./components/InviteMembers";
|
|
14
|
+
export type { InviteMembersProps } from "./components/InviteMembers";
|
|
15
|
+
export { AcceptInvite } from "./components/AcceptInvite";
|
|
16
|
+
export type { AcceptInviteProps } from "./components/AcceptInvite";
|
|
17
|
+
export { ConnectAccount } from "./components/ConnectAccount";
|
|
18
|
+
export type { ConnectAccountProps } from "./components/ConnectAccount";
|
|
19
|
+
export { FileUpload } from "./components/FileUpload";
|
|
20
|
+
export type { FileUploadProps, FileUploadResult, } from "./components/FileUpload";
|
|
21
|
+
export { UserProfile } from "./components/UserProfile";
|
|
22
|
+
export type { UserProfileProps } from "./components/UserProfile";
|
|
23
|
+
export { ForgotPassword, ResetPassword } from "./components/PasswordReset";
|
|
24
|
+
export type { ForgotPasswordProps, ResetPasswordProps, } from "./components/PasswordReset";
|
|
25
|
+
export { ChatBot } from "./components/ChatBot";
|
|
26
|
+
export type { ChatBotProps, ChatMessage } from "./components/ChatBot";
|
|
27
|
+
export { EntityList } from "./components/EntityList";
|
|
28
|
+
export type { EntityListProps, EntityListColumn, } from "./components/EntityList";
|
|
29
|
+
export { EntityForm } from "./components/EntityForm";
|
|
30
|
+
export type { EntityFormProps, EntityFormField, } from "./components/EntityForm";
|
|
31
|
+
export { Router, Link, Outlet } from "./router/Router";
|
|
32
|
+
export type { RouterProps, LinkProps } from "./router/Router";
|
|
33
|
+
export type { RouteSpec, MatchedRoute } from "./router/match";
|
|
34
|
+
export { useRouter, useParams, useSearchParams, usePathname, } from "./router/useRouter";
|
|
35
|
+
export { useAuth } from "./hooks/useAuth";
|
|
36
|
+
export type { UseAuthReturn } from "./hooks/useAuth";
|
|
37
|
+
export { acceptInvite, ApiError, changePassword, completePasswordReset, ensureGuestSession, connectionAuthUrl, createApiKey, createInvite, createOrg, deleteOrg, renameOrg, listActiveSessions, listApiKeys, listAuthProviders, listInvites, listOrgMembers, listOrgs, passwordLogin, passwordRegister, persistSession, removeMember, requestPasswordReset, revokeAllSessions, revokeApiKey, revokeInvite, sendMagicLink, updateMemberRole, verifyMagicLink, } from "./lib/api";
|
|
38
|
+
export type { ActiveSession, ApiKeyCreated, ApiKeySummary, AuthProvider, InviteResult, OrgMember, OrgSummary, PendingInvite, SessionResponse, } from "./lib/api";
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export interface AuthProvider {
|
|
2
|
+
provider: string;
|
|
3
|
+
auth_url: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SessionResponse {
|
|
6
|
+
token: string;
|
|
7
|
+
user_id: string;
|
|
8
|
+
expires_at?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface OrgSummary {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
role: "owner" | "admin" | "member" | string;
|
|
14
|
+
created_at: number;
|
|
15
|
+
}
|
|
16
|
+
declare class ApiError extends Error {
|
|
17
|
+
code: string;
|
|
18
|
+
status: number;
|
|
19
|
+
constructor(code: string, message: string, status: number);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Mint a guest session if the browser doesn't already have a Pylon
|
|
23
|
+
* token. Idempotent — calling repeatedly is a no-op once a session
|
|
24
|
+
* exists. Designed for zero-auth demos (`<EnsureGuest>` is the
|
|
25
|
+
* component wrapper); apps that need real users should use `<SignIn>`
|
|
26
|
+
* instead.
|
|
27
|
+
*
|
|
28
|
+
* Each browser gets its own anonymous `user_id`, so multi-tab demos
|
|
29
|
+
* still observe live sync across tabs sharing the same guest. To
|
|
30
|
+
* reset (e.g. "switch identity"), clear the token from storage and
|
|
31
|
+
* call this again.
|
|
32
|
+
*/
|
|
33
|
+
export declare function ensureGuestSession(): Promise<SessionResponse | null>;
|
|
34
|
+
export declare function listAuthProviders(): Promise<AuthProvider[]>;
|
|
35
|
+
export declare function sendMagicLink(email: string, captchaToken?: string): Promise<{
|
|
36
|
+
ok: true;
|
|
37
|
+
}>;
|
|
38
|
+
export declare function verifyMagicLink(email: string, code: string): Promise<SessionResponse>;
|
|
39
|
+
export declare function passwordRegister(input: {
|
|
40
|
+
email: string;
|
|
41
|
+
password: string;
|
|
42
|
+
displayName?: string;
|
|
43
|
+
captchaToken?: string;
|
|
44
|
+
}): Promise<SessionResponse>;
|
|
45
|
+
export declare function passwordLogin(input: {
|
|
46
|
+
email: string;
|
|
47
|
+
password: string;
|
|
48
|
+
}): Promise<SessionResponse>;
|
|
49
|
+
/**
|
|
50
|
+
* Persist a freshly-minted session locally + tell the sync engine to
|
|
51
|
+
* re-fetch /api/auth/me so cached `useSession()` consumers re-render
|
|
52
|
+
* immediately. Components call this after successful sign-in/sign-up.
|
|
53
|
+
*/
|
|
54
|
+
export declare function persistSession(session: SessionResponse): void;
|
|
55
|
+
export declare function listOrgs(): Promise<OrgSummary[]>;
|
|
56
|
+
export declare function createOrg(name: string): Promise<OrgSummary>;
|
|
57
|
+
/** Rename an org. Server gates this to owners + admins (`can_manage_members`). */
|
|
58
|
+
export declare function renameOrg(orgId: string, name: string): Promise<{
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
}>;
|
|
62
|
+
/** Delete an org and its memberships + invites. Server gates this to owners
|
|
63
|
+
* (`can_delete_org`). Irreversible. */
|
|
64
|
+
export declare function deleteOrg(orgId: string): Promise<{
|
|
65
|
+
deleted: boolean;
|
|
66
|
+
}>;
|
|
67
|
+
export interface OrgMember {
|
|
68
|
+
user_id: string;
|
|
69
|
+
role: string;
|
|
70
|
+
joined_at: number;
|
|
71
|
+
/** Member's email, joined server-side from the User entity. `null` if the
|
|
72
|
+
* User row is missing or has no email. */
|
|
73
|
+
email: string | null;
|
|
74
|
+
/** Member's display name (displayName / name / fullName), if set. */
|
|
75
|
+
name: string | null;
|
|
76
|
+
}
|
|
77
|
+
export declare function listOrgMembers(orgId: string): Promise<OrgMember[]>;
|
|
78
|
+
export declare function updateMemberRole(orgId: string, userId: string, role: string): Promise<{
|
|
79
|
+
updated: boolean;
|
|
80
|
+
}>;
|
|
81
|
+
export declare function removeMember(orgId: string, userId: string): Promise<{
|
|
82
|
+
removed: boolean;
|
|
83
|
+
}>;
|
|
84
|
+
export interface PendingInvite {
|
|
85
|
+
id: string;
|
|
86
|
+
email: string;
|
|
87
|
+
role: string;
|
|
88
|
+
token_prefix: string;
|
|
89
|
+
invited_by: string;
|
|
90
|
+
created_at: number;
|
|
91
|
+
expires_at: number;
|
|
92
|
+
}
|
|
93
|
+
export interface InviteResult {
|
|
94
|
+
id: string;
|
|
95
|
+
email: string;
|
|
96
|
+
role: string;
|
|
97
|
+
expires_at: number;
|
|
98
|
+
accept_url: string;
|
|
99
|
+
/** Dev mode only — full token so the inviter can copy/paste when
|
|
100
|
+
* the email transport isn't configured. */
|
|
101
|
+
token?: string;
|
|
102
|
+
}
|
|
103
|
+
export declare function listInvites(orgId: string): Promise<PendingInvite[]>;
|
|
104
|
+
export declare function createInvite(orgId: string, email: string, role: string): Promise<InviteResult>;
|
|
105
|
+
export declare function revokeInvite(orgId: string, inviteId: string): Promise<{
|
|
106
|
+
revoked: boolean;
|
|
107
|
+
}>;
|
|
108
|
+
export declare function acceptInvite(token: string): Promise<{
|
|
109
|
+
org_id: string;
|
|
110
|
+
role: string;
|
|
111
|
+
}>;
|
|
112
|
+
export interface ConnectionAuthUrl {
|
|
113
|
+
url: string;
|
|
114
|
+
}
|
|
115
|
+
export declare function connectionAuthUrl(name: string, postRedirect?: string): Promise<ConnectionAuthUrl>;
|
|
116
|
+
export interface ActiveSession {
|
|
117
|
+
token_prefix: string;
|
|
118
|
+
user_id: string;
|
|
119
|
+
device?: string;
|
|
120
|
+
created_at: number;
|
|
121
|
+
expires_at: number;
|
|
122
|
+
}
|
|
123
|
+
export declare function listActiveSessions(): Promise<ActiveSession[]>;
|
|
124
|
+
export declare function revokeAllSessions(): Promise<{
|
|
125
|
+
revoked_count: number;
|
|
126
|
+
}>;
|
|
127
|
+
export declare function changePassword(input: {
|
|
128
|
+
currentPassword: string;
|
|
129
|
+
newPassword: string;
|
|
130
|
+
}): Promise<{
|
|
131
|
+
ok: true;
|
|
132
|
+
}>;
|
|
133
|
+
export declare function requestPasswordReset(email: string): Promise<{
|
|
134
|
+
sent: true;
|
|
135
|
+
}>;
|
|
136
|
+
export declare function completePasswordReset(input: {
|
|
137
|
+
token: string;
|
|
138
|
+
newPassword: string;
|
|
139
|
+
}): Promise<{
|
|
140
|
+
ok: true;
|
|
141
|
+
}>;
|
|
142
|
+
export interface ApiKeySummary {
|
|
143
|
+
id: string;
|
|
144
|
+
prefix: string;
|
|
145
|
+
name: string;
|
|
146
|
+
scopes?: string | null;
|
|
147
|
+
expires_at?: number | null;
|
|
148
|
+
last_used_at?: number | null;
|
|
149
|
+
created_at: number;
|
|
150
|
+
}
|
|
151
|
+
export interface ApiKeyCreated extends ApiKeySummary {
|
|
152
|
+
/** Shown once on creation. Never returned again. */
|
|
153
|
+
key: string;
|
|
154
|
+
}
|
|
155
|
+
export declare function listApiKeys(): Promise<ApiKeySummary[]>;
|
|
156
|
+
export declare function createApiKey(input: {
|
|
157
|
+
name: string;
|
|
158
|
+
scopes?: string;
|
|
159
|
+
expiresAt?: number;
|
|
160
|
+
}): Promise<ApiKeyCreated>;
|
|
161
|
+
export declare function revokeApiKey(id: string): Promise<{
|
|
162
|
+
revoked: boolean;
|
|
163
|
+
}>;
|
|
164
|
+
export { ApiError };
|
package/dist/lib/cn.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type AnchorHTMLAttributes, type ReactNode } from "react";
|
|
3
|
+
import { type RouteSpec } from "./match";
|
|
4
|
+
export interface RouterProps {
|
|
5
|
+
routes: RouteSpec[];
|
|
6
|
+
/** Render when no route matches. Default: a minimal 404 line. */
|
|
7
|
+
notFound?: ReactNode;
|
|
8
|
+
/** Initial pathname for SSR / testing. Default: window.location.pathname. */
|
|
9
|
+
initialUrl?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Top-level SPA router. Subscribes to `popstate` + intercepted clicks
|
|
13
|
+
* from `<Link />`, matches the URL against the `routes` tree, and
|
|
14
|
+
* renders the matched chain with nested `<Outlet />` support.
|
|
15
|
+
*
|
|
16
|
+
* Place once near the root of the app:
|
|
17
|
+
*
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <Router routes={[
|
|
20
|
+
* { path: "/", component: HomePage },
|
|
21
|
+
* {
|
|
22
|
+
* path: "/dashboard",
|
|
23
|
+
* component: DashboardLayout,
|
|
24
|
+
* requireAuth: true,
|
|
25
|
+
* children: [
|
|
26
|
+
* { path: "/", component: DashboardHome },
|
|
27
|
+
* { path: "/settings", component: SettingsPage },
|
|
28
|
+
* ],
|
|
29
|
+
* },
|
|
30
|
+
* ]} />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function Router({ routes, notFound, initialUrl }: RouterProps): React.JSX.Element;
|
|
34
|
+
/**
|
|
35
|
+
* Renders the next match in the chain — the page nested under the
|
|
36
|
+
* current layout. Without an `<Outlet />` a layout component's
|
|
37
|
+
* children never appear.
|
|
38
|
+
*/
|
|
39
|
+
export declare function Outlet(): React.JSX.Element | null;
|
|
40
|
+
export interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
41
|
+
href: string;
|
|
42
|
+
/** Use replaceState instead of pushState (no history entry added). */
|
|
43
|
+
replace?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* SPA link. Renders a real `<a>` so middle-click / cmd-click / right-
|
|
47
|
+
* click → "open in new tab" all behave correctly. Plain left-click is
|
|
48
|
+
* intercepted and routed via the in-page Router.
|
|
49
|
+
*/
|
|
50
|
+
export declare function Link({ href, replace, onClick, target, rel, children, ...rest }: LinkProps): React.JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MatchedRoute } from "./match";
|
|
2
|
+
export interface RouterContextValue {
|
|
3
|
+
pathname: string;
|
|
4
|
+
search: string;
|
|
5
|
+
hash: string;
|
|
6
|
+
query: Record<string, string>;
|
|
7
|
+
params: Record<string, string>;
|
|
8
|
+
/** The matched chain — root layout to leaf page. */
|
|
9
|
+
matches: MatchedRoute[];
|
|
10
|
+
push: (href: string) => void;
|
|
11
|
+
replace: (href: string) => void;
|
|
12
|
+
back: () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare const RouterContext: import("react").Context<RouterContextValue | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Used internally so a layout's `<Outlet />` knows which depth in the
|
|
17
|
+
* match chain to render next. Apps don't touch this directly.
|
|
18
|
+
*/
|
|
19
|
+
export declare const RouterDepthContext: import("react").Context<number>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export interface RouteSpec {
|
|
3
|
+
/** Path pattern. `/users/:id`, `/posts/:slug?`, `/*` for catch-all. */
|
|
4
|
+
path: string;
|
|
5
|
+
/** Component rendered when matched. */
|
|
6
|
+
component?: () => ReactNode;
|
|
7
|
+
/** Explicit JSX alternative when you don't want a factory. */
|
|
8
|
+
element?: ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Require an authenticated session. `true` = signed in. `"admin"` =
|
|
11
|
+
* signed in AND isAdmin. Custom predicates: render your own gate
|
|
12
|
+
* inside the component instead.
|
|
13
|
+
*/
|
|
14
|
+
requireAuth?: boolean | "admin";
|
|
15
|
+
/** Where to send unauthenticated visitors. Default: `/sign-in`. */
|
|
16
|
+
signInUrl?: string;
|
|
17
|
+
/** Nested routes — parent renders `<Outlet />` where these appear. */
|
|
18
|
+
children?: RouteSpec[];
|
|
19
|
+
}
|
|
20
|
+
export interface MatchedRoute {
|
|
21
|
+
route: RouteSpec;
|
|
22
|
+
params: Record<string, string>;
|
|
23
|
+
/** Tail of the path that wasn't consumed by this route — used to
|
|
24
|
+
* match children. Empty string when the parent consumed the whole
|
|
25
|
+
* path. */
|
|
26
|
+
tail: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Walk the routes tree depth-first, picking the first leaf that
|
|
30
|
+
* fully consumes the pathname. Returns the full match chain
|
|
31
|
+
* (root → ... → leaf) so layouts can render via `<Outlet />`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function matchRoutes(routes: RouteSpec[], pathname: string): MatchedRoute[] | null;
|
|
34
|
+
declare function normalize(p: string): string;
|
|
35
|
+
export { normalize };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type RouterContextValue } from "./context";
|
|
2
|
+
/**
|
|
3
|
+
* Access the router from any descendant of `<Router />`. Returns a
|
|
4
|
+
* stable snapshot of the current URL plus `push` / `replace` / `back`
|
|
5
|
+
* helpers. Throws when used outside a `<Router />` — that's almost
|
|
6
|
+
* always a bug worth surfacing immediately.
|
|
7
|
+
*
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const { pathname, params, push } = useRouter();
|
|
10
|
+
* useEffect(() => { if (!user) push("/sign-in"); }, [user]);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function useRouter(): RouterContextValue;
|
|
14
|
+
/** Shorthand for `useRouter().params`. */
|
|
15
|
+
export declare function useParams<T extends Record<string, string>>(): T;
|
|
16
|
+
/** Shorthand for `useRouter().query`. */
|
|
17
|
+
export declare function useSearchParams(): Record<string, string>;
|
|
18
|
+
/** Shorthand for `useRouter().pathname`. */
|
|
19
|
+
export declare function usePathname(): string;
|
package/package.json
CHANGED
|
@@ -3,24 +3,25 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.293",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "src/index.ts",
|
|
9
|
-
"types": "
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
-
"types": "./
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
13
|
"import": "./src/index.ts"
|
|
14
14
|
},
|
|
15
15
|
"./theme.css": "./src/theme.css"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
|
-
"build": "tsc -p tsconfig.json
|
|
19
|
-
"check": "tsc -p tsconfig.json --noEmit"
|
|
18
|
+
"build": "tsc -p tsconfig.build.json",
|
|
19
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
20
|
+
"prepack": "bun run build"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@pylonsync/react": "0.3.
|
|
23
|
-
"@pylonsync/sync": "0.3.
|
|
23
|
+
"@pylonsync/react": "0.3.293",
|
|
24
|
+
"@pylonsync/sync": "0.3.293",
|
|
24
25
|
"clsx": "^2.1.1"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
@@ -28,5 +29,10 @@
|
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/react": "^19.0.0"
|
|
31
|
-
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"src",
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md"
|
|
37
|
+
]
|
|
32
38
|
}
|