feat(ui-react): add chatwoot live-chat support widget#6249
feat(ui-react): add chatwoot live-chat support widget#6249
Conversation
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
7d6c32c to
e3c9148
Compare
e3c9148 to
5fe0abd
Compare
|
/review |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
/review |
|
Claude finished @gustavosbarreto's task in 8m 55s —— View job Code Review CompleteReviewed 46 files across code quality, security, testing, TypeScript/React patterns, and architecture — 2 inline issues posted (1 high, 1 medium). Inline comments
Additional notes
If you've addressed the feedback and want a new review, tag |
5fe0abd to
865dfcf
Compare
|
/review |
|
Claude finished @gustavosbarreto's task in 2m 51s —— View job Code Review CompleteReviewed 46 files across code quality, security, testing, TypeScript/React patterns, and architecture — 2 inline issues posted (2 medium). Inline comments
Additional notes
If you've addressed the feedback and want a new review, tag |
865dfcf to
ad749ea
Compare
Implements the full billing workstream for Cloud tenants: - New hooks/useBilling.ts: useSubscription, useCustomer, useCreateCustomer, useCreateSubscription, useAttachPaymentMethod, useDetachPaymentMethod, useSetDefaultPaymentMethod, useOpenBillingPortal (axios wrapper for /api/billing/portal) - New types/billing.ts: BillingStatus union, typed Customer, Subscription, PaymentMethod, NamespaceBilling with safe narrowing - New utils/stripeErrors.ts: Stripe error-code → user message map - New BillingSection in Settings.tsx: status badge, warning banners (past_due / unpaid / incomplete / incomplete_expired / to_cancel), Subscribe button (inactive/canceled/incomplete_expired only), Open Portal button, hash-scroll + pageshow refetch for portal return - New BillingDialog wizard (4 steps): Overview → Payment method → Review → Success; lazy-loaded to exclude Stripe from initial chunk - New BillingPayment: Stripe Elements card form, payment-method list with set-default / detach, auto-creates Stripe customer on mount - New BillingWarning: 402 device-limit dialog wired to DeviceActionDialog + ContainerActionDialog - env.ts: add stripePublishableKey to ClientConfig + in-flight promise dedup for concurrent loadConfig callers - App.tsx: /settings/billing redirect route for Stripe portal return - gen-config.sh: emit SHELLHUB_STRIPE_PUBLISHABLE_KEY into config.json - docker-compose.yml: add SHELLHUB_STRIPE_PUBLISHABLE_KEY env var Fixes: shellhub-io/team#71
Covers the billing workstream with Vitest + React Testing Library: - useBilling.test.ts: mutations (create customer/subscription, attach/detach/default PM), query enable/disable behavior, useOpenBillingPortal URL open + missing-URL error - billing.test.ts (types): toCustomer and toSubscription transforms, readNamespaceBilling safe narrowing, getSubscriptionStatus sentinel - stripeErrors.test.ts: known-code mapping + unknown-code fallback - BillingDialog.test.tsx: all 4 wizard steps, Next/Back nav, error paths (402, generic, incomplete status), pending state, live region announcements, close buttons - BillingSection.test.tsx: Subscribe/portal button visibility per status, non-owner view, warning banners, dialog open/close - BillingWarning.test.tsx: renders, confirm navigates, cancel closes - Update CreateNamespaceDialog + InvitationsMenu + Login mock getConfig to include new stripePublishableKey field in ClientConfig
Wires the official Chatwoot SDK into the AppBar for cloud users with an active subscription. The SDK script is injected lazily after auth, the user is identified via an HMAC token from /api/namespaces/:tenant/support (matching the Vue UI's flow), and conversation custom attributes are tagged with the active namespace on first message. Click branches: ready -> open widget; no-subscription -> paywall dialog linking to /pricing; non-cloud -> github issue tracker. Requires SHELLHUB_CHATWOOT_IDENTITY_KEY on the api service for the backend to mint identifier hashes.
ad749ea to
f4e2d4e
Compare
f4e2d4e to
8df223e
Compare
What
In-app Chatwoot live chat for cloud users with active billing. Closes the largest remaining feature-parity gap with the Vue UI.
Depends on #6232
This branch is stacked on top of the billing PR and must merge after #6232. Several touchpoints reuse
readNamespaceBilling, theuseNamespace().billingshape, and theClientConfigextension pattern introduced there. Until #6232 lands and this branch rebases, the diff against master includes the billing files too.Changes
hooks/chatwootRuntime.ts): script latch, 15s bootstrap watchdog, andtearDownChatwoot()extracted outside the React tree soauthStore.logout()can reset it directly. Watchdog flips status tounavailableifchatwoot:readynever fires (invalid token, content blocker, CSP) — the button never sits spinning forever.hooks/useChatwoot.ts): orchestrates injection, identifies the user via/api/namespaces/:tenant/supportHMAC, sets conversation custom attributes (namespace, tenant, domain) on the first message after each tenant change. Status:non-cloud | unavailable | no-subscription | loading | ready.components/layout/SupportButton.tsx): three branches — ready opens the widget; no-subscription opens a paywall dialog mirroring Vue'sPaywallChatand linking to/pricing; non-cloud links to the GitHub issue tracker.tearDownChatwoot()removes the script tag, the SDK-injected DOM (chat bubble holders + iframe), and clearswindow.\$chatwoot/window.chatwootSDKglobals. Called fromlogout()and from theloginWithToken()failure path so a stale widget never survives into the login screen.chatwootWebsiteToken+chatwootBaseUrladded toClientConfig;gen-config.shreads them fromSHELLHUB_CHATWOOT_WEBSITE_TOKENandSHELLHUB_CHATWOOT_BASEURL(same names the Vue UI uses);docker-compose.ymlforwards them to theui-reactcontainer.Backend dependency
/api/namespaces/:tenant/supportrequiresSHELLHUB_CHATWOOT_IDENTITY_KEYon theapiservice for HMAC minting. The env loader already reads it; the cloud docker-compose overlay needs a one-line addition to wire it through, tracked in a separate cloud-repo change. Without that env, the endpoint returns 400 "chatwoot is disabled" — the frontend handles this gracefully (useSupportIdentifierflips to error → status becomesunavailableand the button hides).Testing
SHELLHUB_CHATWOOT_*set on ui-react,SHELLHUB_CHATWOOT_IDENTITY_KEYon api, and an active subscription — Support icon → widget opens, identified, custom attrs reach the Chatwoot dashboard. Cancel subscription → icon → paywall dialog → Upgrade opens/pricingin a new tab. Log out → widget DOM disappears, no leftover iframe on/login. Operator forgetsIDENTITY_KEY→ exactly one 400 in the network tab, button hides instead of spinning.