@ezychat/web 0.1.46 → 0.1.47

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.
@@ -1,6 +1,9 @@
1
1
  import { type LucideIcon } from 'lucide-react';
2
2
  import { type WebExtension, type WebExtensionAdminSection } from '../extensions';
3
- /** `labelKey` is a translation key in the `admin` namespace. */
3
+ /**
4
+ * Every core admin section. `labelKey` is a translation key in the `admin` namespace. WhatsApp and
5
+ * Inboxes never show together: see `visibleAdminNav`.
6
+ */
4
7
  export declare const ADMIN_NAV: readonly [{
5
8
  readonly to: "members";
6
9
  readonly labelKey: "nav.members";
@@ -13,6 +16,10 @@ export declare const ADMIN_NAV: readonly [{
13
16
  readonly to: "whatsapp";
14
17
  readonly labelKey: "nav.whatsapp";
15
18
  readonly icon: LucideIcon;
19
+ }, {
20
+ readonly to: "inboxes";
21
+ readonly labelKey: "inboxes.title";
22
+ readonly icon: LucideIcon;
16
23
  }, {
17
24
  readonly to: "tunnel";
18
25
  readonly labelKey: "nav.tunnel";
@@ -26,6 +33,12 @@ export declare const ADMIN_NAV: readonly [{
26
33
  readonly labelKey: "nav.audit";
27
34
  readonly icon: LucideIcon;
28
35
  }];
36
+ export type AdminNavItem = (typeof ADMIN_NAV)[number];
37
+ /**
38
+ * The sections in the nav: **Inboxes** replaces **WhatsApp** only in an edition that allows more than
39
+ * one WhatsApp number (Lite allows one, so it keeps WhatsApp).
40
+ */
41
+ export declare function visibleAdminNav(multiInbox: boolean): AdminNavItem[];
29
42
  /** The core section an extension admin route declares for this path (e.g. `members`). */
30
43
  export declare function extensionSectionOf(extensions: readonly WebExtension[], pathname: string): WebExtensionAdminSection | undefined;
31
44
  /**
@@ -0,0 +1,9 @@
1
+ import type { Inbox, User } from '@ezychat/shared';
2
+ /**
3
+ * Members → Inbox access: which inboxes a member may open. Admins always see every inbox; an agent
4
+ * sees only the chats (and notifications) of the inboxes ticked here. `inboxes` are the active ones.
5
+ */
6
+ export declare function InboxAccessCell({ user, inboxes }: Readonly<{
7
+ user: User;
8
+ inboxes: Inbox[];
9
+ }>): import("react").JSX.Element;
@@ -0,0 +1,3 @@
1
+ import type React from 'react';
2
+ /** Every linked WhatsApp number (inbox), with who may open it. Admin only; shown when maxInboxes > 1. */
3
+ export declare function InboxesPage(): React.JSX.Element;
@@ -0,0 +1,16 @@
1
+ import type { WaState } from '@ezychat/shared';
2
+ import type { StatusTone } from '../components/app';
3
+ /** What the inbox card says next to its status: scan the QR code, or the phone is offline. */
4
+ export type InboxNotice = 'scan' | 'offline' | 'replaced' | null;
5
+ export interface InboxCardStatus {
6
+ tone: StatusTone;
7
+ /** Translation key in the `admin` namespace. */
8
+ labelKey: string;
9
+ notice: InboxNotice;
10
+ /** The phone must scan a QR code (or enter a pairing code) before the inbox works. */
11
+ needsLink: boolean;
12
+ }
13
+ /** WhatsApp connection state of one inbox → the status its card shows. */
14
+ export declare function inboxCardStatus(state: WaState): InboxCardStatus;
15
+ /** Phone-number digits from WhatsApp (`60112233445`) → `+60112233445`; anything else as it is. */
16
+ export declare function formatInboxNumber(number: string | null): string | null;
@@ -12,6 +12,7 @@ export declare function useAiMember(): import("@tanstack/react-query").UseQueryR
12
12
  locale: "en" | "ms" | "zh-CN" | null;
13
13
  kind?: "human" | "ai" | undefined;
14
14
  aiRole?: "sales" | undefined;
15
+ inboxIds?: number[] | undefined;
15
16
  } | null;
16
17
  settings: {
17
18
  displayName: string;
@@ -146,6 +147,7 @@ export declare function useAiMemberAction(): import("@tanstack/react-query").Use
146
147
  locale: "en" | "ms" | "zh-CN" | null;
147
148
  kind?: "human" | "ai" | undefined;
148
149
  aiRole?: "sales" | undefined;
150
+ inboxIds?: number[] | undefined;
149
151
  } | null;
150
152
  settings: {
151
153
  displayName: string;
@@ -0,0 +1,65 @@
1
+ import type { UpdateInboxBody } from '@ezychat/shared';
2
+ import { type WaAction } from './queries';
3
+ export declare function useCreateInbox(): import("@tanstack/react-query").UseMutationResult<{
4
+ id: number;
5
+ name: string;
6
+ tone: "success" | "primary" | "warning" | "info";
7
+ number: string | null;
8
+ status: {
9
+ state: "open" | "qr" | "disconnected" | "connecting" | "logged_out" | "replaced" | "blocked";
10
+ me: {
11
+ jid: string;
12
+ name: string | null;
13
+ lid?: string | null | undefined;
14
+ } | null;
15
+ qr: string | null;
16
+ lastError: string | null;
17
+ };
18
+ removed: boolean;
19
+ memberIds?: number[] | undefined;
20
+ }, Error, {
21
+ name: string;
22
+ memberIds?: number[] | undefined;
23
+ }, unknown>;
24
+ export declare function useUpdateInbox(): import("@tanstack/react-query").UseMutationResult<{
25
+ id: number;
26
+ name: string;
27
+ tone: "success" | "primary" | "warning" | "info";
28
+ number: string | null;
29
+ status: {
30
+ state: "open" | "qr" | "disconnected" | "connecting" | "logged_out" | "replaced" | "blocked";
31
+ me: {
32
+ jid: string;
33
+ name: string | null;
34
+ lid?: string | null | undefined;
35
+ } | null;
36
+ qr: string | null;
37
+ lastError: string | null;
38
+ };
39
+ removed: boolean;
40
+ memberIds?: number[] | undefined;
41
+ }, Error, {
42
+ id: number;
43
+ body: UpdateInboxBody;
44
+ }, unknown>;
45
+ export declare function useRemoveInbox(): import("@tanstack/react-query").UseMutationResult<unknown, Error, number, unknown>;
46
+ /** Log out, link again or take over the phone of one inbox. */
47
+ export declare function useInboxWaAction(inboxId: number): import("@tanstack/react-query").UseMutationResult<unknown, Error, WaAction, unknown>;
48
+ /** Link one inbox by phone number: the 8-character code to enter in WhatsApp → Linked devices. */
49
+ export declare function useInboxPairingCode(inboxId: number): import("@tanstack/react-query").UseMutationResult<{
50
+ code: string;
51
+ }, Error, string, unknown>;
52
+ /** Members → Inbox access: the inboxes one agent may open. */
53
+ export declare function useSetUserInboxes(userId: number): import("@tanstack/react-query").UseMutationResult<{
54
+ id: number;
55
+ username: string;
56
+ displayName: string;
57
+ role: "admin" | "agent";
58
+ mustChangePassword: boolean;
59
+ disabled: boolean;
60
+ createdAt: number;
61
+ locale: "en" | "ms" | "zh-CN" | null;
62
+ kind?: "human" | "ai" | undefined;
63
+ aiRole?: "sales" | undefined;
64
+ inboxIds?: number[] | undefined;
65
+ }, Error, number[], unknown>;
@@ -1,11 +1,13 @@
1
1
  import { type InfiniteData, type QueryClient } from '@tanstack/react-query';
2
- import { type CreateApiTokenBodyInput, type UpdateApiTokenBody, type Chat, type ChatListResponse, type ChatStatus, type Message, type MessageListResponse, type PatchUserBody, type Settings, type User } from '@ezychat/shared';
2
+ import { type CreateApiTokenBodyInput, type UpdateApiTokenBody, type Chat, type ChatListResponse, type ChatStatus, type DirectoryUser, type InboxStatusPayload, type Message, type MessageListResponse, type PatchUserBody, type Settings, type WaStatus } from '@ezychat/shared';
3
3
  export interface ChatFilters {
4
4
  status?: ChatStatus;
5
5
  assigned: 'me' | 'none' | 'any';
6
6
  q?: string;
7
7
  /** customer tag filter */
8
8
  tag?: string;
9
+ /** One inbox (multi-inbox); unset = every inbox the user may open. */
10
+ inbox?: number;
9
11
  }
10
12
  export declare const qk: {
11
13
  me: readonly ["me"];
@@ -24,6 +26,7 @@ export declare const qk: {
24
26
  users: readonly ["users"];
25
27
  directory: readonly ["users", "directory"];
26
28
  wa: readonly ["wa"];
29
+ inboxes: readonly ["inboxes"];
27
30
  tunnel: readonly ["tunnel"];
28
31
  cloudflareSetup: readonly ["cloudflare-setup"];
29
32
  settings: readonly ["settings"];
@@ -65,6 +68,7 @@ export declare function useMe(): import("@tanstack/react-query").UseQueryResult<
65
68
  locale: "en" | "ms" | "zh-CN" | null;
66
69
  kind?: "human" | "ai" | undefined;
67
70
  aiRole?: "sales" | undefined;
71
+ inboxIds?: number[] | undefined;
68
72
  } | null, Error>;
69
73
  export declare function useSetupStatus(): import("@tanstack/react-query").UseQueryResult<{
70
74
  needsSetup: boolean;
@@ -86,6 +90,7 @@ export declare function useLogin(): import("@tanstack/react-query").UseMutationR
86
90
  locale: "en" | "ms" | "zh-CN" | null;
87
91
  kind?: "human" | "ai" | undefined;
88
92
  aiRole?: "sales" | undefined;
93
+ inboxIds?: number[] | undefined;
89
94
  } | null, Error, {
90
95
  username: string;
91
96
  password: string;
@@ -108,6 +113,7 @@ export declare function useSetMyLocale(): import("@tanstack/react-query").UseMut
108
113
  locale: "en" | "ms" | "zh-CN" | null;
109
114
  kind?: "human" | "ai" | undefined;
110
115
  aiRole?: "sales" | undefined;
116
+ inboxIds?: number[] | undefined;
111
117
  }, Error, "en" | "ms" | "zh-CN" | null, unknown>;
112
118
  export declare function useChangePassword(): import("@tanstack/react-query").UseMutationResult<unknown, Error, {
113
119
  currentPassword: string;
@@ -116,6 +122,8 @@ export declare function useChangePassword(): import("@tanstack/react-query").Use
116
122
  export declare function useChats(filters: ChatFilters): import("@tanstack/react-query").UseInfiniteQueryResult<InfiniteData<{
117
123
  chats: {
118
124
  jid: string;
125
+ inboxId: number;
126
+ waJid: string;
119
127
  type: "dm" | "group";
120
128
  name: string;
121
129
  avatarUrl: string | null;
@@ -134,6 +142,8 @@ export declare function useChats(filters: ChatFilters): import("@tanstack/react-
134
142
  export declare function useChat(jid: string | null | undefined): import("@tanstack/react-query").UseQueryResult<{
135
143
  chat: {
136
144
  jid: string;
145
+ inboxId: number;
146
+ waJid: string;
137
147
  type: "dm" | "group";
138
148
  name: string;
139
149
  avatarUrl: string | null;
@@ -300,6 +310,8 @@ export declare function useRetryMessage(): import("@tanstack/react-query").UseMu
300
310
  }, Error, string, unknown>;
301
311
  export declare function usePatchChat(jid: string): import("@tanstack/react-query").UseMutationResult<{
302
312
  jid: string;
313
+ inboxId: number;
314
+ waJid: string;
303
315
  type: "dm" | "group";
304
316
  name: string;
305
317
  avatarUrl: string | null;
@@ -448,9 +460,10 @@ export declare function useUsers(opts?: {
448
460
  locale: "en" | "ms" | "zh-CN" | null;
449
461
  kind?: "human" | "ai" | undefined;
450
462
  aiRole?: "sales" | undefined;
463
+ inboxIds?: number[] | undefined;
451
464
  }[], Error>;
452
465
  /** Public team-member fields every logged-in user may see (`GET /api/users/directory`). */
453
- export type DirectoryUser = Pick<User, 'id' | 'displayName' | 'role' | 'disabled' | 'kind' | 'aiRole'>;
466
+ export type { DirectoryUser };
454
467
  /**
455
468
  * Team directory for every role (agents included). Needs the server route
456
469
  * `GET /api/users/directory`; when it is missing (404) or forbidden, resolves to an empty list
@@ -458,7 +471,15 @@ export type DirectoryUser = Pick<User, 'id' | 'displayName' | 'role' | 'disabled
458
471
  */
459
472
  export declare function useUserDirectory(opts?: {
460
473
  enabled?: boolean;
461
- }): import("@tanstack/react-query").UseQueryResult<DirectoryUser[], Error>;
474
+ }): import("@tanstack/react-query").UseQueryResult<{
475
+ id: number;
476
+ displayName: string;
477
+ role: "admin" | "agent";
478
+ disabled: boolean;
479
+ inboxIds: "all" | number[];
480
+ kind?: "human" | "ai" | undefined;
481
+ aiRole?: "sales" | undefined;
482
+ }[], Error>;
462
483
  export declare function useCreateUser(): import("@tanstack/react-query").UseMutationResult<{
463
484
  id: number;
464
485
  username: string;
@@ -470,6 +491,7 @@ export declare function useCreateUser(): import("@tanstack/react-query").UseMuta
470
491
  locale: "en" | "ms" | "zh-CN" | null;
471
492
  kind?: "human" | "ai" | undefined;
472
493
  aiRole?: "sales" | undefined;
494
+ inboxIds?: number[] | undefined;
473
495
  }, Error, {
474
496
  username: string;
475
497
  displayName: string;
@@ -487,6 +509,7 @@ export declare function usePatchUser(): import("@tanstack/react-query").UseMutat
487
509
  locale: "en" | "ms" | "zh-CN" | null;
488
510
  kind?: "human" | "ai" | undefined;
489
511
  aiRole?: "sales" | undefined;
512
+ inboxIds?: number[] | undefined;
490
513
  }, Error, {
491
514
  id: number;
492
515
  patch: PatchUserBody;
@@ -507,6 +530,43 @@ export declare function useWaStatus(opts?: {
507
530
  qr: string | null;
508
531
  lastError: string | null;
509
532
  }, Error>;
533
+ /** The inbox 1 number (the number linked before multi-inbox). */
534
+ export declare const PRIMARY_INBOX_ID = 1;
535
+ /**
536
+ * The inboxes the signed-in user may open (`GET /api/inboxes`; the server filters them). A server
537
+ * without the route (404) counts as one inbox, so no inbox UI shows.
538
+ */
539
+ export declare function useInboxes(opts?: {
540
+ enabled?: boolean;
541
+ }): import("@tanstack/react-query").UseQueryResult<{
542
+ inboxes: {
543
+ id: number;
544
+ name: string;
545
+ tone: "success" | "primary" | "warning" | "info";
546
+ number: string | null;
547
+ status: {
548
+ state: "open" | "qr" | "disconnected" | "connecting" | "logged_out" | "replaced" | "blocked";
549
+ me: {
550
+ jid: string;
551
+ name: string | null;
552
+ lid?: string | null | undefined;
553
+ } | null;
554
+ qr: string | null;
555
+ lastError: string | null;
556
+ };
557
+ removed: boolean;
558
+ memberIds?: number[] | undefined;
559
+ }[];
560
+ maxInboxes: number;
561
+ canAdd: boolean;
562
+ }, Error>;
563
+ /** Applies a socket `inbox:status` to the cached inbox list. */
564
+ export declare function patchInboxStatusInCache(qc: QueryClient, p: InboxStatusPayload): void;
565
+ /**
566
+ * Connection status of one inbox. Inbox 1 reads `GET /api/wa/status` as before (Lite unchanged);
567
+ * other inboxes read the inbox list, which `inbox:status` keeps current.
568
+ */
569
+ export declare function useInboxStatus(inboxId: number | undefined): WaStatus | undefined;
510
570
  export type WaAction = 'logout' | 'relink' | 'takeover';
511
571
  export declare function useWaAction(): import("@tanstack/react-query").UseMutationResult<unknown, Error, WaAction, unknown>;
512
572
  /** Link by phone number: returns the 8-character code to enter in WhatsApp → Linked devices. */
@@ -36,7 +36,7 @@ export interface WebExtensionRoute {
36
36
  section?: WebExtensionAdminSection;
37
37
  }
38
38
  /** Core admin sections an extension admin route may sit under (the `ADMIN_NAV` paths). */
39
- export declare const WEB_EXTENSION_ADMIN_SECTIONS: readonly ["members", "quick-replies", "whatsapp", "tunnel", "settings", "audit"];
39
+ export declare const WEB_EXTENSION_ADMIN_SECTIONS: readonly ["members", "quick-replies", "whatsapp", "inboxes", "tunnel", "settings", "audit"];
40
40
  export type WebExtensionAdminSection = (typeof WEB_EXTENSION_ADMIN_SECTIONS)[number];
41
41
  /** A small badge on an admin nav link: a count, or a dot that asks for attention. */
42
42
  export interface WebExtensionNavBadge {
@@ -149,6 +149,8 @@ export declare const enResources: {
149
149
  noInstallerFeed: string;
150
150
  unverifiedFeed: string;
151
151
  releaseNotes: string;
152
+ whatsNew: string;
153
+ updatedTo: string;
152
154
  };
153
155
  share: {
154
156
  title: string;
@@ -628,6 +630,7 @@ export declare const enResources: {
628
630
  waBanner: {
629
631
  settingsShort: string;
630
632
  openSettings: string;
633
+ openInboxes: string;
631
634
  connecting: {
632
635
  title: string;
633
636
  body: string;
@@ -652,6 +655,19 @@ export declare const enResources: {
652
655
  title: string;
653
656
  body: string;
654
657
  };
658
+ inbox: {
659
+ connecting: string;
660
+ disconnected: string;
661
+ qr: string;
662
+ loggedOut: string;
663
+ replaced: string;
664
+ blocked: string;
665
+ };
666
+ };
667
+ inboxFilter: {
668
+ label: string;
669
+ all: string;
670
+ option: string;
655
671
  };
656
672
  phoneLink: {
657
673
  label: string;
@@ -994,6 +1010,10 @@ export declare const enResources: {
994
1010
  apiTokenAuthFailed: string;
995
1011
  mcpEnable: string;
996
1012
  mcpDisable: string;
1013
+ inboxCreate: string;
1014
+ inboxUpdate: string;
1015
+ inboxRemove: string;
1016
+ inboxAccessUpdate: string;
997
1017
  };
998
1018
  fields: {
999
1019
  chat: string;
@@ -1033,6 +1053,9 @@ export declare const enResources: {
1033
1053
  lanEnabled: string;
1034
1054
  historyDays: string;
1035
1055
  expires: string;
1056
+ inbox: string;
1057
+ inboxes: string;
1058
+ members: string;
1036
1059
  };
1037
1060
  reasons: {
1038
1061
  rateLimited: string;
@@ -1694,6 +1717,59 @@ export declare const enResources: {
1694
1717
  correction: string;
1695
1718
  };
1696
1719
  };
1720
+ inboxes: {
1721
+ title: string;
1722
+ add: string;
1723
+ emptyTitle: string;
1724
+ emptyBody: string;
1725
+ description: string;
1726
+ edit: string;
1727
+ remove: string;
1728
+ status: string;
1729
+ whoCanOpen: string;
1730
+ admin: string;
1731
+ accessHint: string;
1732
+ typeLinkedPhone: string;
1733
+ numberAfterScan: string;
1734
+ limitReached: string;
1735
+ membersField: string;
1736
+ membersFieldHint: string;
1737
+ membersFieldNoAgents: string;
1738
+ membersFieldNone: string;
1739
+ removeTitle: string;
1740
+ removeConfirm: string;
1741
+ removeBodyPhone: string;
1742
+ removed: string;
1743
+ required: string;
1744
+ saved: string;
1745
+ createdPhone: string;
1746
+ addTitle: string;
1747
+ editTitle: string;
1748
+ dialogDescription: string;
1749
+ save: string;
1750
+ name: string;
1751
+ nameHint: string;
1752
+ scanTitle: string;
1753
+ scanBody: string;
1754
+ scanSteps: string;
1755
+ linkedAs: string;
1756
+ relink: string;
1757
+ offlineTitle: string;
1758
+ offlineBody: string;
1759
+ state: {
1760
+ connected: string;
1761
+ waiting_scan: string;
1762
+ phone_offline: string;
1763
+ };
1764
+ membersAccess: string;
1765
+ membersAccessAll: string;
1766
+ membersAccessNone: string;
1767
+ membersAccessTitle: string;
1768
+ membersAccessDescription: string;
1769
+ membersAccessNoInboxes: string;
1770
+ membersAccessSave: string;
1771
+ membersAccessSaved: string;
1772
+ };
1697
1773
  };
1698
1774
  errors: {
1699
1775
  generic: string;
@@ -1,6 +1,9 @@
1
+ import type { Inbox } from '@ezychat/shared';
1
2
  import { type ChatFilters as Filters } from '../api/queries';
2
3
  export interface ChatFiltersProps {
3
4
  value: Filters;
4
5
  onChange(next: Filters): void;
6
+ /** The active inboxes the user may open; the inbox filter shows with two or more. */
7
+ inboxes?: readonly Inbox[];
5
8
  }
6
- export declare function ChatFilters({ value, onChange }: ChatFiltersProps): import("react").JSX.Element;
9
+ export declare function ChatFilters({ value, onChange, inboxes }: ChatFiltersProps): import("react").JSX.Element;
@@ -1,4 +1,4 @@
1
- import type { Chat } from '@ezychat/shared';
1
+ import type { Chat, Inbox } from '@ezychat/shared';
2
2
  export interface ChatListItemProps {
3
3
  chat: Chat;
4
4
  active: boolean;
@@ -6,8 +6,10 @@ export interface ChatListItemProps {
6
6
  assigneeName: string | null;
7
7
  /** The current user owns this chat: the chip gets the accent outline. */
8
8
  assignedToMe?: boolean;
9
+ /** The chat's inbox, only while the user sees two or more inboxes. */
10
+ inbox?: Pick<Inbox, 'name' | 'tone'>;
9
11
  }
10
12
  export declare function GroupIcon({ className }: {
11
13
  className?: string;
12
14
  }): import("react").JSX.Element;
13
- export declare function ChatListItem({ chat, active, assigneeName, assignedToMe, }: ChatListItemProps): import("react").JSX.Element;
15
+ export declare function ChatListItem({ chat, active, assigneeName, assignedToMe, inbox, }: ChatListItemProps): import("react").JSX.Element;
@@ -1,4 +1,4 @@
1
- import type { Chat } from '@ezychat/shared';
1
+ import type { Chat, Inbox } from '@ezychat/shared';
2
2
  import type { Directory } from './useDirectory';
3
3
  export interface ConversationHeaderProps {
4
4
  chat: Chat;
@@ -20,6 +20,8 @@ export interface ConversationHeaderProps {
20
20
  onOpenInfo?(): void;
21
21
  /** Group chats: WhatsApp's member count, shown under the name once known. */
22
22
  memberCount?: number | null;
23
+ /** The chat's inbox, only while the user sees two or more inboxes (replies leave from it). */
24
+ inbox?: Pick<Inbox, 'name' | 'tone'>;
23
25
  busy?: boolean;
24
26
  }
25
- export declare function ConversationHeader({ chat, directory, onBack, onAssign, onToggleStatus, notesOpen, notesCount, onToggleNotes, showCustomer, customerOpen, onToggleCustomer, onOpenInfo, memberCount, busy, }: ConversationHeaderProps): import("react").JSX.Element;
27
+ export declare function ConversationHeader({ chat, directory, onBack, onAssign, onToggleStatus, notesOpen, notesCount, onToggleNotes, showCustomer, customerOpen, onToggleCustomer, onOpenInfo, memberCount, inbox, busy, }: ConversationHeaderProps): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import type { Inbox } from '@ezychat/shared';
2
+ /** The inbox a chat belongs to, in that inbox's colour (shown only with two or more inboxes). */
3
+ export declare function InboxChip({ inbox, className, }: Readonly<{
4
+ inbox: Pick<Inbox, 'name' | 'tone'>;
5
+ className?: string;
6
+ }>): import("react").JSX.Element;
@@ -4,6 +4,11 @@ type WaStateName = WaStatus['state'];
4
4
  export declare function waQueuesMessages(state: WaStateName | undefined): boolean;
5
5
  /** States where sending is pointless until an admin acts (composer disabled). */
6
6
  export declare function waSendingBlocked(state: WaStateName | undefined): boolean;
7
- /** Top-of-inbox banner shown whenever the WhatsApp connection isn't open. */
7
+ /**
8
+ * Top-of-inbox banner shown whenever a WhatsApp connection isn't open. With one inbox (Lite) it is
9
+ * today's single banner; with two or more visible inboxes there is one per inbox that is not open,
10
+ * titled with that inbox's name. A user who sees one inbox other than inbox 1 gets that inbox's
11
+ * banner (named, never inbox 1's state); with several inboxes and none visible there is no banner.
12
+ */
8
13
  export declare function WaBanner(): import("react").JSX.Element | null;
9
14
  export {};
@@ -1,18 +1,35 @@
1
1
  import type { User } from '@ezychat/shared';
2
2
  import { type DirectoryUser } from '../api/queries';
3
+ /**
4
+ * A team member as the inbox knows them: the directory row (agents), or the full user (admins read
5
+ * `GET /api/users`, where `inboxIds` is set for agents only). No `inboxIds` means every inbox only
6
+ * in a single-inbox install; with several inboxes it means no inbox (fail closed).
7
+ */
8
+ export type DirectoryEntry = Omit<DirectoryUser, 'inboxIds'> & {
9
+ inboxIds?: DirectoryUser['inboxIds'];
10
+ };
11
+ /**
12
+ * The member may open chats of this inbox (admins and the AI member open every inbox). A row without
13
+ * `inboxIds` opens every inbox only when `multiInbox` is false (one inbox, no inbox UI).
14
+ */
15
+ export declare function canOpenInbox(u: DirectoryEntry, inboxId: number, multiInbox?: boolean): boolean;
3
16
  export interface Directory {
4
17
  me: User | null;
5
18
  isAdmin: boolean;
6
- byId: Map<number, DirectoryUser>;
19
+ byId: Map<number, DirectoryEntry>;
7
20
  /** Users that can be picked as assignee (active only). */
8
- assignable: DirectoryUser[];
21
+ assignable: DirectoryEntry[];
22
+ /** Active users who may open chats of this inbox: the assign dropdown of such a chat. */
23
+ assignableFor(inboxId: number): DirectoryEntry[];
9
24
  /** Display name for a user id ("You" for the current user when `youLabel`). */
10
25
  nameOf(id: number | null | undefined, opts?: {
11
26
  youLabel?: boolean;
12
27
  }): string | null;
13
28
  }
14
29
  /** Pure builder (exported for tests). */
15
- export declare function buildDirectory(me: User | null, isAdmin: boolean, list: readonly DirectoryUser[] | undefined): Directory;
30
+ export declare function buildDirectory(me: User | null, isAdmin: boolean, list: readonly DirectoryEntry[] | undefined, opts?: {
31
+ multiInbox?: boolean;
32
+ }): Directory;
16
33
  /**
17
34
  * Team member lookup for the inbox. Admins use the full `GET /api/users`; every other role
18
35
  * uses the public `GET /api/users/directory` ({id, displayName, role, disabled}). Ids that are
@@ -0,0 +1,12 @@
1
+ import type { Inbox } from '@ezychat/shared';
2
+ export { useInboxStatus } from '../api/queries';
3
+ /**
4
+ * The active inboxes the signed-in user may open. The server already filters `GET /api/inboxes` by
5
+ * access, so there is no access logic here; removed inboxes (admins only) are left out.
6
+ */
7
+ export declare function useVisibleInboxes(): Inbox[];
8
+ /**
9
+ * The inbox chip of a chat: the chat's inbox while the user sees two or more inboxes, otherwise
10
+ * undefined (one inbox and Lite show no chip). Removed inboxes still name their read-only chats.
11
+ */
12
+ export declare function useInboxOfChat(): (inboxId: number) => Inbox | undefined;
@@ -0,0 +1,5 @@
1
+ import type React from 'react';
2
+ /** A message body as WhatsApp shows it (React nodes for a `whitespace-pre-wrap` element). */
3
+ export declare function formatWhatsApp(text: string): React.ReactNode[];
4
+ /** The same text without the marks WhatsApp hides, for one-line previews (quotes, chat list). */
5
+ export declare function plainWhatsApp(text: string): string;
@@ -1,4 +1,9 @@
1
1
  /** "ABCD1234" → "ABCD-1234", the way WhatsApp displays pairing codes. */
2
2
  export declare function formatPairingCode(code: string): string;
3
- /** Link WhatsApp with a phone number + 8-character pairing code instead of scanning a QR. */
4
- export declare function PhoneLink(): import("react").JSX.Element;
3
+ /**
4
+ * Link WhatsApp with a phone number + 8-character pairing code instead of scanning a QR. Without
5
+ * `inboxId` it links the first number (`/wa/pairing-code`, as before multi-inbox).
6
+ */
7
+ export declare function PhoneLink({ inboxId }?: Readonly<{
8
+ inboxId?: number;
9
+ }>): import("react").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ezychat/web",
3
- "version": "0.1.46",
3
+ "version": "0.1.47",
4
4
  "type": "module",
5
5
  "description": "EzyChat core: the team inbox web app (React) as a library, with createInboxApp and its extension API.",
6
6
  "license": "MIT",
@@ -40,7 +40,7 @@
40
40
  "build:lib": "node ../../scripts/build-web-lib.mjs"
41
41
  },
42
42
  "dependencies": {
43
- "@ezychat/shared": "0.1.46",
43
+ "@ezychat/shared": "0.1.47",
44
44
  "@fontsource-variable/inter": "^5.3.0",
45
45
  "@tanstack/react-query": "^5.104.4",
46
46
  "@types/react": "^19.3.0",