Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions apps/web/src/components/chat/ChatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ export const ChatList = memo(function ChatList({
isLoading: areConnectionsLoading,
isError: areConnectionsUnavailable,
} = useWhatsAppConnections();
const { data: channelAccounts = [] } = useChannelAccounts();
const {
data: channelAccounts = [],
isLoading: areChannelAccountsLoading,
isError: areChannelAccountsUnavailable,
} = useChannelAccounts();

useEffect(() => {
if (
Expand Down Expand Up @@ -253,8 +257,9 @@ export const ChatList = memo(function ChatList({
]);
const connectionState = resolveInboxConnectionState({
connections,
isLoading: areConnectionsLoading,
isError: areConnectionsUnavailable,
channelAccounts,
isLoading: areConnectionsLoading || areChannelAccountsLoading,
isError: areConnectionsUnavailable || areChannelAccountsUnavailable,
});
const isFirstRun =
connectionState === "no-connections" && visibleChats.length === 0;
Expand Down Expand Up @@ -750,9 +755,9 @@ export const ChatList = memo(function ChatList({
</div>
)}

{/* First-run state - WhatsApp has not been linked yet. This is also
the primary mobile onboarding surface, where the main pane is not
visible until a conversation is selected. */}
{/* First-run state - no provider account has been linked yet. This is
also the primary mobile onboarding surface, where the main pane is
not visible until a conversation is selected. */}
{!isLoading &&
!isError &&
isFirstRun &&
Expand Down Expand Up @@ -783,12 +788,12 @@ export const ChatList = memo(function ChatList({
<p className="mt-2 text-sm leading-6 text-[#6b7b75] dark:text-dark-text-secondary">
{can("can_manage_connections")
? t(
"chat.connectWhatsappSidebarHint",
"Connect one WhatsApp account and conversations will begin appearing here automatically.",
"chat.connectChannelsSidebarHint",
"Connect WhatsApp or Telegram and conversations will begin appearing here automatically.",
)
: t(
"chat.askAdminToConnectShort",
"Ask a workspace owner or admin to connect a WhatsApp account.",
"chat.askAdminToConnectChannelShort",
"Ask a workspace owner or admin to connect a channel.",
)}
</p>

Expand All @@ -800,14 +805,14 @@ export const ChatList = memo(function ChatList({
<div className="min-w-0">
<p className="text-sm font-semibold text-[#304d43] dark:text-[#d0e1db]">
{t(
"chat.sidebarConnectStep",
"Connect a WhatsApp account",
"chat.sidebarConnectChannelStep",
"Connect your first channel",
)}
</p>
<p className="mt-1 text-xs leading-5 text-[#7a8983] dark:text-dark-text-tertiary">
{t(
"chat.sidebarConnectStepHint",
"Pair securely by scanning a QR code",
"chat.sidebarConnectChannelStepHint",
"Choose WhatsApp or Telegram to start",
)}
</p>
</div>
Expand Down Expand Up @@ -835,7 +840,7 @@ export const ChatList = memo(function ChatList({
)}
className="mt-6 inline-flex h-11 items-center justify-center gap-2 rounded-xl bg-[#14795e] px-4 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-[#0f684f] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#00a884] focus-visible:ring-offset-2 dark:bg-[#20b68e] dark:text-[#071b16] dark:hover:bg-[#35c59e] dark:focus-visible:ring-offset-dark-secondary md:hidden"
>
{t("chat.connectWhatsapp", "Connect WhatsApp")}
{t("chat.connectChannels", "Connect channels")}
<ArrowRight className="size-4" aria-hidden="true" />
</Link>
)}
Expand Down
19 changes: 19 additions & 0 deletions apps/web/src/components/chat/inbox-connection-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,23 @@ describe("resolveInboxConnectionState", () => {
}),
).toBe("offline");
});

it("never shows first-run setup when any provider account is already known", () => {
expect(
resolveInboxConnectionState({
connections: [],
channelAccounts: [{ status: "connected" }],
isLoading: true,
isError: false,
}),
).toBe("connected");
expect(
resolveInboxConnectionState({
connections: [],
channelAccounts: [{ status: "paused" }],
isLoading: false,
isError: true,
}),
).toBe("offline");
});
});
20 changes: 14 additions & 6 deletions apps/web/src/components/chat/inbox-connection-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,30 @@ interface ConnectionStatus {
}

/**
* Resolves the inbox landing state without treating a failed or pending query
* as proof that a workspace has never linked WhatsApp.
* Resolves the inbox landing state across every provider without treating a
* failed or pending query as proof that a workspace has never linked a channel.
*/
export function resolveInboxConnectionState({
connections,
channelAccounts = [],
isLoading,
isError,
}: {
connections: readonly ConnectionStatus[];
channelAccounts?: readonly ConnectionStatus[];
isLoading: boolean;
isError: boolean;
}): InboxConnectionState {
// A known account wins over another provider source that is still loading
// or unavailable. Otherwise a connected Telegram bot plus an empty or
// failed WhatsApp query can incorrectly reopen first-run onboarding.
const accounts = [...connections, ...channelAccounts];
if (accounts.length > 0) {
return accounts.some((connection) => connection.status === "connected")
? "connected"
: "offline";
}
if (isError) return "unavailable";
if (isLoading) return "loading";
if (connections.length === 0) return "no-connections";
return connections.some((connection) => connection.status === "connected")
? "connected"
: "offline";
return "no-connections";
}
19 changes: 19 additions & 0 deletions apps/web/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,21 @@
"whatsappSynced": "WhatsApp synced",
"whatsappOffline": "WhatsApp needs attention",
"checkingWhatsapp": "Checking WhatsApp connection",
"checkingChannels": "Checking connected channels",
"sharedInboxSetup": "Shared inbox setup",
"connectChannelsTitle": "Connect your first channel",
"connectChannelsHint": "Bring WhatsApp and Telegram into one team inbox. Messages, assignments, and follow-ups stay together as you add more channels.",
"connectChannels": "Connect channels",
"askAdminToConnectChannel": "Ask a workspace owner or admin to connect a channel. Conversations will appear here once an account is linked.",
"channelCredentialsHint": "Channel credentials are encrypted, and each connected account stays under your control.",
"chooseChannelTitle": "Choose where to start",
"chooseChannelHint": "Connect the channels your customers already use.",
"whatsappChannel": "WhatsApp",
"telegramChannel": "Telegram Bot",
"readyNow": "Ready now",
"whatsappConnectHint": "Pair securely with a QR code",
"telegramConnectHint": "Add a bot token from BotFather",
"moreChannelsSoon": "More channels are on the way",
"connectWhatsappEyebrow": "One-minute setup",
"connectWhatsappTitle": "Bring WhatsApp into your team inbox",
"connectWhatsappHint": "Link your WhatsApp account once. Conversations will sync here so your team can reply, assign, and follow up together.",
Expand All @@ -315,9 +330,13 @@
"oneStepRemaining": "One step remaining",
"sidebarSetupTitle": "Your inbox is almost ready",
"connectWhatsappSidebarHint": "Connect one WhatsApp account and conversations will begin appearing here automatically.",
"connectChannelsSidebarHint": "Connect WhatsApp or Telegram and conversations will begin appearing here automatically.",
"askAdminToConnectShort": "Ask a workspace owner or admin to connect a WhatsApp account.",
"askAdminToConnectChannelShort": "Ask a workspace owner or admin to connect a channel.",
"sidebarConnectStep": "Connect a WhatsApp account",
"sidebarConnectStepHint": "Pair securely by scanning a QR code",
"sidebarConnectChannelStep": "Connect your first channel",
"sidebarConnectChannelStepHint": "Choose WhatsApp or Telegram to start",
"sidebarReadyStep": "Chats and groups sync automatically",
"sidebarContinueHint": "Continue setup in the main panel. It usually takes about a minute.",
"selectChatHint": "Select a chat from the list to begin",
Expand Down
19 changes: 19 additions & 0 deletions apps/web/src/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,21 @@
"whatsappSynced": "已与 WhatsApp 同步",
"whatsappOffline": "WhatsApp 连接需要处理",
"checkingWhatsapp": "正在检查 WhatsApp 连接",
"checkingChannels": "正在检查已连接的渠道",
"sharedInboxSetup": "共享收件箱设置",
"connectChannelsTitle": "连接你的第一个渠道",
"connectChannelsHint": "将 WhatsApp 和 Telegram 接入同一个团队收件箱。随着更多渠道加入,消息、分配和跟进始终集中管理。",
"connectChannels": "连接渠道",
"askAdminToConnectChannel": "请联系工作区所有者或管理员连接渠道。账号关联后,会话将显示在这里。",
"channelCredentialsHint": "渠道凭据会被加密,每个已连接账号始终由你掌控。",
"chooseChannelTitle": "选择起始渠道",
"chooseChannelHint": "连接客户已经在使用的渠道。",
"whatsappChannel": "WhatsApp",
"telegramChannel": "Telegram 机器人",
"readyNow": "现在可用",
"whatsappConnectHint": "扫描二维码安全配对",
"telegramConnectHint": "添加 BotFather 提供的机器人令牌",
"moreChannelsSoon": "更多渠道即将上线",
"connectWhatsappEyebrow": "一分钟完成设置",
"connectWhatsappTitle": "将 WhatsApp 接入团队收件箱",
"connectWhatsappHint": "只需关联一次 WhatsApp 账号,会话便会同步到这里,供团队共同回复、分配和跟进。",
Expand All @@ -315,9 +330,13 @@
"oneStepRemaining": "还剩一个步骤",
"sidebarSetupTitle": "收件箱即将就绪",
"connectWhatsappSidebarHint": "连接一个 WhatsApp 账号后,会话会自动显示在这里。",
"connectChannelsSidebarHint": "连接 WhatsApp 或 Telegram 后,会话会自动显示在这里。",
"askAdminToConnectShort": "请联系工作区所有者或管理员连接 WhatsApp 账号。",
"askAdminToConnectChannelShort": "请联系工作区所有者或管理员连接渠道。",
"sidebarConnectStep": "连接 WhatsApp 账号",
"sidebarConnectStepHint": "扫描二维码进行安全配对",
"sidebarConnectChannelStep": "连接你的第一个渠道",
"sidebarConnectChannelStepHint": "选择 WhatsApp 或 Telegram 开始",
"sidebarReadyStep": "聊天和群组将自动同步",
"sidebarContinueHint": "请在主面板继续设置,通常约一分钟即可完成。",
"selectChatHint": "从列表中选择一个会话开始",
Expand Down
Loading
Loading