@pylonsync/client 0.3.267
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/README.md +125 -0
- package/package.json +32 -0
- package/src/components/AcceptInvite.tsx +160 -0
- package/src/components/ChatBot.tsx +228 -0
- package/src/components/ConnectAccount.tsx +119 -0
- package/src/components/EnsureGuest.tsx +49 -0
- package/src/components/EntityForm.tsx +308 -0
- package/src/components/EntityList.tsx +203 -0
- package/src/components/FileUpload.tsx +213 -0
- package/src/components/Gates.tsx +139 -0
- package/src/components/InviteMembers.tsx +562 -0
- package/src/components/OrganizationSwitcher.tsx +417 -0
- package/src/components/PasswordReset.tsx +302 -0
- package/src/components/SignIn.tsx +515 -0
- package/src/components/SignOutButton.tsx +42 -0
- package/src/components/UserButton.tsx +163 -0
- package/src/components/UserProfile.tsx +485 -0
- package/src/hooks/useAuth.ts +27 -0
- package/src/index.ts +130 -0
- package/src/lib/api.ts +368 -0
- package/src/lib/cn.ts +7 -0
- package/src/router/Router.tsx +282 -0
- package/src/router/context.ts +25 -0
- package/src/router/match.ts +106 -0
- package/src/router/useRouter.ts +40 -0
- package/src/theme.css +30 -0
- package/tsconfig.json +6 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
export interface RouteSpec {
|
|
6
|
+
/** Path pattern. `/users/:id`, `/posts/:slug?`, `/*` for catch-all. */
|
|
7
|
+
path: string;
|
|
8
|
+
/** Component rendered when matched. */
|
|
9
|
+
component?: () => ReactNode;
|
|
10
|
+
/** Explicit JSX alternative when you don't want a factory. */
|
|
11
|
+
element?: ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* Require an authenticated session. `true` = signed in. `"admin"` =
|
|
14
|
+
* signed in AND isAdmin. Custom predicates: render your own gate
|
|
15
|
+
* inside the component instead.
|
|
16
|
+
*/
|
|
17
|
+
requireAuth?: boolean | "admin";
|
|
18
|
+
/** Where to send unauthenticated visitors. Default: `/sign-in`. */
|
|
19
|
+
signInUrl?: string;
|
|
20
|
+
/** Nested routes — parent renders `<Outlet />` where these appear. */
|
|
21
|
+
children?: RouteSpec[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MatchedRoute {
|
|
25
|
+
route: RouteSpec;
|
|
26
|
+
params: Record<string, string>;
|
|
27
|
+
/** Tail of the path that wasn't consumed by this route — used to
|
|
28
|
+
* match children. Empty string when the parent consumed the whole
|
|
29
|
+
* path. */
|
|
30
|
+
tail: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Walk the routes tree depth-first, picking the first leaf that
|
|
35
|
+
* fully consumes the pathname. Returns the full match chain
|
|
36
|
+
* (root → ... → leaf) so layouts can render via `<Outlet />`.
|
|
37
|
+
*/
|
|
38
|
+
export function matchRoutes(
|
|
39
|
+
routes: RouteSpec[],
|
|
40
|
+
pathname: string,
|
|
41
|
+
): MatchedRoute[] | null {
|
|
42
|
+
const normalized = normalize(pathname);
|
|
43
|
+
for (const route of routes) {
|
|
44
|
+
const here = matchOne(route, normalized);
|
|
45
|
+
if (!here) continue;
|
|
46
|
+
if (route.children && route.children.length > 0) {
|
|
47
|
+
const childChain = matchRoutes(route.children, here.tail);
|
|
48
|
+
if (childChain) return [here, ...childChain];
|
|
49
|
+
// A parent with children must match a child too; otherwise
|
|
50
|
+
// we treat the parent as non-matching for this URL. This
|
|
51
|
+
// avoids rendering a layout with no page inside it.
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// Leaf — require the tail to be empty so we don't half-match.
|
|
55
|
+
if (here.tail === "" || here.tail === "/") return [here];
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function matchOne(route: RouteSpec, pathname: string): MatchedRoute | null {
|
|
61
|
+
const pattern = normalize(route.path);
|
|
62
|
+
const isCatchAll = pattern.endsWith("/*");
|
|
63
|
+
const patternSegments = pattern
|
|
64
|
+
.replace(/\/\*$/, "")
|
|
65
|
+
.split("/")
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
const pathSegments = pathname.split("/").filter(Boolean);
|
|
68
|
+
|
|
69
|
+
const params: Record<string, string> = {};
|
|
70
|
+
let i = 0;
|
|
71
|
+
for (; i < patternSegments.length; i++) {
|
|
72
|
+
const p = patternSegments[i]!;
|
|
73
|
+
const seg = pathSegments[i];
|
|
74
|
+
if (p.startsWith(":")) {
|
|
75
|
+
const optional = p.endsWith("?");
|
|
76
|
+
const name = p.slice(1).replace(/\?$/, "");
|
|
77
|
+
if (seg === undefined) {
|
|
78
|
+
if (optional) continue;
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
params[name] = decodeURIComponent(seg);
|
|
82
|
+
} else {
|
|
83
|
+
if (seg === undefined || seg !== p) return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (isCatchAll) {
|
|
88
|
+
const rest = pathSegments.slice(i).join("/");
|
|
89
|
+
params["*"] = rest;
|
|
90
|
+
return { route, params, tail: "" };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const tail = "/" + pathSegments.slice(i).join("/");
|
|
94
|
+
// When children are declared we leave the tail for them; otherwise
|
|
95
|
+
// the leaf check above requires tail === "".
|
|
96
|
+
return { route, params, tail: tail === "/" ? "" : tail };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalize(p: string): string {
|
|
100
|
+
if (!p) return "/";
|
|
101
|
+
const trimmed = p.replace(/\/+$/, "");
|
|
102
|
+
if (!trimmed.startsWith("/")) return `/${trimmed}`;
|
|
103
|
+
return trimmed === "" ? "/" : trimmed;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { normalize };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useContext } from "react";
|
|
4
|
+
import { RouterContext, type RouterContextValue } from "./context";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Access the router from any descendant of `<Router />`. Returns a
|
|
8
|
+
* stable snapshot of the current URL plus `push` / `replace` / `back`
|
|
9
|
+
* helpers. Throws when used outside a `<Router />` — that's almost
|
|
10
|
+
* always a bug worth surfacing immediately.
|
|
11
|
+
*
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const { pathname, params, push } = useRouter();
|
|
14
|
+
* useEffect(() => { if (!user) push("/sign-in"); }, [user]);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function useRouter(): RouterContextValue {
|
|
18
|
+
const ctx = useContext(RouterContext);
|
|
19
|
+
if (!ctx) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"[@pylonsync/client] useRouter() must be called inside a <Router /> tree",
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return ctx;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Shorthand for `useRouter().params`. */
|
|
28
|
+
export function useParams<T extends Record<string, string>>(): T {
|
|
29
|
+
return useRouter().params as T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Shorthand for `useRouter().query`. */
|
|
33
|
+
export function useSearchParams(): Record<string, string> {
|
|
34
|
+
return useRouter().query;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Shorthand for `useRouter().pathname`. */
|
|
38
|
+
export function usePathname(): string {
|
|
39
|
+
return useRouter().pathname;
|
|
40
|
+
}
|
package/src/theme.css
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* @pylonsync/client — default theme tokens.
|
|
2
|
+
Override any of these CSS vars at the document root (or any ancestor
|
|
3
|
+
of the auth components) to restyle the entire suite. Tailwind classes
|
|
4
|
+
in the components reference these via var(--pylon-*) fallbacks so the
|
|
5
|
+
package works zero-config without Tailwind too. */
|
|
6
|
+
:root {
|
|
7
|
+
--pylon-ink: #0a0a0a;
|
|
8
|
+
--pylon-ink-2: #52525b;
|
|
9
|
+
--pylon-ink-3: #71717a;
|
|
10
|
+
--pylon-paper: #ffffff;
|
|
11
|
+
--pylon-paper-2: #f4f4f5;
|
|
12
|
+
--pylon-rule: #e5e7eb;
|
|
13
|
+
--pylon-error-ink: #b91c1c;
|
|
14
|
+
--pylon-error-bg: #fef2f2;
|
|
15
|
+
--pylon-error-rule: #fecaca;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@media (prefers-color-scheme: dark) {
|
|
19
|
+
:root {
|
|
20
|
+
--pylon-ink: #fafafa;
|
|
21
|
+
--pylon-ink-2: #a1a1aa;
|
|
22
|
+
--pylon-ink-3: #71717a;
|
|
23
|
+
--pylon-paper: #0a0a0a;
|
|
24
|
+
--pylon-paper-2: #18181b;
|
|
25
|
+
--pylon-rule: #27272a;
|
|
26
|
+
--pylon-error-ink: #fca5a5;
|
|
27
|
+
--pylon-error-bg: #450a0a;
|
|
28
|
+
--pylon-error-rule: #7f1d1d;
|
|
29
|
+
}
|
|
30
|
+
}
|