@realiizlabs/admin 0.11.2 → 0.13.0
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/Sidebar-CNuphww6.d.cts +60 -0
- package/dist/Sidebar-CNuphww6.d.ts +60 -0
- package/dist/auth/index.d.cts +5 -127
- package/dist/auth/index.d.ts +5 -127
- package/dist/auth-ui/index.js +124 -1
- package/dist/auth-ui/index.js.map +1 -1
- package/dist/forms/index.js +287 -1
- package/dist/forms/index.js.map +1 -1
- package/dist/forms-ui/index.js +355 -6
- package/dist/forms-ui/index.js.map +1 -1
- package/dist/git/index.cjs.map +1 -1
- package/dist/git/index.d.cts +3 -77
- package/dist/git/index.d.ts +3 -77
- package/dist/git/index.js.map +1 -1
- package/dist/members-nhbRSYyr.d.cts +106 -0
- package/dist/members-nhbRSYyr.d.ts +106 -0
- package/dist/roles-BjWuj2_v.d.cts +25 -0
- package/dist/roles-Dnwj-DAm.d.ts +25 -0
- package/dist/shell/index.d.cts +3 -58
- package/dist/shell/index.d.ts +3 -58
- package/dist/studio/index.cjs +1150 -0
- package/dist/studio/index.cjs.map +1 -0
- package/dist/studio/index.d.cts +325 -0
- package/dist/studio/index.d.ts +325 -0
- package/dist/studio/index.js +1131 -0
- package/dist/studio/index.js.map +1 -0
- package/dist/studio-ui/index.cjs +3931 -0
- package/dist/studio-ui/index.cjs.map +1 -0
- package/dist/studio-ui/index.d.cts +375 -0
- package/dist/studio-ui/index.d.ts +375 -0
- package/dist/studio-ui/index.js +3900 -0
- package/dist/studio-ui/index.js.map +1 -0
- package/dist/types-BP5G1myE.d.cts +78 -0
- package/dist/types-BP5G1myE.d.ts +78 -0
- package/package.json +20 -2
- package/dist/chunk-2GSYBARR.js +0 -126
- package/dist/chunk-2GSYBARR.js.map +0 -1
- package/dist/chunk-JLR5K6RP.js +0 -289
- package/dist/chunk-JLR5K6RP.js.map +0 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Light / dark, remembered per browser. The attribute lives on the .rz-admin
|
|
5
|
+
* root (not <html>) so the host site's own theme is never touched.
|
|
6
|
+
*
|
|
7
|
+
* No flash of the wrong theme: the server renders defaultTheme, and the effect
|
|
8
|
+
* only flips when a stored value differs — a single client-side change, never
|
|
9
|
+
* a mismatch between server HTML and first client render.
|
|
10
|
+
*/
|
|
11
|
+
type Theme = "light" | "dark";
|
|
12
|
+
declare function useTheme(defaultTheme?: Theme): [Theme, (t: Theme) => void];
|
|
13
|
+
declare function ThemeToggle({ theme, onChange }: {
|
|
14
|
+
theme: Theme;
|
|
15
|
+
onChange: (t: Theme) => void;
|
|
16
|
+
}): react.JSX.Element;
|
|
17
|
+
|
|
18
|
+
interface ShellUser {
|
|
19
|
+
email: string;
|
|
20
|
+
name?: string | null;
|
|
21
|
+
/** Profile photo; initials show when absent. */
|
|
22
|
+
avatarUrl?: string | null;
|
|
23
|
+
}
|
|
24
|
+
declare function firstNameOf(user: ShellUser): string;
|
|
25
|
+
declare function initialsOf(user: ShellUser): string;
|
|
26
|
+
declare function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, helpHref }: {
|
|
27
|
+
user: ShellUser;
|
|
28
|
+
role: string;
|
|
29
|
+
theme: Theme;
|
|
30
|
+
onTheme: (t: Theme) => void;
|
|
31
|
+
signOutPath: string;
|
|
32
|
+
/** The Account page (My Profile · Change Password · Team · Business). Omit to hide the item. */
|
|
33
|
+
accountHref?: string;
|
|
34
|
+
/** The Help centre. Omit to hide the item. */
|
|
35
|
+
helpHref?: string;
|
|
36
|
+
}): react.JSX.Element;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Sidebar — items from the content-type registry (via navFromContentTypes),
|
|
40
|
+
* active by href prefix, collapsible with the state remembered per browser.
|
|
41
|
+
* Plain anchors: the package does not know which router the host uses.
|
|
42
|
+
*/
|
|
43
|
+
interface NavItem {
|
|
44
|
+
id: string;
|
|
45
|
+
label: string;
|
|
46
|
+
href: string;
|
|
47
|
+
/** Icon id; unknown ids fall back to a document icon. Defaults to `id`. */
|
|
48
|
+
icon?: string;
|
|
49
|
+
/** Shown greyed out and not navigable — a section that is coming but not built yet. */
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
}
|
|
52
|
+
declare function isActive(item: NavItem, activeHref: string, basePath: string): boolean;
|
|
53
|
+
declare function Sidebar({ items, pinned, activeHref, basePath }: {
|
|
54
|
+
items: NavItem[];
|
|
55
|
+
pinned?: NavItem[];
|
|
56
|
+
activeHref: string;
|
|
57
|
+
basePath: string;
|
|
58
|
+
}): react.JSX.Element;
|
|
59
|
+
|
|
60
|
+
export { AccountMenu as A, type NavItem as N, type ShellUser as S, type Theme as T, Sidebar as a, ThemeToggle as b, isActive as c, firstNameOf as f, initialsOf as i, useTheme as u };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Light / dark, remembered per browser. The attribute lives on the .rz-admin
|
|
5
|
+
* root (not <html>) so the host site's own theme is never touched.
|
|
6
|
+
*
|
|
7
|
+
* No flash of the wrong theme: the server renders defaultTheme, and the effect
|
|
8
|
+
* only flips when a stored value differs — a single client-side change, never
|
|
9
|
+
* a mismatch between server HTML and first client render.
|
|
10
|
+
*/
|
|
11
|
+
type Theme = "light" | "dark";
|
|
12
|
+
declare function useTheme(defaultTheme?: Theme): [Theme, (t: Theme) => void];
|
|
13
|
+
declare function ThemeToggle({ theme, onChange }: {
|
|
14
|
+
theme: Theme;
|
|
15
|
+
onChange: (t: Theme) => void;
|
|
16
|
+
}): react.JSX.Element;
|
|
17
|
+
|
|
18
|
+
interface ShellUser {
|
|
19
|
+
email: string;
|
|
20
|
+
name?: string | null;
|
|
21
|
+
/** Profile photo; initials show when absent. */
|
|
22
|
+
avatarUrl?: string | null;
|
|
23
|
+
}
|
|
24
|
+
declare function firstNameOf(user: ShellUser): string;
|
|
25
|
+
declare function initialsOf(user: ShellUser): string;
|
|
26
|
+
declare function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, helpHref }: {
|
|
27
|
+
user: ShellUser;
|
|
28
|
+
role: string;
|
|
29
|
+
theme: Theme;
|
|
30
|
+
onTheme: (t: Theme) => void;
|
|
31
|
+
signOutPath: string;
|
|
32
|
+
/** The Account page (My Profile · Change Password · Team · Business). Omit to hide the item. */
|
|
33
|
+
accountHref?: string;
|
|
34
|
+
/** The Help centre. Omit to hide the item. */
|
|
35
|
+
helpHref?: string;
|
|
36
|
+
}): react.JSX.Element;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Sidebar — items from the content-type registry (via navFromContentTypes),
|
|
40
|
+
* active by href prefix, collapsible with the state remembered per browser.
|
|
41
|
+
* Plain anchors: the package does not know which router the host uses.
|
|
42
|
+
*/
|
|
43
|
+
interface NavItem {
|
|
44
|
+
id: string;
|
|
45
|
+
label: string;
|
|
46
|
+
href: string;
|
|
47
|
+
/** Icon id; unknown ids fall back to a document icon. Defaults to `id`. */
|
|
48
|
+
icon?: string;
|
|
49
|
+
/** Shown greyed out and not navigable — a section that is coming but not built yet. */
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
}
|
|
52
|
+
declare function isActive(item: NavItem, activeHref: string, basePath: string): boolean;
|
|
53
|
+
declare function Sidebar({ items, pinned, activeHref, basePath }: {
|
|
54
|
+
items: NavItem[];
|
|
55
|
+
pinned?: NavItem[];
|
|
56
|
+
activeHref: string;
|
|
57
|
+
basePath: string;
|
|
58
|
+
}): react.JSX.Element;
|
|
59
|
+
|
|
60
|
+
export { AccountMenu as A, type NavItem as N, type ShellUser as S, type Theme as T, Sidebar as a, ThemeToggle as b, isActive as c, firstNameOf as f, initialsOf as i, useTheme as u };
|
package/dist/auth/index.d.cts
CHANGED
|
@@ -1,69 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Either cookie-store shape a host can hand us:
|
|
8
|
-
* - @supabase/ssr's CookieMethodsServer: { getAll, setAll }
|
|
9
|
-
* - Next's `await cookies()` store: { getAll, set }
|
|
10
|
-
* `setAll` is preferred when present; otherwise each cookie goes through `set`.
|
|
11
|
-
*/
|
|
12
|
-
interface CookieStore {
|
|
13
|
-
getAll(): {
|
|
14
|
-
name: string;
|
|
15
|
-
value: string;
|
|
16
|
-
}[] | Promise<{
|
|
17
|
-
name: string;
|
|
18
|
-
value: string;
|
|
19
|
-
}[]>;
|
|
20
|
-
setAll?(cookies: {
|
|
21
|
-
name: string;
|
|
22
|
-
value: string;
|
|
23
|
-
options?: Record<string, unknown>;
|
|
24
|
-
}[]): void | Promise<void>;
|
|
25
|
-
set?(name: string, value: string, options?: Record<string, unknown>): unknown;
|
|
26
|
-
}
|
|
27
|
-
/** Enough to read the signed-in user with the anon key + their cookies. */
|
|
28
|
-
interface IdentityContext {
|
|
29
|
-
url: string;
|
|
30
|
-
anonKey: string;
|
|
31
|
-
cookies: CookieStore;
|
|
32
|
-
}
|
|
33
|
-
/** Server-only. The service-role key bypasses RLS; the module checks roles in code first. */
|
|
34
|
-
interface ServiceContext {
|
|
35
|
-
url: string;
|
|
36
|
-
serviceRoleKey: string;
|
|
37
|
-
}
|
|
38
|
-
type SiteRole = "staff" | "owner" | "editor";
|
|
39
|
-
declare const ROLE_RANK: Record<SiteRole, number>;
|
|
40
|
-
interface IdentityUser {
|
|
41
|
-
id: string;
|
|
42
|
-
email: string | null;
|
|
43
|
-
}
|
|
44
|
-
interface SiteMembership {
|
|
45
|
-
userId: string;
|
|
46
|
-
siteId: string;
|
|
47
|
-
role: "owner" | "editor";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* createIdentityClient — a Supabase client bound to the request's cookies.
|
|
52
|
-
*
|
|
53
|
-
* Built per request from arguments. A Next 16 route does:
|
|
54
|
-
*
|
|
55
|
-
* const store = await cookies();
|
|
56
|
-
* const client = createIdentityClient({ url, anonKey, cookies: store });
|
|
57
|
-
*
|
|
58
|
-
* No `next` import here. The CookieStore interface accepts either shape a
|
|
59
|
-
* host might hand us: @supabase/ssr's { getAll, setAll }, or Next's
|
|
60
|
-
* cookies() store, which has { getAll, set } — the session cookies are
|
|
61
|
-
* written through whichever exists. (Observed 2026-09-08: with only setAll
|
|
62
|
-
* supported, Next's store silently wrote nothing and every sign-in bounced.)
|
|
63
|
-
*/
|
|
64
|
-
|
|
65
|
-
type IdentityClient = SupabaseClient;
|
|
66
|
-
declare function createIdentityClient(ctx: IdentityContext): IdentityClient;
|
|
1
|
+
import { I as IdentityClient, a as IdentityUser, S as ServiceContext } from '../members-nhbRSYyr.cjs';
|
|
2
|
+
export { B as BrandingPatch, C as CookieStore, b as IdentityContext, R as ROLE_RANK, c as SiteBranding, d as SiteMember, e as SiteMembership, f as SiteRole, g as createIdentityClient, h as getSiteBranding, l as listSiteMembers, u as updateSiteBranding } from '../members-nhbRSYyr.cjs';
|
|
3
|
+
export { R as RequireOptions, g as getSiteRole, r as requireSiteUser, a as roleAtLeast } from '../roles-BjWuj2_v.cjs';
|
|
4
|
+
import '@supabase/supabase-js';
|
|
67
5
|
|
|
68
6
|
/**
|
|
69
7
|
* getCurrentUser — who is signed in, verified against the auth server.
|
|
@@ -74,28 +12,6 @@ declare function createIdentityClient(ctx: IdentityContext): IdentityClient;
|
|
|
74
12
|
|
|
75
13
|
declare function getCurrentUser(client: IdentityClient): Promise<IdentityUser | null>;
|
|
76
14
|
|
|
77
|
-
/**
|
|
78
|
-
* Role resolution and the request gate.
|
|
79
|
-
*
|
|
80
|
-
* Two selects on the caller's OWN rows — both permitted by RLS with the anon
|
|
81
|
-
* key, so this never needs the service role. Staff wins over any site role.
|
|
82
|
-
*/
|
|
83
|
-
|
|
84
|
-
declare function getSiteRole(client: IdentityClient, siteId: string, userId?: string): Promise<SiteRole | null>;
|
|
85
|
-
declare function roleAtLeast(actual: SiteRole | null, minimum: SiteRole): boolean;
|
|
86
|
-
interface RequireOptions {
|
|
87
|
-
/** Default "editor" — any member of the site. */
|
|
88
|
-
minimumRole?: SiteRole;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* The gate. Throws UnauthenticatedError (→ sign in) or ForbiddenError (→ 403);
|
|
92
|
-
* otherwise returns the user and their effective role for this site.
|
|
93
|
-
*/
|
|
94
|
-
declare function requireSiteUser(client: IdentityClient, siteId: string, opts?: RequireOptions): Promise<{
|
|
95
|
-
user: IdentityUser;
|
|
96
|
-
role: SiteRole;
|
|
97
|
-
}>;
|
|
98
|
-
|
|
99
15
|
/**
|
|
100
16
|
* handleAuthCallback — the emailed magic link lands here with ?code=…&next=…
|
|
101
17
|
*
|
|
@@ -195,44 +111,6 @@ declare function resendInvite(ctx: ServiceContext, input: ResendInput): Promise<
|
|
|
195
111
|
message: string;
|
|
196
112
|
}>;
|
|
197
113
|
|
|
198
|
-
/**
|
|
199
|
-
* Per-site branding — the Business tab. Reads and writes public.sites through
|
|
200
|
-
* the signed-in user's client, so RLS decides who may (members read; owners
|
|
201
|
-
* and staff write). Nothing here needs the service role.
|
|
202
|
-
*/
|
|
203
|
-
|
|
204
|
-
interface SiteBranding {
|
|
205
|
-
name: string;
|
|
206
|
-
logoUrl: string | null;
|
|
207
|
-
faviconUrl: string | null;
|
|
208
|
-
businessEmail: string | null;
|
|
209
|
-
businessPhone: string | null;
|
|
210
|
-
}
|
|
211
|
-
type BrandingPatch = Partial<SiteBranding> & {
|
|
212
|
-
name: string;
|
|
213
|
-
};
|
|
214
|
-
declare function getSiteBranding(client: IdentityClient, siteId: string): Promise<SiteBranding | null>;
|
|
215
|
-
/** Upsert the row. RLS refuses anyone but the site's owners or staff. */
|
|
216
|
-
declare function updateSiteBranding(client: IdentityClient, siteId: string, patch: BrandingPatch): Promise<void>;
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* The Team roster — public.site_members(site_id), a SECURITY DEFINER function
|
|
220
|
-
* that returns rows only to that site's owners or staff (editors get a 42501).
|
|
221
|
-
* Read with the signed-in user's client; writes are in service.ts.
|
|
222
|
-
*/
|
|
223
|
-
|
|
224
|
-
interface SiteMember {
|
|
225
|
-
userId: string;
|
|
226
|
-
email: string;
|
|
227
|
-
name: string | null;
|
|
228
|
-
avatarUrl: string | null;
|
|
229
|
-
role: "owner" | "editor";
|
|
230
|
-
/** "invited" until the person has signed in once. */
|
|
231
|
-
status: "invited" | "active";
|
|
232
|
-
invitedAt: string;
|
|
233
|
-
}
|
|
234
|
-
declare function listSiteMembers(client: IdentityClient, siteId: string): Promise<SiteMember[]>;
|
|
235
|
-
|
|
236
114
|
/**
|
|
237
115
|
* My Profile and Change Password — the signed-in user editing themselves,
|
|
238
116
|
* through their own client (auth.updateUser). Name and photo live in user
|
|
@@ -277,4 +155,4 @@ declare class ForbiddenError extends IdentityError {
|
|
|
277
155
|
constructor(siteId: string, required: string, actual: string | null);
|
|
278
156
|
}
|
|
279
157
|
|
|
280
|
-
export { type
|
|
158
|
+
export { type CallbackOptions, type CallbackResult, ForbiddenError, IdentityClient, IdentityError, IdentityUser, type InviteInput, type InviteResult, MIN_PASSWORD_LENGTH, PASSWORD_TOO_SHORT, type Profile, type ProfilePatch, type RemoveInput, type ResendInput, type RoleInput, ServiceContext, UnauthenticatedError, getCurrentUser, handleAuthCallback, inviteUser, profileOf, removeSiteUser, resendInvite, safeNextPath, setPassword, signOut, updateProfile, updateSiteRole };
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,69 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Either cookie-store shape a host can hand us:
|
|
8
|
-
* - @supabase/ssr's CookieMethodsServer: { getAll, setAll }
|
|
9
|
-
* - Next's `await cookies()` store: { getAll, set }
|
|
10
|
-
* `setAll` is preferred when present; otherwise each cookie goes through `set`.
|
|
11
|
-
*/
|
|
12
|
-
interface CookieStore {
|
|
13
|
-
getAll(): {
|
|
14
|
-
name: string;
|
|
15
|
-
value: string;
|
|
16
|
-
}[] | Promise<{
|
|
17
|
-
name: string;
|
|
18
|
-
value: string;
|
|
19
|
-
}[]>;
|
|
20
|
-
setAll?(cookies: {
|
|
21
|
-
name: string;
|
|
22
|
-
value: string;
|
|
23
|
-
options?: Record<string, unknown>;
|
|
24
|
-
}[]): void | Promise<void>;
|
|
25
|
-
set?(name: string, value: string, options?: Record<string, unknown>): unknown;
|
|
26
|
-
}
|
|
27
|
-
/** Enough to read the signed-in user with the anon key + their cookies. */
|
|
28
|
-
interface IdentityContext {
|
|
29
|
-
url: string;
|
|
30
|
-
anonKey: string;
|
|
31
|
-
cookies: CookieStore;
|
|
32
|
-
}
|
|
33
|
-
/** Server-only. The service-role key bypasses RLS; the module checks roles in code first. */
|
|
34
|
-
interface ServiceContext {
|
|
35
|
-
url: string;
|
|
36
|
-
serviceRoleKey: string;
|
|
37
|
-
}
|
|
38
|
-
type SiteRole = "staff" | "owner" | "editor";
|
|
39
|
-
declare const ROLE_RANK: Record<SiteRole, number>;
|
|
40
|
-
interface IdentityUser {
|
|
41
|
-
id: string;
|
|
42
|
-
email: string | null;
|
|
43
|
-
}
|
|
44
|
-
interface SiteMembership {
|
|
45
|
-
userId: string;
|
|
46
|
-
siteId: string;
|
|
47
|
-
role: "owner" | "editor";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* createIdentityClient — a Supabase client bound to the request's cookies.
|
|
52
|
-
*
|
|
53
|
-
* Built per request from arguments. A Next 16 route does:
|
|
54
|
-
*
|
|
55
|
-
* const store = await cookies();
|
|
56
|
-
* const client = createIdentityClient({ url, anonKey, cookies: store });
|
|
57
|
-
*
|
|
58
|
-
* No `next` import here. The CookieStore interface accepts either shape a
|
|
59
|
-
* host might hand us: @supabase/ssr's { getAll, setAll }, or Next's
|
|
60
|
-
* cookies() store, which has { getAll, set } — the session cookies are
|
|
61
|
-
* written through whichever exists. (Observed 2026-09-08: with only setAll
|
|
62
|
-
* supported, Next's store silently wrote nothing and every sign-in bounced.)
|
|
63
|
-
*/
|
|
64
|
-
|
|
65
|
-
type IdentityClient = SupabaseClient;
|
|
66
|
-
declare function createIdentityClient(ctx: IdentityContext): IdentityClient;
|
|
1
|
+
import { I as IdentityClient, a as IdentityUser, S as ServiceContext } from '../members-nhbRSYyr.js';
|
|
2
|
+
export { B as BrandingPatch, C as CookieStore, b as IdentityContext, R as ROLE_RANK, c as SiteBranding, d as SiteMember, e as SiteMembership, f as SiteRole, g as createIdentityClient, h as getSiteBranding, l as listSiteMembers, u as updateSiteBranding } from '../members-nhbRSYyr.js';
|
|
3
|
+
export { R as RequireOptions, g as getSiteRole, r as requireSiteUser, a as roleAtLeast } from '../roles-Dnwj-DAm.js';
|
|
4
|
+
import '@supabase/supabase-js';
|
|
67
5
|
|
|
68
6
|
/**
|
|
69
7
|
* getCurrentUser — who is signed in, verified against the auth server.
|
|
@@ -74,28 +12,6 @@ declare function createIdentityClient(ctx: IdentityContext): IdentityClient;
|
|
|
74
12
|
|
|
75
13
|
declare function getCurrentUser(client: IdentityClient): Promise<IdentityUser | null>;
|
|
76
14
|
|
|
77
|
-
/**
|
|
78
|
-
* Role resolution and the request gate.
|
|
79
|
-
*
|
|
80
|
-
* Two selects on the caller's OWN rows — both permitted by RLS with the anon
|
|
81
|
-
* key, so this never needs the service role. Staff wins over any site role.
|
|
82
|
-
*/
|
|
83
|
-
|
|
84
|
-
declare function getSiteRole(client: IdentityClient, siteId: string, userId?: string): Promise<SiteRole | null>;
|
|
85
|
-
declare function roleAtLeast(actual: SiteRole | null, minimum: SiteRole): boolean;
|
|
86
|
-
interface RequireOptions {
|
|
87
|
-
/** Default "editor" — any member of the site. */
|
|
88
|
-
minimumRole?: SiteRole;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* The gate. Throws UnauthenticatedError (→ sign in) or ForbiddenError (→ 403);
|
|
92
|
-
* otherwise returns the user and their effective role for this site.
|
|
93
|
-
*/
|
|
94
|
-
declare function requireSiteUser(client: IdentityClient, siteId: string, opts?: RequireOptions): Promise<{
|
|
95
|
-
user: IdentityUser;
|
|
96
|
-
role: SiteRole;
|
|
97
|
-
}>;
|
|
98
|
-
|
|
99
15
|
/**
|
|
100
16
|
* handleAuthCallback — the emailed magic link lands here with ?code=…&next=…
|
|
101
17
|
*
|
|
@@ -195,44 +111,6 @@ declare function resendInvite(ctx: ServiceContext, input: ResendInput): Promise<
|
|
|
195
111
|
message: string;
|
|
196
112
|
}>;
|
|
197
113
|
|
|
198
|
-
/**
|
|
199
|
-
* Per-site branding — the Business tab. Reads and writes public.sites through
|
|
200
|
-
* the signed-in user's client, so RLS decides who may (members read; owners
|
|
201
|
-
* and staff write). Nothing here needs the service role.
|
|
202
|
-
*/
|
|
203
|
-
|
|
204
|
-
interface SiteBranding {
|
|
205
|
-
name: string;
|
|
206
|
-
logoUrl: string | null;
|
|
207
|
-
faviconUrl: string | null;
|
|
208
|
-
businessEmail: string | null;
|
|
209
|
-
businessPhone: string | null;
|
|
210
|
-
}
|
|
211
|
-
type BrandingPatch = Partial<SiteBranding> & {
|
|
212
|
-
name: string;
|
|
213
|
-
};
|
|
214
|
-
declare function getSiteBranding(client: IdentityClient, siteId: string): Promise<SiteBranding | null>;
|
|
215
|
-
/** Upsert the row. RLS refuses anyone but the site's owners or staff. */
|
|
216
|
-
declare function updateSiteBranding(client: IdentityClient, siteId: string, patch: BrandingPatch): Promise<void>;
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* The Team roster — public.site_members(site_id), a SECURITY DEFINER function
|
|
220
|
-
* that returns rows only to that site's owners or staff (editors get a 42501).
|
|
221
|
-
* Read with the signed-in user's client; writes are in service.ts.
|
|
222
|
-
*/
|
|
223
|
-
|
|
224
|
-
interface SiteMember {
|
|
225
|
-
userId: string;
|
|
226
|
-
email: string;
|
|
227
|
-
name: string | null;
|
|
228
|
-
avatarUrl: string | null;
|
|
229
|
-
role: "owner" | "editor";
|
|
230
|
-
/** "invited" until the person has signed in once. */
|
|
231
|
-
status: "invited" | "active";
|
|
232
|
-
invitedAt: string;
|
|
233
|
-
}
|
|
234
|
-
declare function listSiteMembers(client: IdentityClient, siteId: string): Promise<SiteMember[]>;
|
|
235
|
-
|
|
236
114
|
/**
|
|
237
115
|
* My Profile and Change Password — the signed-in user editing themselves,
|
|
238
116
|
* through their own client (auth.updateUser). Name and photo live in user
|
|
@@ -277,4 +155,4 @@ declare class ForbiddenError extends IdentityError {
|
|
|
277
155
|
constructor(siteId: string, required: string, actual: string | null);
|
|
278
156
|
}
|
|
279
157
|
|
|
280
|
-
export { type
|
|
158
|
+
export { type CallbackOptions, type CallbackResult, ForbiddenError, IdentityClient, IdentityError, IdentityUser, type InviteInput, type InviteResult, MIN_PASSWORD_LENGTH, PASSWORD_TOO_SHORT, type Profile, type ProfilePatch, type RemoveInput, type ResendInput, type RoleInput, ServiceContext, UnauthenticatedError, getCurrentUser, handleAuthCallback, inviteUser, profileOf, removeSiteUser, resendInvite, safeNextPath, setPassword, signOut, updateProfile, updateSiteRole };
|
package/dist/auth-ui/index.js
CHANGED
|
@@ -1,9 +1,132 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { FORM_CSS } from '../chunk-2GSYBARR.js';
|
|
3
2
|
import { createBrowserClient } from '@supabase/ssr';
|
|
4
3
|
import { useMemo, useState } from 'react';
|
|
5
4
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
5
|
|
|
6
|
+
// src/auth-ui/MagicLinkForm.tsx
|
|
7
|
+
|
|
8
|
+
// src/forms-ui/styles.ts
|
|
9
|
+
var FORM_CSS = `
|
|
10
|
+
.realiiz-form{
|
|
11
|
+
--_fg:var(--realiiz-form-fg,#111);
|
|
12
|
+
--_muted:var(--realiiz-form-muted,#666);
|
|
13
|
+
--_border:var(--realiiz-form-border,#cfcfcf);
|
|
14
|
+
--_accent:var(--realiiz-form-accent,#111);
|
|
15
|
+
--_accent-fg:var(--realiiz-form-accent-fg,#fff);
|
|
16
|
+
--_danger:var(--realiiz-form-danger,#b3261e);
|
|
17
|
+
--_radius:var(--realiiz-form-radius,6px);
|
|
18
|
+
--_font:var(--realiiz-form-font,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif);
|
|
19
|
+
--_ring:var(--realiiz-form-focus-ring,0 0 0 3px rgba(0,0,0,.15));
|
|
20
|
+
box-sizing:border-box;max-width:100%;color:var(--_fg);font-family:var(--_font);font-size:15px;line-height:1.4;
|
|
21
|
+
}
|
|
22
|
+
.realiiz-form *,.realiiz-form *::before,.realiiz-form *::after{box-sizing:inherit}
|
|
23
|
+
.realiiz-form fieldset{border:0;margin:0 0 20px;padding:0;min-width:0}
|
|
24
|
+
.realiiz-form legend{font-weight:600;font-size:13px;letter-spacing:.02em;text-transform:uppercase;color:var(--_muted);margin-bottom:12px;padding:0}
|
|
25
|
+
.realiiz-form__field{display:block;margin:0 0 16px}
|
|
26
|
+
.realiiz-form__label{display:block;font-weight:500;margin-bottom:6px}
|
|
27
|
+
.realiiz-form__required{color:var(--_danger);margin-left:3px}
|
|
28
|
+
.realiiz-form__control{display:block;width:100%;max-width:100%;font:inherit;color:inherit;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius);padding:9px 11px;min-height:40px}
|
|
29
|
+
.realiiz-form__control:focus{outline:none;border-color:var(--_accent);box-shadow:var(--_ring)}
|
|
30
|
+
.realiiz-form__control--textarea{min-height:110px;resize:vertical}
|
|
31
|
+
.realiiz-form__control--body{min-height:280px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px}
|
|
32
|
+
.realiiz-form__control--invalid{border-color:var(--_danger)}
|
|
33
|
+
.realiiz-select{position:relative}
|
|
34
|
+
.realiiz-select__btn{display:flex;align-items:center;justify-content:space-between;gap:10px;text-align:left;cursor:pointer;background:#fff}
|
|
35
|
+
.realiiz-select__btn svg{flex-shrink:0;color:var(--_muted);transition:transform .15s}
|
|
36
|
+
.realiiz-select__btn--open{border-color:var(--_accent);box-shadow:var(--_ring)}
|
|
37
|
+
.realiiz-select__btn--open svg{transform:rotate(180deg)}
|
|
38
|
+
.realiiz-select__btn:disabled{opacity:.6;cursor:default}
|
|
39
|
+
.realiiz-select__placeholder{color:var(--_muted)}
|
|
40
|
+
.realiiz-select__menu{position:absolute;left:0;right:0;top:calc(100% + 6px);margin:0;padding:6px 0;list-style:none;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius);box-shadow:0 8px 24px rgba(14,23,38,.14);max-height:260px;overflow:auto;z-index:40}
|
|
41
|
+
.realiiz-select__item{display:flex;align-items:center;gap:8px;padding:8px 12px;font-size:14px;color:var(--_fg);cursor:pointer}
|
|
42
|
+
.realiiz-select__item--active{background:var(--realiiz-form-hover,rgba(0,0,0,.05))}
|
|
43
|
+
.realiiz-select__item--selected{font-weight:600}
|
|
44
|
+
.realiiz-select__check{width:1em;flex-shrink:0;color:var(--_accent);font-weight:700}
|
|
45
|
+
.realiiz-form__check{display:flex;align-items:center;gap:8px;font-weight:500}
|
|
46
|
+
.realiiz-form__check input{width:18px;height:18px;margin:0}
|
|
47
|
+
.realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}
|
|
48
|
+
.realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}
|
|
49
|
+
.realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}
|
|
50
|
+
.realiiz-image{border:1px dashed var(--_border);border-radius:var(--_radius);padding:16px 18px;background:#fff;outline:none;transition:border-color .15s}
|
|
51
|
+
.realiiz-image--over{border-color:var(--_accent);border-style:solid}
|
|
52
|
+
.realiiz-image--invalid{border-color:var(--_danger)}
|
|
53
|
+
.realiiz-image--busy{opacity:.7}
|
|
54
|
+
.realiiz-image__empty{display:flex;align-items:center;gap:14px;flex-wrap:wrap;color:var(--_muted)}
|
|
55
|
+
.realiiz-image__icon{flex-shrink:0;color:var(--_muted)}
|
|
56
|
+
.realiiz-image__or{font-size:13px}
|
|
57
|
+
.realiiz-image__has{display:flex;align-items:flex-start;gap:16px}
|
|
58
|
+
.realiiz-image__thumb{display:block;max-height:160px;max-width:min(320px,55%);width:auto;object-fit:cover;border-radius:8px;border:1px solid var(--_border);background:#f6f8fa}
|
|
59
|
+
.realiiz-image__meta{display:flex;flex-direction:column;gap:10px;min-width:0}
|
|
60
|
+
.realiiz-image__detail{font-size:13px;color:var(--_muted);word-break:break-all}
|
|
61
|
+
.realiiz-image__actions{display:flex;align-items:center;gap:14px}
|
|
62
|
+
.realiiz-image__remove{font:inherit;font-size:13px;background:none;border:0;padding:0;color:var(--_muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer}
|
|
63
|
+
.realiiz-image__remove:hover{color:var(--_danger)}
|
|
64
|
+
.realiiz-image__hint{font-size:12px;color:var(--_muted);margin-top:10px}
|
|
65
|
+
.realiiz-image .realiiz-form__error{margin-top:8px}
|
|
66
|
+
@media (max-width:720px){.realiiz-image__has{flex-direction:column}.realiiz-image__thumb{max-width:100%}}
|
|
67
|
+
.realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}
|
|
68
|
+
.realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}
|
|
69
|
+
.realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}
|
|
70
|
+
.realiiz-form__help{text-decoration:underline dotted;text-decoration-color:color-mix(in srgb,currentColor 45%,transparent);text-underline-offset:4px;cursor:help}
|
|
71
|
+
.realiiz-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
|
72
|
+
.realiiz-form__footer{max-width:920px}
|
|
73
|
+
.realiiz-form__bar{display:flex;align-items:center;gap:10px;margin-top:16px;padding:12px 0;border-top:1px solid var(--_border);background:#fff}
|
|
74
|
+
.realiiz-form__status{margin-right:auto;font-size:.85rem;color:var(--_muted)}
|
|
75
|
+
.realiiz-form__bar[data-dirty] .realiiz-form__status{color:var(--_fg)}
|
|
76
|
+
.realiiz-form__button{font:inherit;font-weight:600;border-radius:var(--_radius);padding:10px 16px;border:1px solid var(--_border);background:#fff;color:var(--_fg);cursor:pointer;min-height:40px}
|
|
77
|
+
.realiiz-form__button--primary{background:var(--_accent);color:var(--_accent-fg);border-color:var(--_accent)}
|
|
78
|
+
.realiiz-form__button:disabled{opacity:.6;cursor:default}
|
|
79
|
+
.realiiz-md{--_md-head:#fafbfd;--_md-hover:#f4f8fd;--_md-hover-line:#bcd0ea;--_md-on:#eef4fb;--_md-tip:#1f2933;position:relative}
|
|
80
|
+
.realiiz-md__mode{display:inline-flex;align-self:flex-start;width:max-content;gap:2px;padding:3px;background:color-mix(in srgb,var(--_border) 40%,transparent);border-radius:8px;margin-bottom:8px}
|
|
81
|
+
.realiiz-md__mode button{font:inherit;font-size:.8rem;padding:5px 13px;border:0;border-radius:6px;background:transparent;color:var(--_muted);cursor:pointer}
|
|
82
|
+
.realiiz-md__mode button[aria-selected="true"]{background:#fff;color:var(--_fg);box-shadow:0 1px 2px rgba(0,0,0,.12)}
|
|
83
|
+
.realiiz-md__panel{position:relative;z-index:0;display:flex;flex-direction:column;border:1px solid var(--_border);border-radius:10px;background:#fff;overflow:hidden}
|
|
84
|
+
.realiiz-md__panel:focus-within{border-color:var(--_md-hover-line)}
|
|
85
|
+
.realiiz-md__head{position:relative;z-index:5;display:flex;align-items:center;gap:2px;flex-wrap:wrap;padding:5px 7px;background:var(--_md-head);border-bottom:1px solid var(--_border);flex-shrink:0}
|
|
86
|
+
.realiiz-md__tools{display:flex;align-items:center;gap:3px;flex-wrap:wrap}
|
|
87
|
+
.realiiz-md__spacer{flex:1}
|
|
88
|
+
.realiiz-md__head button{position:relative;min-width:31px;height:28px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;background:#fff;border:1px solid var(--_border);border-radius:6px;font-family:inherit;font-size:.82rem;color:var(--_fg);cursor:pointer;line-height:1}
|
|
89
|
+
.realiiz-md__head button:hover:not(:disabled){border-color:var(--_md-hover-line);background:var(--_md-hover)}
|
|
90
|
+
.realiiz-md__head button:active{background:var(--_md-on)}
|
|
91
|
+
.realiiz-md__head button.on{background:var(--_md-on);border-color:var(--_accent);color:var(--_accent);font-weight:600}
|
|
92
|
+
.realiiz-md__ico{width:15px;height:15px;display:block}
|
|
93
|
+
.realiiz-md__dot{font-size:1.05rem;line-height:1}
|
|
94
|
+
.realiiz-md__sep{width:1px;height:17px;background:var(--_border);margin:0 5px}
|
|
95
|
+
.realiiz-md__insert{display:inline-flex;align-items:center;gap:3px;border-left:1px solid var(--_border);padding-left:8px;margin-left:6px}
|
|
96
|
+
.realiiz-md__insert button{background:#f6f8fa;color:var(--_muted);padding:0;width:31px}
|
|
97
|
+
.realiiz-md__insert button:hover{color:var(--_accent)}
|
|
98
|
+
.realiiz-md__lab{font-size:.74rem;color:var(--_muted);margin-right:3px}
|
|
99
|
+
.realiiz-md__head button[data-tip]::after{content:attr(data-tip);position:absolute;top:calc(100% + 6px);left:0;background:var(--_md-tip);color:#fff;font-size:.72rem;padding:4px 8px;border-radius:5px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .12s;z-index:20}
|
|
100
|
+
.realiiz-md__head button[data-tip]:hover::after,.realiiz-md__head button[data-tip]:focus-visible::after{opacity:1}
|
|
101
|
+
.realiiz-md__expand[data-tip]::after{left:auto;right:0}
|
|
102
|
+
.realiiz-md__tgwrap{position:relative;display:inline-flex}
|
|
103
|
+
.realiiz-md__tgrid{position:absolute;top:calc(100% + 6px);left:0;background:#fff;border:1px solid var(--_border);border-radius:9px;padding:9px;box-shadow:0 10px 28px rgba(0,0,0,.14);z-index:7;display:block}
|
|
104
|
+
.realiiz-md__tgcells{display:grid;grid-template-columns:repeat(6,17px);gap:3px}
|
|
105
|
+
.realiiz-md__tgcells i{width:17px;height:15px;border:1px solid var(--_border);border-radius:2px;background:#fff;cursor:pointer;display:block}
|
|
106
|
+
.realiiz-md__tgcells i.hot{background:#dbe9fb;border-color:var(--_accent)}
|
|
107
|
+
.realiiz-md__tglab{display:block;margin-top:8px;font-size:.74rem;color:var(--_muted);text-align:center;white-space:nowrap}
|
|
108
|
+
.realiiz-md .realiiz-md__ta{flex:1 1 auto;border:0;border-radius:0;resize:vertical;background:#fff;max-width:none;padding:16px 18px;line-height:1.65}
|
|
109
|
+
.realiiz-md .realiiz-md__ta:focus{outline:none;box-shadow:none}
|
|
110
|
+
.realiiz-md__preview{padding:20px 24px;background:#fff}
|
|
111
|
+
.realiiz-md__note{padding:4px 2px}
|
|
112
|
+
.realiiz-md--fs{position:fixed;inset:0;z-index:60;background:#fff;padding:14px 22px 22px;overflow:auto;display:flex;flex-direction:column}
|
|
113
|
+
.realiiz-md--fs .realiiz-md__panel{flex:1;min-height:0}
|
|
114
|
+
.realiiz-md--fs .realiiz-md__ta,.realiiz-md--fs .realiiz-md__preview{flex:1;min-height:0;overflow:auto;resize:none}
|
|
115
|
+
.realiiz-prose{max-width:68ch;line-height:1.65}
|
|
116
|
+
.realiiz-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-height:40px;padding:5px 8px;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius)}
|
|
117
|
+
.realiiz-chips:focus-within{border-color:var(--_accent);box-shadow:var(--_ring)}
|
|
118
|
+
.realiiz-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 9px;border:1px solid var(--_border);border-radius:999px;font-size:.8rem}
|
|
119
|
+
.realiiz-chip button{font:inherit;line-height:1;width:18px;height:18px;border-radius:50%;border:0;background:transparent;color:var(--_muted);cursor:pointer}
|
|
120
|
+
.realiiz-chips__input{flex:1;min-width:120px;border:0;background:transparent;font:inherit;color:inherit;outline:none;padding:4px}
|
|
121
|
+
.realiiz-chips__count{margin-left:auto;color:var(--_muted);font-size:.75rem}
|
|
122
|
+
.realiiz-gauge{display:inline-flex;align-items:center;gap:10px;font-size:.75rem;color:var(--_muted)}
|
|
123
|
+
.realiiz-gauge__bar{position:relative;width:140px;height:6px;border-radius:3px;background:var(--_border);overflow:hidden}
|
|
124
|
+
.realiiz-gauge__zone{position:absolute;top:0;bottom:0;background:rgba(21,128,61,.28)}
|
|
125
|
+
.realiiz-gauge__fill{position:absolute;top:0;bottom:0;left:0;background:var(--_muted);border-radius:3px;transition:width .12s}
|
|
126
|
+
.realiiz-gauge__text b{font-weight:600;color:inherit}
|
|
127
|
+
.realiiz-gauge--ok{color:#15803d}.realiiz-gauge--ok .realiiz-gauge__fill{background:#15803d}
|
|
128
|
+
.realiiz-gauge--over{color:var(--_danger)}.realiiz-gauge--over .realiiz-gauge__fill{background:var(--_danger)}
|
|
129
|
+
`;
|
|
7
130
|
var UNINVITED_MESSAGE = "This email address isn't set up for this site yet. Ask the site owner to send you an invite.";
|
|
8
131
|
function isUninvitedError(message) {
|
|
9
132
|
return /signups? not allowed|not allowed|user not found|invalid login/i.test(message);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth-ui/MagicLinkForm.tsx","../../src/auth-ui/SignOutButton.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * MagicLinkForm — one field, one button, and the right words for each outcome.\n *\n * The audience is a non-technical client. Two outcomes matter:\n * sent → tell them where the link went and that it expires in an hour\n * uninvited → signups are off, so Supabase refuses unknown emails with a\n * \"Signups not allowed\" error. That string is a support ticket;\n * \"this email isn't set up for this site\" is not.\n *\n * `shouldCreateUser: false` restates the dashboard setting in code — the flag\n * a reviewer greps for.\n */\n\nimport { createBrowserClient } from \"@supabase/ssr\";\nimport { useMemo, useState, type FormEvent } from \"react\";\nimport { FORM_CSS } from \"../forms-ui/styles\";\n\nexport interface MagicLinkFormProps {\n url: string;\n anonKey: string;\n /** The site's callback, e.g. https://5degrees.org/admin/auth/callback?next=/admin */\n redirectTo: string;\n /** Shown in the heading: \"Sign in to {siteName}\". */\n siteName: string;\n className?: string;\n}\n\ntype State = { kind: \"idle\" } | { kind: \"sending\" } | { kind: \"sent\"; email: string } | { kind: \"error\"; message: string; uninvited: boolean };\n\nexport const UNINVITED_MESSAGE =\n \"This email address isn't set up for this site yet. Ask the site owner to send you an invite.\";\n\nexport function isUninvitedError(message: string): boolean {\n return /signups? not allowed|not allowed|user not found|invalid login/i.test(message);\n}\n\nexport function MagicLinkForm({ url, anonKey, redirectTo, siteName, className }: MagicLinkFormProps) {\n const supabase = useMemo(() => createBrowserClient(url, anonKey), [url, anonKey]);\n const [email, setEmail] = useState(\"\");\n const [state, setState] = useState<State>({ kind: \"idle\" });\n\n const submit = async (e: FormEvent) => {\n e.preventDefault();\n if (state.kind === \"sending\") return;\n const address = email.trim().toLowerCase();\n setState({ kind: \"sending\" });\n const { error } = await supabase.auth.signInWithOtp({\n email: address,\n options: { emailRedirectTo: redirectTo, shouldCreateUser: false },\n });\n if (error) {\n const uninvited = isUninvitedError(error.message);\n setState({ kind: \"error\", message: uninvited ? UNINVITED_MESSAGE : error.message, uninvited });\n return;\n }\n setState({ kind: \"sent\", email: address });\n };\n\n const root = [\"realiiz-form\", \"realiiz-signin\", className].filter(Boolean).join(\" \");\n\n if (state.kind === \"sent\") {\n return (\n <div className={root} data-realiiz-signin=\"sent\">\n <style>{FORM_CSS}</style>\n <h2 className=\"realiiz-signin__heading\">Check your email</h2>\n <p>\n We sent a sign-in link to <strong>{state.email}</strong>. It expires in an hour.\n </p>\n <button type=\"button\" className=\"realiiz-form__button\" onClick={() => setState({ kind: \"idle\" })}>\n Use a different address\n </button>\n </div>\n );\n }\n\n const sending = state.kind === \"sending\";\n const fieldError = state.kind === \"error\" && !state.uninvited ? state.message : null;\n\n return (\n <form className={root} onSubmit={submit} noValidate data-realiiz-signin=\"form\">\n <style>{FORM_CSS}</style>\n <h2 className=\"realiiz-signin__heading\">Sign in to {siteName}</h2>\n\n {state.kind === \"error\" && state.uninvited && (\n <div className=\"realiiz-form__alert\" role=\"alert\">{state.message}</div>\n )}\n\n <div className=\"realiiz-form__field\">\n <label className=\"realiiz-form__label\" htmlFor=\"realiiz-signin-email\">Email address</label>\n <input\n id=\"realiiz-signin-email\"\n className={[\"realiiz-form__control\", fieldError && \"realiiz-form__control--invalid\"].filter(Boolean).join(\" \")}\n type=\"email\"\n name=\"email\"\n autoComplete=\"email\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n aria-invalid={fieldError ? true : undefined}\n aria-describedby={fieldError ? \"realiiz-signin-email-error\" : undefined}\n />\n {fieldError && (\n <div className=\"realiiz-form__error\" id=\"realiiz-signin-email-error\">{fieldError}</div>\n )}\n </div>\n\n <div className=\"realiiz-form__actions\">\n <button type=\"submit\" className=\"realiiz-form__button realiiz-form__button--primary\" disabled={sending || email.trim() === \"\"}>\n {sending ? \"Sending…\" : \"Send me a sign-in link\"}\n </button>\n </div>\n </form>\n );\n}\n","/**\n * SignOutButton — a plain POST form. No JavaScript needed, so it works before\n * hydration and inside a server component. The site's route (ADMIN-04) calls\n * signOut() from @realiizlabs/admin/auth and redirects.\n */\n\nexport interface SignOutButtonProps {\n /** Default \"/admin/auth/signout\". */\n actionPath?: string;\n className?: string;\n label?: string;\n}\n\nexport function SignOutButton({ actionPath = \"/admin/auth/signout\", className, label = \"Sign out\" }: SignOutButtonProps) {\n return (\n <form method=\"post\" action={actionPath} className={[\"realiiz-signout\", className].filter(Boolean).join(\" \")}>\n <button type=\"submit\" className=\"realiiz-form__button\">{label}</button>\n </form>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/forms-ui/styles.ts","../../src/auth-ui/MagicLinkForm.tsx","../../src/auth-ui/SignOutButton.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Theming hooks, same convention as BookingWidget: a root class, BEM elements,\n * and --realiiz-form-* custom properties with fallbacks so the form is usable\n * with zero host CSS and fully themeable with a dozen variables.\n */\n\nexport const FORM_CSS = `\n.realiiz-form{\n --_fg:var(--realiiz-form-fg,#111);\n --_muted:var(--realiiz-form-muted,#666);\n --_border:var(--realiiz-form-border,#cfcfcf);\n --_accent:var(--realiiz-form-accent,#111);\n --_accent-fg:var(--realiiz-form-accent-fg,#fff);\n --_danger:var(--realiiz-form-danger,#b3261e);\n --_radius:var(--realiiz-form-radius,6px);\n --_font:var(--realiiz-form-font,system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif);\n --_ring:var(--realiiz-form-focus-ring,0 0 0 3px rgba(0,0,0,.15));\n box-sizing:border-box;max-width:100%;color:var(--_fg);font-family:var(--_font);font-size:15px;line-height:1.4;\n}\n.realiiz-form *,.realiiz-form *::before,.realiiz-form *::after{box-sizing:inherit}\n.realiiz-form fieldset{border:0;margin:0 0 20px;padding:0;min-width:0}\n.realiiz-form legend{font-weight:600;font-size:13px;letter-spacing:.02em;text-transform:uppercase;color:var(--_muted);margin-bottom:12px;padding:0}\n.realiiz-form__field{display:block;margin:0 0 16px}\n.realiiz-form__label{display:block;font-weight:500;margin-bottom:6px}\n.realiiz-form__required{color:var(--_danger);margin-left:3px}\n.realiiz-form__control{display:block;width:100%;max-width:100%;font:inherit;color:inherit;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius);padding:9px 11px;min-height:40px}\n.realiiz-form__control:focus{outline:none;border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-form__control--textarea{min-height:110px;resize:vertical}\n.realiiz-form__control--body{min-height:280px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px}\n.realiiz-form__control--invalid{border-color:var(--_danger)}\n.realiiz-select{position:relative}\n.realiiz-select__btn{display:flex;align-items:center;justify-content:space-between;gap:10px;text-align:left;cursor:pointer;background:#fff}\n.realiiz-select__btn svg{flex-shrink:0;color:var(--_muted);transition:transform .15s}\n.realiiz-select__btn--open{border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-select__btn--open svg{transform:rotate(180deg)}\n.realiiz-select__btn:disabled{opacity:.6;cursor:default}\n.realiiz-select__placeholder{color:var(--_muted)}\n.realiiz-select__menu{position:absolute;left:0;right:0;top:calc(100% + 6px);margin:0;padding:6px 0;list-style:none;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius);box-shadow:0 8px 24px rgba(14,23,38,.14);max-height:260px;overflow:auto;z-index:40}\n.realiiz-select__item{display:flex;align-items:center;gap:8px;padding:8px 12px;font-size:14px;color:var(--_fg);cursor:pointer}\n.realiiz-select__item--active{background:var(--realiiz-form-hover,rgba(0,0,0,.05))}\n.realiiz-select__item--selected{font-weight:600}\n.realiiz-select__check{width:1em;flex-shrink:0;color:var(--_accent);font-weight:700}\n.realiiz-form__check{display:flex;align-items:center;gap:8px;font-weight:500}\n.realiiz-form__check input{width:18px;height:18px;margin:0}\n.realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}\n.realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}\n.realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}\n.realiiz-image{border:1px dashed var(--_border);border-radius:var(--_radius);padding:16px 18px;background:#fff;outline:none;transition:border-color .15s}\n.realiiz-image--over{border-color:var(--_accent);border-style:solid}\n.realiiz-image--invalid{border-color:var(--_danger)}\n.realiiz-image--busy{opacity:.7}\n.realiiz-image__empty{display:flex;align-items:center;gap:14px;flex-wrap:wrap;color:var(--_muted)}\n.realiiz-image__icon{flex-shrink:0;color:var(--_muted)}\n.realiiz-image__or{font-size:13px}\n.realiiz-image__has{display:flex;align-items:flex-start;gap:16px}\n.realiiz-image__thumb{display:block;max-height:160px;max-width:min(320px,55%);width:auto;object-fit:cover;border-radius:8px;border:1px solid var(--_border);background:#f6f8fa}\n.realiiz-image__meta{display:flex;flex-direction:column;gap:10px;min-width:0}\n.realiiz-image__detail{font-size:13px;color:var(--_muted);word-break:break-all}\n.realiiz-image__actions{display:flex;align-items:center;gap:14px}\n.realiiz-image__remove{font:inherit;font-size:13px;background:none;border:0;padding:0;color:var(--_muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer}\n.realiiz-image__remove:hover{color:var(--_danger)}\n.realiiz-image__hint{font-size:12px;color:var(--_muted);margin-top:10px}\n.realiiz-image .realiiz-form__error{margin-top:8px}\n@media (max-width:720px){.realiiz-image__has{flex-direction:column}.realiiz-image__thumb{max-width:100%}}\n.realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}\n.realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}\n.realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}\n.realiiz-form__help{text-decoration:underline dotted;text-decoration-color:color-mix(in srgb,currentColor 45%,transparent);text-underline-offset:4px;cursor:help}\n.realiiz-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.realiiz-form__footer{max-width:920px}\n.realiiz-form__bar{display:flex;align-items:center;gap:10px;margin-top:16px;padding:12px 0;border-top:1px solid var(--_border);background:#fff}\n.realiiz-form__status{margin-right:auto;font-size:.85rem;color:var(--_muted)}\n.realiiz-form__bar[data-dirty] .realiiz-form__status{color:var(--_fg)}\n.realiiz-form__button{font:inherit;font-weight:600;border-radius:var(--_radius);padding:10px 16px;border:1px solid var(--_border);background:#fff;color:var(--_fg);cursor:pointer;min-height:40px}\n.realiiz-form__button--primary{background:var(--_accent);color:var(--_accent-fg);border-color:var(--_accent)}\n.realiiz-form__button:disabled{opacity:.6;cursor:default}\n.realiiz-md{--_md-head:#fafbfd;--_md-hover:#f4f8fd;--_md-hover-line:#bcd0ea;--_md-on:#eef4fb;--_md-tip:#1f2933;position:relative}\n.realiiz-md__mode{display:inline-flex;align-self:flex-start;width:max-content;gap:2px;padding:3px;background:color-mix(in srgb,var(--_border) 40%,transparent);border-radius:8px;margin-bottom:8px}\n.realiiz-md__mode button{font:inherit;font-size:.8rem;padding:5px 13px;border:0;border-radius:6px;background:transparent;color:var(--_muted);cursor:pointer}\n.realiiz-md__mode button[aria-selected=\"true\"]{background:#fff;color:var(--_fg);box-shadow:0 1px 2px rgba(0,0,0,.12)}\n.realiiz-md__panel{position:relative;z-index:0;display:flex;flex-direction:column;border:1px solid var(--_border);border-radius:10px;background:#fff;overflow:hidden}\n.realiiz-md__panel:focus-within{border-color:var(--_md-hover-line)}\n.realiiz-md__head{position:relative;z-index:5;display:flex;align-items:center;gap:2px;flex-wrap:wrap;padding:5px 7px;background:var(--_md-head);border-bottom:1px solid var(--_border);flex-shrink:0}\n.realiiz-md__tools{display:flex;align-items:center;gap:3px;flex-wrap:wrap}\n.realiiz-md__spacer{flex:1}\n.realiiz-md__head button{position:relative;min-width:31px;height:28px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;background:#fff;border:1px solid var(--_border);border-radius:6px;font-family:inherit;font-size:.82rem;color:var(--_fg);cursor:pointer;line-height:1}\n.realiiz-md__head button:hover:not(:disabled){border-color:var(--_md-hover-line);background:var(--_md-hover)}\n.realiiz-md__head button:active{background:var(--_md-on)}\n.realiiz-md__head button.on{background:var(--_md-on);border-color:var(--_accent);color:var(--_accent);font-weight:600}\n.realiiz-md__ico{width:15px;height:15px;display:block}\n.realiiz-md__dot{font-size:1.05rem;line-height:1}\n.realiiz-md__sep{width:1px;height:17px;background:var(--_border);margin:0 5px}\n.realiiz-md__insert{display:inline-flex;align-items:center;gap:3px;border-left:1px solid var(--_border);padding-left:8px;margin-left:6px}\n.realiiz-md__insert button{background:#f6f8fa;color:var(--_muted);padding:0;width:31px}\n.realiiz-md__insert button:hover{color:var(--_accent)}\n.realiiz-md__lab{font-size:.74rem;color:var(--_muted);margin-right:3px}\n.realiiz-md__head button[data-tip]::after{content:attr(data-tip);position:absolute;top:calc(100% + 6px);left:0;background:var(--_md-tip);color:#fff;font-size:.72rem;padding:4px 8px;border-radius:5px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .12s;z-index:20}\n.realiiz-md__head button[data-tip]:hover::after,.realiiz-md__head button[data-tip]:focus-visible::after{opacity:1}\n.realiiz-md__expand[data-tip]::after{left:auto;right:0}\n.realiiz-md__tgwrap{position:relative;display:inline-flex}\n.realiiz-md__tgrid{position:absolute;top:calc(100% + 6px);left:0;background:#fff;border:1px solid var(--_border);border-radius:9px;padding:9px;box-shadow:0 10px 28px rgba(0,0,0,.14);z-index:7;display:block}\n.realiiz-md__tgcells{display:grid;grid-template-columns:repeat(6,17px);gap:3px}\n.realiiz-md__tgcells i{width:17px;height:15px;border:1px solid var(--_border);border-radius:2px;background:#fff;cursor:pointer;display:block}\n.realiiz-md__tgcells i.hot{background:#dbe9fb;border-color:var(--_accent)}\n.realiiz-md__tglab{display:block;margin-top:8px;font-size:.74rem;color:var(--_muted);text-align:center;white-space:nowrap}\n.realiiz-md .realiiz-md__ta{flex:1 1 auto;border:0;border-radius:0;resize:vertical;background:#fff;max-width:none;padding:16px 18px;line-height:1.65}\n.realiiz-md .realiiz-md__ta:focus{outline:none;box-shadow:none}\n.realiiz-md__preview{padding:20px 24px;background:#fff}\n.realiiz-md__note{padding:4px 2px}\n.realiiz-md--fs{position:fixed;inset:0;z-index:60;background:#fff;padding:14px 22px 22px;overflow:auto;display:flex;flex-direction:column}\n.realiiz-md--fs .realiiz-md__panel{flex:1;min-height:0}\n.realiiz-md--fs .realiiz-md__ta,.realiiz-md--fs .realiiz-md__preview{flex:1;min-height:0;overflow:auto;resize:none}\n.realiiz-prose{max-width:68ch;line-height:1.65}\n.realiiz-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-height:40px;padding:5px 8px;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius)}\n.realiiz-chips:focus-within{border-color:var(--_accent);box-shadow:var(--_ring)}\n.realiiz-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 9px;border:1px solid var(--_border);border-radius:999px;font-size:.8rem}\n.realiiz-chip button{font:inherit;line-height:1;width:18px;height:18px;border-radius:50%;border:0;background:transparent;color:var(--_muted);cursor:pointer}\n.realiiz-chips__input{flex:1;min-width:120px;border:0;background:transparent;font:inherit;color:inherit;outline:none;padding:4px}\n.realiiz-chips__count{margin-left:auto;color:var(--_muted);font-size:.75rem}\n.realiiz-gauge{display:inline-flex;align-items:center;gap:10px;font-size:.75rem;color:var(--_muted)}\n.realiiz-gauge__bar{position:relative;width:140px;height:6px;border-radius:3px;background:var(--_border);overflow:hidden}\n.realiiz-gauge__zone{position:absolute;top:0;bottom:0;background:rgba(21,128,61,.28)}\n.realiiz-gauge__fill{position:absolute;top:0;bottom:0;left:0;background:var(--_muted);border-radius:3px;transition:width .12s}\n.realiiz-gauge__text b{font-weight:600;color:inherit}\n.realiiz-gauge--ok{color:#15803d}.realiiz-gauge--ok .realiiz-gauge__fill{background:#15803d}\n.realiiz-gauge--over{color:var(--_danger)}.realiiz-gauge--over .realiiz-gauge__fill{background:var(--_danger)}\n`;\n","/**\n * MagicLinkForm — one field, one button, and the right words for each outcome.\n *\n * The audience is a non-technical client. Two outcomes matter:\n * sent → tell them where the link went and that it expires in an hour\n * uninvited → signups are off, so Supabase refuses unknown emails with a\n * \"Signups not allowed\" error. That string is a support ticket;\n * \"this email isn't set up for this site\" is not.\n *\n * `shouldCreateUser: false` restates the dashboard setting in code — the flag\n * a reviewer greps for.\n */\n\nimport { createBrowserClient } from \"@supabase/ssr\";\nimport { useMemo, useState, type FormEvent } from \"react\";\nimport { FORM_CSS } from \"../forms-ui/styles\";\n\nexport interface MagicLinkFormProps {\n url: string;\n anonKey: string;\n /** The site's callback, e.g. https://5degrees.org/admin/auth/callback?next=/admin */\n redirectTo: string;\n /** Shown in the heading: \"Sign in to {siteName}\". */\n siteName: string;\n className?: string;\n}\n\ntype State = { kind: \"idle\" } | { kind: \"sending\" } | { kind: \"sent\"; email: string } | { kind: \"error\"; message: string; uninvited: boolean };\n\nexport const UNINVITED_MESSAGE =\n \"This email address isn't set up for this site yet. Ask the site owner to send you an invite.\";\n\nexport function isUninvitedError(message: string): boolean {\n return /signups? not allowed|not allowed|user not found|invalid login/i.test(message);\n}\n\nexport function MagicLinkForm({ url, anonKey, redirectTo, siteName, className }: MagicLinkFormProps) {\n const supabase = useMemo(() => createBrowserClient(url, anonKey), [url, anonKey]);\n const [email, setEmail] = useState(\"\");\n const [state, setState] = useState<State>({ kind: \"idle\" });\n\n const submit = async (e: FormEvent) => {\n e.preventDefault();\n if (state.kind === \"sending\") return;\n const address = email.trim().toLowerCase();\n setState({ kind: \"sending\" });\n const { error } = await supabase.auth.signInWithOtp({\n email: address,\n options: { emailRedirectTo: redirectTo, shouldCreateUser: false },\n });\n if (error) {\n const uninvited = isUninvitedError(error.message);\n setState({ kind: \"error\", message: uninvited ? UNINVITED_MESSAGE : error.message, uninvited });\n return;\n }\n setState({ kind: \"sent\", email: address });\n };\n\n const root = [\"realiiz-form\", \"realiiz-signin\", className].filter(Boolean).join(\" \");\n\n if (state.kind === \"sent\") {\n return (\n <div className={root} data-realiiz-signin=\"sent\">\n <style>{FORM_CSS}</style>\n <h2 className=\"realiiz-signin__heading\">Check your email</h2>\n <p>\n We sent a sign-in link to <strong>{state.email}</strong>. It expires in an hour.\n </p>\n <button type=\"button\" className=\"realiiz-form__button\" onClick={() => setState({ kind: \"idle\" })}>\n Use a different address\n </button>\n </div>\n );\n }\n\n const sending = state.kind === \"sending\";\n const fieldError = state.kind === \"error\" && !state.uninvited ? state.message : null;\n\n return (\n <form className={root} onSubmit={submit} noValidate data-realiiz-signin=\"form\">\n <style>{FORM_CSS}</style>\n <h2 className=\"realiiz-signin__heading\">Sign in to {siteName}</h2>\n\n {state.kind === \"error\" && state.uninvited && (\n <div className=\"realiiz-form__alert\" role=\"alert\">{state.message}</div>\n )}\n\n <div className=\"realiiz-form__field\">\n <label className=\"realiiz-form__label\" htmlFor=\"realiiz-signin-email\">Email address</label>\n <input\n id=\"realiiz-signin-email\"\n className={[\"realiiz-form__control\", fieldError && \"realiiz-form__control--invalid\"].filter(Boolean).join(\" \")}\n type=\"email\"\n name=\"email\"\n autoComplete=\"email\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n aria-invalid={fieldError ? true : undefined}\n aria-describedby={fieldError ? \"realiiz-signin-email-error\" : undefined}\n />\n {fieldError && (\n <div className=\"realiiz-form__error\" id=\"realiiz-signin-email-error\">{fieldError}</div>\n )}\n </div>\n\n <div className=\"realiiz-form__actions\">\n <button type=\"submit\" className=\"realiiz-form__button realiiz-form__button--primary\" disabled={sending || email.trim() === \"\"}>\n {sending ? \"Sending…\" : \"Send me a sign-in link\"}\n </button>\n </div>\n </form>\n );\n}\n","/**\n * SignOutButton — a plain POST form. No JavaScript needed, so it works before\n * hydration and inside a server component. The site's route (ADMIN-04) calls\n * signOut() from @realiizlabs/admin/auth and redirects.\n */\n\nexport interface SignOutButtonProps {\n /** Default \"/admin/auth/signout\". */\n actionPath?: string;\n className?: string;\n label?: string;\n}\n\nexport function SignOutButton({ actionPath = \"/admin/auth/signout\", className, label = \"Sign out\" }: SignOutButtonProps) {\n return (\n <form method=\"post\" action={actionPath} className={[\"realiiz-signout\", className].filter(Boolean).join(\" \")}>\n <button type=\"submit\" className=\"realiiz-form__button\">{label}</button>\n </form>\n );\n}\n"]}
|