@protobase/ui 0.22.0 → 0.22.1

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.
Files changed (33) hide show
  1. package/dist/app/pages/action-href.js +1 -1
  2. package/dist/app/pages/render-node.js +3 -1
  3. package/dist/app/pages/use-run-action.js +3 -3
  4. package/dist/invitations/invite-form.js +56 -0
  5. package/dist/invitations/link-notice.js +12 -0
  6. package/dist/invitations/pending-invitation-row.js +54 -0
  7. package/dist/lists/paged-search-list.js +42 -0
  8. package/dist/{organizations/use-all-organizations.js → lists/use-paged-search.js} +11 -10
  9. package/dist/organizations/all-organizations.js +24 -45
  10. package/dist/organizations/app-roles-picker.js +5 -2
  11. package/dist/organizations/invitation-page.js +59 -16
  12. package/dist/organizations/invitations-page.js +41 -116
  13. package/dist/organizations/members-page.js +40 -54
  14. package/dist/package.js +1 -1
  15. package/dist/people/person-row.js +26 -0
  16. package/dist/shell/assets/app-xO_xPXCq.js +69 -0
  17. package/dist/shell/assets/{index-DB2bNsqa.js → index-BvrIX29L.js} +1 -1
  18. package/dist/shell/assets/{maplibre-DRs-NHQ9.js → maplibre-DEGWdV6B.js} +1 -1
  19. package/dist/shell/assets/{markdown-content-obHEqNW8.js → markdown-content-DdB7hUqJ.js} +1 -1
  20. package/dist/shell/assets/{record-map-zJ2TD-WG.js → record-map-C_dz1z7c.js} +1 -1
  21. package/dist/shell/assets/{records-map-BVaFY5HA.js → records-map-ByiKJWnW.js} +1 -1
  22. package/dist/shell/assets/{shared-protobase-ui-DWzrRp3Z.js → shared-protobase-ui-Di_9bo0v.js} +1 -1
  23. package/dist/shell/index.html +3 -3
  24. package/dist/shell/protobase.shell.json +1 -1
  25. package/dist/users/app-invitations.js +63 -0
  26. package/dist/users/create-user-form.js +86 -0
  27. package/dist/users/role-names.js +6 -0
  28. package/dist/users/use-user-roles.js +20 -0
  29. package/dist/users/user-list.js +34 -0
  30. package/dist/users/user-row.js +62 -0
  31. package/dist/users/users-block.js +30 -0
  32. package/package.json +4 -4
  33. package/dist/shell/assets/app-DFxg-FwV.js +0 -69
@@ -32,7 +32,7 @@ const holdsAll = (record, values) => Object.entries(values).every(([name, value]
32
32
  */
33
33
  const actionFor = (action, record) => {
34
34
  const run = action.run;
35
- if (run?.kind === "update") return record && holdsAll(record, run.values) ? void 0 : action;
35
+ if (run?.kind === "update") return record && !action.inputs?.length && holdsAll(record, run.values) ? void 0 : action;
36
36
  if (run?.kind !== "link" || !("href" in run)) return action;
37
37
  const target = linkTarget(run.href, record);
38
38
  if (target === void 0) return void 0;
@@ -16,6 +16,7 @@ import { RecordRowBlock } from "./blocks/record-row.js";
16
16
  import { ShowBlock } from "./blocks/show.js";
17
17
  import { StatBlock } from "./blocks/stat.js";
18
18
  import { TableBlock } from "./blocks/table.js";
19
+ import { UsersBlock } from "../../users/users-block.js";
19
20
  import { Fragment } from "react";
20
21
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
21
22
  import { unmarkFieldRefs } from "@protobase/schema/internal";
@@ -36,7 +37,8 @@ const blocks = {
36
37
  ModalForm: ModalFormBlock,
37
38
  Action: ActionBlock,
38
39
  Show: ShowBlock,
39
- Link: LinkBlock
40
+ Link: LinkBlock,
41
+ Users: UsersBlock
40
42
  };
41
43
  const isInline = (child) => typeof child === "string" || !child.custom && child.type === "Link";
42
44
  /** Text and links run together as one paragraph, like a sentence with a link in it; other elements stand alone. */
@@ -7,7 +7,7 @@ import { can } from "../permissions.js";
7
7
  import { actionFor, linkTarget, openLink } from "./action-href.js";
8
8
  import { useRefreshResource } from "./use-layout-data.js";
9
9
  import { useState } from "react";
10
- import { fillAddress } from "@protobase/schema/internal";
10
+ import { fillAddress, updateValues } from "@protobase/schema/internal";
11
11
  //#region src/app/pages/use-run-action.ts
12
12
  const message = (error) => error instanceof Error ? error.message : "Something went wrong";
13
13
  /**
@@ -15,7 +15,7 @@ const message = (error) => error instanceof Error ? error.message : "Something w
15
15
  * (`update`, `delete`, `link`, and `ai`, which the server runs), else the handler the project registered under the
16
16
  * action's name. An update whose values the record holds already is left out, and so is a link to a URL the record
17
17
  * holds while it holds none. `blocked` says why one cannot run; `afterRun` hears about every action that succeeded.
18
- * `submit` runs one with the answers to its inputs and throws its failure, for the dialog to show.
18
+ * `submit` runs one with the answers to its inputs (a handler gets them, an update writes them) and throws its failure, for the dialog to show.
19
19
  */
20
20
  const useActionRunner = (resource, scope, afterRun) => {
21
21
  const { views } = useAdminMeta();
@@ -45,7 +45,7 @@ const useActionRunner = (resource, scope, afterRun) => {
45
45
  else openLink(linkTarget(run.href, scope?.record));
46
46
  return;
47
47
  }
48
- if (run?.kind === "update") await client.update(resource, scope.key, run.values, scope.etag ?? "*");
48
+ if (run?.kind === "update") await client.update(resource, scope.key, updateValues(run.values, values ?? {}), scope.etag ?? "*");
49
49
  else if (run?.kind === "delete") await client.remove(resource, scope.key, { etag: scope.etag });
50
50
  else if (run?.kind === "ai") await client.generate(resource, scope.key, action.name);
51
51
  else await handlers[action.name]({
@@ -0,0 +1,56 @@
1
+ import { Input } from "../primitives/input.js";
2
+ import { Button } from "../primitives/button.js";
3
+ import { AccountSection, SectionError } from "../account/account-section.js";
4
+ import { useAction } from "../organizations/use-action.js";
5
+ import { LinkNotice } from "./link-notice.js";
6
+ import { useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/invitations/invite-form.tsx
9
+ /** Inviting an address: the address, the controls for what the invitation gives, Invite, and the link without mail. */
10
+ const InviteForm = ({ title, description, onInvite, beside, children, incomplete = false }) => {
11
+ const [email, setEmail] = useState("");
12
+ const [link, setLink] = useState();
13
+ const { busy, error, run } = useAction();
14
+ const invite = () => run(async () => {
15
+ setLink((await onInvite(email.trim())).link);
16
+ setEmail("");
17
+ });
18
+ return /* @__PURE__ */ jsxs(AccountSection, {
19
+ title,
20
+ description,
21
+ children: [
22
+ /* @__PURE__ */ jsx(SectionError, { message: error }),
23
+ /* @__PURE__ */ jsxs("form", {
24
+ className: "flex flex-col gap-3",
25
+ onSubmit: (event) => {
26
+ event.preventDefault();
27
+ invite();
28
+ },
29
+ children: [/* @__PURE__ */ jsxs("div", {
30
+ className: "flex flex-col gap-2 sm:flex-row",
31
+ children: [
32
+ /* @__PURE__ */ jsx(Input, {
33
+ "aria-label": "Email",
34
+ type: "email",
35
+ placeholder: "name@example.com",
36
+ value: email,
37
+ onChange: (event) => setEmail(event.target.value),
38
+ wrapperClassName: "flex-1"
39
+ }),
40
+ beside,
41
+ /* @__PURE__ */ jsx(Button, {
42
+ type: "submit",
43
+ variant: "primary",
44
+ loading: busy,
45
+ disabled: !email.trim() || incomplete,
46
+ children: "Invite"
47
+ })
48
+ ]
49
+ }), children]
50
+ }),
51
+ /* @__PURE__ */ jsx(LinkNotice, { link })
52
+ ]
53
+ });
54
+ };
55
+ //#endregion
56
+ export { InviteForm };
@@ -0,0 +1,12 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ //#region src/invitations/link-notice.tsx
3
+ /** The invitation link to pass on, when the app cannot email it. */
4
+ const LinkNotice = ({ link }) => link ? /* @__PURE__ */ jsxs("p", {
5
+ className: "mt-3 break-all rounded-md border bg-muted px-3 py-2 text-xs",
6
+ children: ["This app cannot send email, so pass this link on yourself. It works once, until the invitation expires: ", /* @__PURE__ */ jsx("span", {
7
+ className: "font-mono",
8
+ children: link
9
+ })]
10
+ }) : null;
11
+ //#endregion
12
+ export { LinkNotice };
@@ -0,0 +1,54 @@
1
+ import { Button } from "../primitives/button.js";
2
+ import { SectionError } from "../account/account-section.js";
3
+ import { useAction } from "../organizations/use-action.js";
4
+ import { LinkNotice } from "./link-notice.js";
5
+ import { useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/invitations/pending-invitation-row.tsx
8
+ /** An invitation not accepted yet: to whom, what it gives and until when, with Send again and Revoke. */
9
+ const PendingInvitationRow = ({ email, gives, expiresAt, onResend, onRevoke }) => {
10
+ const [link, setLink] = useState();
11
+ const { busy, error, run } = useAction();
12
+ const expired = new Date(expiresAt).getTime() < Date.now();
13
+ return /* @__PURE__ */ jsxs("li", {
14
+ className: "px-3 py-2.5",
15
+ children: [
16
+ /* @__PURE__ */ jsxs("div", {
17
+ className: "flex flex-wrap items-center gap-2",
18
+ children: [
19
+ /* @__PURE__ */ jsxs("span", {
20
+ className: "min-w-0 flex-1",
21
+ children: [/* @__PURE__ */ jsx("span", {
22
+ className: "block truncate font-medium",
23
+ children: email
24
+ }), /* @__PURE__ */ jsxs("span", {
25
+ className: "block text-xs text-muted-foreground",
26
+ children: [
27
+ gives,
28
+ " · ",
29
+ expired ? "Expired" : `Until ${new Date(expiresAt).toLocaleDateString()}`
30
+ ]
31
+ })]
32
+ }),
33
+ /* @__PURE__ */ jsx(Button, {
34
+ size: "sm",
35
+ loading: busy,
36
+ onClick: () => void run(async () => setLink((await onResend()).link)),
37
+ children: "Send again"
38
+ }),
39
+ /* @__PURE__ */ jsx(Button, {
40
+ size: "sm",
41
+ variant: "danger",
42
+ loading: busy,
43
+ onClick: () => void run(onRevoke),
44
+ children: "Revoke"
45
+ })
46
+ ]
47
+ }),
48
+ /* @__PURE__ */ jsx(SectionError, { message: error }),
49
+ /* @__PURE__ */ jsx(LinkNotice, { link })
50
+ ]
51
+ });
52
+ };
53
+ //#endregion
54
+ export { PendingInvitationRow };
@@ -0,0 +1,42 @@
1
+ import { Input } from "../primitives/input.js";
2
+ import { Button } from "../primitives/button.js";
3
+ import { AccountSection, SectionError } from "../account/account-section.js";
4
+ import { Search } from "lucide-react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/lists/paged-search-list.tsx
7
+ /** A section with a search box, how many of how many show, the rows found so far and "Show more" for the next page. */
8
+ const PagedSearchList = ({ title, description, searchLabel, placeholder, query, onQuery, list, error, renderRow }) => /* @__PURE__ */ jsxs(AccountSection, {
9
+ title,
10
+ description,
11
+ children: [
12
+ /* @__PURE__ */ jsx(SectionError, { message: error }),
13
+ /* @__PURE__ */ jsx(Input, {
14
+ "aria-label": searchLabel,
15
+ placeholder,
16
+ value: query,
17
+ onChange: (event) => onQuery(event.target.value),
18
+ leading: /* @__PURE__ */ jsx(Search, { className: "size-3.5" }),
19
+ wrapperClassName: "mb-2"
20
+ }),
21
+ /* @__PURE__ */ jsxs("p", {
22
+ className: "mb-3 text-xs text-muted-foreground",
23
+ children: [
24
+ list.found.length,
25
+ " of ",
26
+ list.total
27
+ ]
28
+ }),
29
+ list.found.length > 0 && /* @__PURE__ */ jsx("ul", {
30
+ className: "divide-y rounded-md border",
31
+ children: list.found.map(renderRow)
32
+ }),
33
+ list.found.length < list.total && /* @__PURE__ */ jsx(Button, {
34
+ className: "mt-3",
35
+ loading: list.loading,
36
+ onClick: () => void list.more(),
37
+ children: "Show more"
38
+ })
39
+ ]
40
+ });
41
+ //#endregion
42
+ export { PagedSearchList };
@@ -1,10 +1,11 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
- //#region src/organizations/use-all-organizations.ts
2
+ //#region src/lists/use-paged-search.ts
3
3
  /**
4
- * Every organization, 20 at a time, narrowed by `query` (searched 200 ms after the text stops changing). `more` adds the
5
- * next page. A page that arrives after the text changed is dropped, so it never adds rows of the old filter.
4
+ * What `search` finds for `query`, a page at a time (searched 200 ms after the text stops changing). `more` adds the
5
+ * next page. A page that arrives after the text changed is dropped, so it never adds rows of the old filter. `search`
6
+ * must keep its identity between renders, or each render searches again.
6
7
  */
7
- const useAllOrganizations = (client, query) => {
8
+ const usePagedSearch = (search, query) => {
8
9
  const [found, setFound] = useState([]);
9
10
  const [total, setTotal] = useState(0);
10
11
  const [loading, setLoading] = useState(false);
@@ -13,9 +14,9 @@ const useAllOrganizations = (client, query) => {
13
14
  useEffect(() => {
14
15
  const timer = setTimeout(() => {
15
16
  setLoading(true);
16
- client.search(query).then((page) => {
17
+ search(query, 0).then((page) => {
17
18
  if (latest.current !== query) return;
18
- setFound(page.organizations);
19
+ setFound(page.items);
19
20
  setTotal(page.total);
20
21
  setLoading(false);
21
22
  }, () => {
@@ -26,14 +27,14 @@ const useAllOrganizations = (client, query) => {
26
27
  });
27
28
  }, 200);
28
29
  return () => clearTimeout(timer);
29
- }, [client, query]);
30
+ }, [search, query]);
30
31
  const more = async () => {
31
32
  const asked = query;
32
33
  setLoading(true);
33
- const page = await client.search(asked, found.length).catch(() => void 0);
34
+ const page = await search(asked, found.length).catch(() => void 0);
34
35
  if (latest.current !== asked) return;
35
36
  if (page) {
36
- setFound((shown) => [...shown, ...page.organizations]);
37
+ setFound((shown) => [...shown, ...page.items]);
37
38
  setTotal(page.total);
38
39
  }
39
40
  setLoading(false);
@@ -46,4 +47,4 @@ const useAllOrganizations = (client, query) => {
46
47
  };
47
48
  };
48
49
  //#endregion
49
- export { useAllOrganizations };
50
+ export { usePagedSearch };
@@ -1,61 +1,40 @@
1
- import { Input } from "../primitives/input.js";
2
1
  import { useAuth } from "../auth/auth-provider.js";
3
- import { Button } from "../primitives/button.js";
4
- import { AccountSection, SectionError } from "../account/account-section.js";
5
2
  import { useAction } from "./use-action.js";
3
+ import { PagedSearchList } from "../lists/paged-search-list.js";
4
+ import { usePagedSearch } from "../lists/use-paged-search.js";
6
5
  import { reopenIn } from "./use-organizations.js";
7
6
  import { OrganizationRow } from "./organization-row.js";
8
- import { useAllOrganizations } from "./use-all-organizations.js";
9
- import { useState } from "react";
10
- import { Search } from "lucide-react";
11
- import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { useCallback, useState } from "react";
8
+ import { jsx } from "react/jsx-runtime";
12
9
  //#region src/organizations/all-organizations.tsx
13
10
  /** Every organization in the app for people with a global role, who may work in any of them. */
14
11
  const AllOrganizations = ({ basePath, currentId }) => {
15
12
  const { session } = useAuth();
16
13
  const [query, setQuery] = useState("");
17
- const { found, total, more, loading } = useAllOrganizations(session.organizations, query);
14
+ const search = useCallback((text, offset) => session.organizations.search(text, offset).then((page) => ({
15
+ items: page.organizations,
16
+ total: page.total
17
+ })), [session]);
18
+ const list = usePagedSearch(search, query);
18
19
  const { busy, error, run } = useAction();
19
- return /* @__PURE__ */ jsxs(AccountSection, {
20
+ return /* @__PURE__ */ jsx(PagedSearchList, {
20
21
  title: "All organizations",
21
22
  description: "Every organization in the app. Your global roles let you work in any of them, with what those roles allow there. Opening one you are not a member of is written to the audit log.",
22
- children: [
23
- /* @__PURE__ */ jsx(SectionError, { message: error }),
24
- /* @__PURE__ */ jsx(Input, {
25
- "aria-label": "Find an organization",
26
- placeholder: "Name or slug",
27
- value: query,
28
- onChange: (event) => setQuery(event.target.value),
29
- leading: /* @__PURE__ */ jsx(Search, { className: "size-3.5" }),
30
- wrapperClassName: "mb-2"
31
- }),
32
- /* @__PURE__ */ jsxs("p", {
33
- className: "mb-3 text-xs text-muted-foreground",
34
- children: [
35
- found.length,
36
- " of ",
37
- total
38
- ]
39
- }),
40
- found.length > 0 && /* @__PURE__ */ jsx("ul", {
41
- className: "divide-y rounded-md border",
42
- children: found.map((organization) => /* @__PURE__ */ jsx(OrganizationRow, {
43
- organization,
44
- current: organization.id === currentId,
45
- busy,
46
- onOpen: () => void run(async () => {
47
- await session.organizations.switchTo(organization.id);
48
- reopenIn(basePath);
49
- })
50
- }, organization.id))
51
- }),
52
- found.length < total && /* @__PURE__ */ jsx(Button, {
53
- className: "mt-3",
54
- loading,
55
- onClick: () => void more(),
56
- children: "Show more"
23
+ searchLabel: "Find an organization",
24
+ placeholder: "Name or slug",
25
+ query,
26
+ onQuery: setQuery,
27
+ list,
28
+ error,
29
+ renderRow: (organization) => /* @__PURE__ */ jsx(OrganizationRow, {
30
+ organization,
31
+ current: organization.id === currentId,
32
+ busy,
33
+ onOpen: () => void run(async () => {
34
+ await session.organizations.switchTo(organization.id);
35
+ reopenIn(basePath);
57
36
  })
58
- ]
37
+ }, organization.id)
59
38
  });
60
39
  };
61
40
  //#endregion
@@ -2,10 +2,13 @@ import { cn } from "../lib/cn.js";
2
2
  import { Checkbox } from "../primitives/checkbox.js";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  //#region src/organizations/app-roles-picker.tsx
5
- /** The app roles a member can hold, ticked when held; only those the person may give or take away can change. */
5
+ /**
6
+ * The roles given, ticked when held; only those the person may give or take away can change. Organization pages pass
7
+ * the roles a member can hold (`membership`), the Users page every role.
8
+ */
6
9
  const AppRolesPicker = ({ roles, selected, grantable, disabled, onChange }) => /* @__PURE__ */ jsx("div", {
7
10
  className: "flex flex-wrap gap-x-4 gap-y-2",
8
- children: roles.filter((role) => role.membership).map((role) => {
11
+ children: roles.map((role) => {
9
12
  const held = selected.includes(role.name);
10
13
  const locked = disabled || !grantable.has(role.name);
11
14
  return /* @__PURE__ */ jsxs("label", {
@@ -10,9 +10,9 @@ import { organizationRoleLabels, roleLabel } from "./organization-paths.js";
10
10
  import { useAction } from "./use-action.js";
11
11
  import { useEffect, useState } from "react";
12
12
  import { AuthError } from "@protobase/client";
13
- import { jsx, jsxs } from "react/jsx-runtime";
13
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
14
  //#region src/organizations/invitation-page.tsx
15
- const SignUp = ({ token, preview, onDone }) => {
15
+ const SignUp = ({ token, preview, joins, onDone }) => {
16
16
  const { session, signInMethods, recheck } = useAuth();
17
17
  const [name, setName] = useState("");
18
18
  const [password, setPassword] = useState("");
@@ -34,13 +34,9 @@ const SignUp = ({ token, preview, onDone }) => {
34
34
  submit();
35
35
  },
36
36
  children: [
37
- /* @__PURE__ */ jsxs("p", {
37
+ /* @__PURE__ */ jsx("p", {
38
38
  className: "text-muted-foreground",
39
- children: [
40
- "Make your account for ",
41
- preview.email,
42
- " to join."
43
- ]
39
+ children: joins ? `Make your account for ${preview.email} to join.` : `Make your account for ${preview.email}.`
44
40
  }),
45
41
  error && /* @__PURE__ */ jsx("p", {
46
42
  role: "alert",
@@ -67,14 +63,15 @@ const SignUp = ({ token, preview, onDone }) => {
67
63
  variant: "primary",
68
64
  loading: busy,
69
65
  disabled: !name.trim() || withPassword && !passwordOptional && password.length < 12,
70
- children: "Create account and join"
66
+ children: joins ? "Create account and join" : "Create your account"
71
67
  })
72
68
  ]
73
69
  });
74
70
  };
75
71
  /**
76
72
  * Where an emailed invitation link leads: what it is for, then accepting it signed in as the invited address, signing
77
- * in first, or making an account for an address without one.
73
+ * in first, or making an account for an address without one. An invitation to the app itself, rather than to an
74
+ * organization, only makes the account: someone with one just signs in.
78
75
  */
79
76
  const InvitationPage = ({ token, onDone, signIn }) => {
80
77
  const { session, state, signOut, organizations } = useAuth();
@@ -100,11 +97,56 @@ const InvitationPage = ({ token, onDone, signIn }) => {
100
97
  children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
101
98
  })
102
99
  });
103
- const roles = preview.appRoles.map((name) => roleLabel(organizations?.roles ?? [], name));
104
- const description = `${preview.inviter.name} invites you to ${preview.organization.name} as ${organizationRoleLabels[preview.role].toLowerCase()}${roles.length > 0 ? `, with ${roles.join(", ")}` : ""}.`;
105
100
  const user = state.kind === "signed-in" ? state.user : void 0;
106
- if (user && user.email.toLowerCase() !== preview.email.toLowerCase()) return /* @__PURE__ */ jsxs(AuthLayout, {
107
- title: `Join ${preview.organization.name}`,
101
+ const sameAddress = user?.email.toLowerCase() === preview.email.toLowerCase();
102
+ if (!preview.organization) {
103
+ const app = preview.app?.name ?? "";
104
+ const description = `${preview.inviter.name} invites you to join ${app}.`;
105
+ return /* @__PURE__ */ jsx(AuthLayout, {
106
+ title: `Join ${app}`,
107
+ description,
108
+ children: preview.hasAccount ? user && sameAddress ? /* @__PURE__ */ jsx(Button, {
109
+ variant: "primary",
110
+ className: "w-full",
111
+ onClick: onDone,
112
+ children: "Go to the app"
113
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("p", {
114
+ className: "mb-3 text-muted-foreground",
115
+ children: [preview.email, " has an account already: sign in with it."]
116
+ }), user ? /* @__PURE__ */ jsx(Button, {
117
+ className: "w-full",
118
+ onClick: () => void signOut(),
119
+ children: "Sign out"
120
+ }) : /* @__PURE__ */ jsxs(RouteLink, {
121
+ to: signIn,
122
+ className: cn(buttonClass({ variant: "primary" }), "w-full"),
123
+ children: ["Sign in as ", preview.email]
124
+ })] }) : user ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("p", {
125
+ className: "mb-3 text-muted-foreground",
126
+ children: [
127
+ "This invitation is for ",
128
+ preview.email,
129
+ ", and you are signed in as ",
130
+ user.email,
131
+ "."
132
+ ]
133
+ }), /* @__PURE__ */ jsx(Button, {
134
+ className: "w-full",
135
+ onClick: () => void signOut(),
136
+ children: "Sign out to accept it"
137
+ })] }) : /* @__PURE__ */ jsx(SignUp, {
138
+ token,
139
+ preview,
140
+ joins: false,
141
+ onDone
142
+ })
143
+ });
144
+ }
145
+ const organization = preview.organization;
146
+ const roles = preview.appRoles.map((name) => roleLabel(organizations?.roles ?? [], name));
147
+ const description = `${preview.inviter.name} invites you to ${organization.name} as ${organizationRoleLabels[preview.role].toLowerCase()}${roles.length > 0 ? `, with ${roles.join(", ")}` : ""}.`;
148
+ if (user && !sameAddress) return /* @__PURE__ */ jsxs(AuthLayout, {
149
+ title: `Join ${organization.name}`,
108
150
  description,
109
151
  children: [/* @__PURE__ */ jsxs("p", {
110
152
  className: "mb-3 text-muted-foreground",
@@ -122,7 +164,7 @@ const InvitationPage = ({ token, onDone, signIn }) => {
122
164
  })]
123
165
  });
124
166
  return /* @__PURE__ */ jsx(AuthLayout, {
125
- title: `Join ${preview.organization.name}`,
167
+ title: `Join ${organization.name}`,
126
168
  description,
127
169
  children: user ? /* @__PURE__ */ jsxs("div", {
128
170
  className: "flex flex-col gap-3",
@@ -138,7 +180,7 @@ const InvitationPage = ({ token, onDone, signIn }) => {
138
180
  await session.organizations.accept(token);
139
181
  onDone();
140
182
  }),
141
- children: ["Join ", preview.organization.name]
183
+ children: ["Join ", organization.name]
142
184
  })]
143
185
  }) : preview.hasAccount ? /* @__PURE__ */ jsxs(RouteLink, {
144
186
  to: signIn,
@@ -151,6 +193,7 @@ const InvitationPage = ({ token, onDone, signIn }) => {
151
193
  }) : /* @__PURE__ */ jsx(SignUp, {
152
194
  token,
153
195
  preview,
196
+ joins: true,
154
197
  onDone
155
198
  })
156
199
  });