Skip to content

feat(organizations): soft suggestedJoin onboarding banner + recovery-screen copy#4176

Merged
PierreBrisorgueil merged 7 commits into
masterfrom
feat/suggested-join-banner
May 19, 2026
Merged

feat(organizations): soft suggestedJoin onboarding banner + recovery-screen copy#4176
PierreBrisorgueil merged 7 commits into
masterfrom
feat/suggested-join-banner

Conversation

@PierreBrisorgueil
Copy link
Copy Markdown
Collaborator

What

PR (d) — the final PR of the Devkit onboarding/billing/UI-decoupling effort (PR(b) #4170, PR(c) #4175, PR(a) #3680 all merged). Consumes the suggestedJoin signup-response field PR(a) added.

  • Auth store suggestedJoin lifecycle — captured from the signup response (graceful no-op if absent → backward/forward compatible); setSuggestedJoin/dismissSuggestedJoin/clearSuggestedJoinIfMember; cleared on signout; self-healing localStorage persistence (try/catch + payload guard) mirroring the existing CookieExpire/UserRoles pattern.
  • organizations.suggestedJoinBanner — app-level dismissible banner (mounted in app.vue after adminPendingBanner, mirrors that sibling). Renders only when suggestedJoin truthy (safe global no-op when null). CTA reuses organizationsStore.createJoinRequest. Benign-rejection handling (verified against the merged Node strings): Already a member of this organization / A pending request already exists / cross-org one-pending cap You already have a pending request… / 404 → neutral info toast + dismiss, never red. Genuine 500/network → error toast, NOT dismissed (retry). Double-submit guarded (Vuetify 4 :loading doesn't block clicks).
  • Recovery-screen copyorganizations.required.view.vue recentred from onboarding to recovery framing ("No workspace found. Create one or ask for an invitation."). Copy-only; all flows (request-join / create-org / check-status / sign-out) byte-identical.

Verification

  • npm run lint: 0 errors. Full suite: 110 files / 1843 tests, 0 failures.
  • Per-task two-stage review (spec + code-quality) with fix loops — caught a corrupt-storage init fragility, an unguarded server payload, a double-submit bug, and a spec gap where the cross-org one-pending cap was wrongly classified as a hard error (now benign per spec, verified against the real merged backend strings).

Risk

Low — additive auth-store state + an opt-in banner that no-ops when suggestedJoin is absent (older/other backends unaffected). Recovery copy is behavior-neutral. Downstream propagates via /update-stack.

6 commits (f7ba701ba7ccc3c2).

Captures suggestedJoin:{orgId,orgName}|null from signup response,
persists client-side via localStorage (mirrors existing auth prefs),
and adds setSuggestedJoin / dismissSuggestedJoin /
clearSuggestedJoinIfMember actions. signout clears state + storage.
Backward-compatible: field absent = graceful no-op.

14 new TDD tests (RED → GREEN), 0 regressions (69/69 store, 165/165 auth).
… CTA (D2)

- New component `organizations.suggestedJoinBanner.component.vue`: renders a
  persistent snackbar when `authStore.suggestedJoin` is truthy; no-op when null
  (globally-mountable safe). CTA calls `organizationsStore.createJoinRequest`.
- Benign terminal rejections (already a member, pending request exists, 404 org
  not found) → neutral info toast + `dismissSuggestedJoin` — never a red toast.
- Genuine unexpected errors (500 / network) → error toast, dismiss NOT called
  so the user can retry.
- Dismiss control → `authStore.dismissSuggestedJoin()` (persisted, no nag on
  reload).
- Mounted app-level in `app.vue` alongside other global banners.
- 11 unit tests: no-op when null, render with org name, CTA success, benign
  matrix (3 cases parametrized), genuine error x2, dismiss, loading state,
  guard. 106/106 organizations tests + 168/168 auth tests green.
replace onboarding heading/intro ("Organization Required", "You need
to belong to an organization") with recovery tone — "No workspace
found" + "You are not a member of any workspace. Create one or ask
for an invitation." all interactive elements unchanged.

tests: assert new copy present, old onboarding copy absent, and
functional surface intact (create-org, sign-out, request-to-join,
check-status).
Copilot AI review requested due to automatic review settings May 19, 2026 20:21
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Warning

Rate limit exceeded

@PierreBrisorgueil has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 4 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 708c5d44-e2c4-48e4-8dc7-2cadea203175

📥 Commits

Reviewing files that changed from the base of the PR and between 1f36137 and e32fe7e.

📒 Files selected for processing (7)
  • src/modules/app/app.vue
  • src/modules/auth/stores/auth.store.js
  • src/modules/auth/tests/auth.store.unit.tests.js
  • src/modules/organizations/components/organizations.suggestedJoinBanner.component.vue
  • src/modules/organizations/tests/organizations.required.view.unit.tests.js
  • src/modules/organizations/tests/organizations.suggestedJoinBanner.component.unit.tests.js
  • src/modules/organizations/views/organizations.required.view.vue
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/suggested-join-banner

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.55%. Comparing base (1f36137) to head (e32fe7e).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #4176   +/-   ##
=======================================
  Coverage   99.55%   99.55%           
=======================================
  Files          31       31           
  Lines        1114     1136   +22     
  Branches      321      328    +7     
=======================================
+ Hits         1109     1131   +22     
  Misses          5        5           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “suggested workspace join” onboarding affordance by persisting suggestedJoin from the signup response in the auth store and surfacing it as a dismissible app-level banner, plus updates the “organization required” screen copy to a recovery framing.

Changes:

  • Persist suggestedJoin in auth.store (init from localStorage, set/dismiss/clear helpers, clear on signout) and capture it from the signup response.
  • Introduce organizationsSuggestedJoinBanner and comprehensive unit tests covering success, benign rejection, and error flows.
  • Update organizations.required.view copy and extend unit tests for the new recovery wording.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/modules/organizations/views/organizations.required.view.vue Updates heading/intro text to recovery framing (“No workspace found”).
src/modules/organizations/tests/organizations.required.view.unit.tests.js Adds assertions for the updated recovery copy and confirms key UI actions remain present.
src/modules/organizations/components/organizations.suggestedJoinBanner.component.vue New dismissible banner + CTA to request access, with benign-error handling and feedback snackbar.
src/modules/organizations/tests/organizations.suggestedJoinBanner.component.unit.tests.js New unit tests for banner rendering, request flow, benign errors, and double-submit guard.
src/modules/auth/stores/auth.store.js Adds suggestedJoin state, persistence/init, and lifecycle actions; sets from signup response and clears on signout.
src/modules/auth/tests/auth.store.unit.tests.js Adds unit tests for suggestedJoin state lifecycle, persistence, and storage corruption handling.
src/modules/app/app.vue Mounts the new organizations suggested-join banner at the app level.
Comments suppressed due to low confidence (2)

src/modules/auth/stores/auth.store.js:172

  • setSuggestedJoin() only checks for a generic object, so arrays or objects missing orgId/orgName will be persisted to localStorage and later treated as a valid suggested join. Validate that payload.orgId and payload.orgName are non-empty strings (and ignore + avoid persisting otherwise) to keep the store contract consistent.
    setSuggestedJoin(payload) {
      if (!payload || typeof payload !== 'object') return;
      this.suggestedJoin = payload;
      localStorage.setItem(`${config.cookie.prefix}SuggestedJoin`, JSON.stringify(payload));
    },

src/modules/organizations/components/organizations.suggestedJoinBanner.component.vue:8

  • The banner uses v-model="visible" but visible is a plain data field with no setter side-effect. If the snackbar closes via internal behavior (e.g., ESC / programmatic model update), visible becomes false while suggestedJoin stays persisted, leaving the banner permanently hidden until storage is cleared. Mirror organizations.adminPendingBanner's pattern: implement visible as a computed with a setter that calls dismiss() when set to false (or watch visible and dismiss).
    <v-snackbar
      v-model="visible"
      location="top right"
      color="info"
      :timeout="-1"
      multi-line

Comment thread src/modules/auth/stores/auth.store.js
Two Copilot-surfaced bugs fixed:

1. auth.store setSuggestedJoin + initFromStorage: tighten payload guard
   to require non-empty string orgId + orgName; reject arrays, objects
   missing fields, and non-string values. Invalid-but-parseable
   localStorage values now also purge the key (not only corrupt JSON).
   4 new tests cover arrays, missing fields, and non-string types.

2. organizations.suggestedJoinBanner: move feedback v-snackbar outside
   the v-if="suggestedJoin" guard. Previously dismissSuggestedJoin()
   (called right after showFeedback) set suggestedJoin null, collapsing
   the outer template v-if before the feedback snackbar could render.
   Now the primary banner uses standalone v-if; feedback snackbar is a
   sibling at root level (Vue 3 multi-root). Test updated accordingly.

lint: 0 errors — full suite: 110 files / 1847 tests, 0 failures.
@PierreBrisorgueil PierreBrisorgueil merged commit 25a3ceb into master May 19, 2026
7 checks passed
PierreBrisorgueil pushed a commit that referenced this pull request Jun 1, 2026
# [2.1.0](v2.0.0...v2.1.0) (2026-06-01)

### Bug Fixes

* **billing:** 4 pricing page bugs (signed-in features, hero bg, truncation, guest CTA) ([#4123](#4123)) ([bd399bd](bd399bd))
* **billing:** add aria-expanded + touch support to BillingComputeGauge components ([#4181](#4181)) ([7e7fa97](7e7fa97))
* **billing:** billing UX hardening — 5 reliability fixes ([#4079](#4079)) ([dd62b3d](dd62b3d)), closes [#4078](#4078)
* **billing:** canonical FA6 icons + meterError lifecycle + reject URL credentials ([#4082](#4082)) ([be16045](be16045)), closes [#4081](#4081)
* **billing:** clear intentId on Stripe cancel-redirect ([#4085](#4085)) ([11be9ab](11be9ab))
* **billing:** drop empty overage aria-live div + add fr overDetail key ([#4142](#4142)) ([10e5d81](10e5d81))
* **billing:** i18n migration + Intl.NumberFormat USD (audit Codex P1) ([#4069](#4069)) ([3d3a4f6](3d3a4f6))
* **billing:** mega vue hardening v3 — UX + design + a11y + V4 ([#4063](#4063)) ([802cbc4](802cbc4))
* **billing:** send {} body on portal POST to satisfy Zod PortalRequest schema ([#4137](#4137)) ([bf6b9c8](bf6b9c8))
* **billing:** send intentId UUID to close extras double-charge window ([#4084](#4084)) ([3e27c4c](3e27c4c))
* **billing:** subscription state safety + webhook lag polling (audit Codex P1) ([#4068](#4068)) ([328700b](328700b))
* **billing:** surface meterError + remove dead 409 dialog from subscriptions ([#4081](#4081)) ([61f5d53](61f5d53))
* **billing:** V5 polish — F5 polling recovery + visibility refetch + i18n residual + locale null-guard ([#4070](#4070)) ([4908adb](4908adb))
* **billing:** V6 polish — sessionStorage guard + locale BCP47 + i18n plural + NaN guard ([#4076](#4076)) ([2ad018c](2ad018c))
* **build:** drop ARG defaults for analytics_* + filter empty env vars ([#4112](#4112)) ([afd336e](afd336e)), closes [Vue#4110](https://github.com/Vue/issues/4110) [#4110](#4110)
* **build:** restore Layer 5 empty-string env override (revert [#4112](#4112) part 2) ([c9aa9e6](c9aa9e6)), closes [Vue#4110](https://github.com/Vue/issues/4110) [comes-io/trawl_vue#880](https://github.com/comes-io/trawl_vue/issues/880) [Vue#4110](https://github.com/Vue/issues/4110)
* **core:** pin CorePageHeader to content size in flex-column contexts ([#4210](#4210)) ([e182512](e182512)), closes [#4202](#4202)
* **layout:** route /pricing outside app shell — no drawer offset on signed-in ([#4124](#4124)) ([d862fbb](d862fbb))
* **legal:** cookie banner UA-detection + appName fallback to app.title ([#4113](#4113)) ([04cc70a](04cc70a)), closes [trawl_vue#876](https://github.com/trawl_vue/issues/876) [#4109](#4109)
* **legal:** cookie consent + footer polish (5 QA bugs) ([#4098](#4098)) ([a9bf0a3](a9bf0a3))
* **legal:** gate cookie banner on isMounted to prevent prerender hydration double-render ([#4109](#4109)) ([3198e48](3198e48)), closes [#app](https://github.com/pierreb-devkit/Vue/issues/app)
* **router:** redirect authenticated users to config.sign.route instead of hardcoded '/' ([#4088](#4088)) ([ca9094e](ca9094e)), closes [#4083](#4083)
* **tasks:** propagate store errors so views gate navigation on success ([#4221](#4221)) ([b029d39](b029d39)), closes [#4218](#4218)
* **ui:** chrome convergence — restore section title + surface backgrounds + homogeneous gutter ([#4188](#4188)) ([1e5fdf1](1e5fdf1))
* **users:** re-apply route tab when serverConfig.billing arrives async ([#4138](#4138)) ([87cc7b2](87cc7b2))
* **users:** refetch billing subscription on auth state change ([#4125](#4125)) ([5a3425a](5a3425a))

### Features

* **admin:** invitations management tab ([#4217](#4217)) ([61ad86c](61ad86c)), closes [invitedBy/#actions](https://github.com/pierreb-devkit/Vue/issues/actions)
* **analytics:** add identify and reset helpers, wire auth store ([#4104](#4104)) ([c4d8b87](c4d8b87))
* **auth:** invite-gated signup UI ([#4212](#4212)) ([d17a841](d17a841))
* **billing:** add 'Manage subscription' footer link in meterDrawer ([#4056](#4056)) ([#4057](#4057)) ([7d494af](7d494af))
* **billing:** align packs.component on BillingCardComponent (V4 unified schema) ([#4147](#4147)) ([7f5dd41](7f5dd41))
* **billing:** combined pool gauge + linear breakdown bars + alerts cleanup ([f5034b1](f5034b1))
* **billing:** config-driven static-content resolver (no file replace) ([e95d944](e95d944))
* **billing:** expose netRemainingRaw + overage in useMeter for negative quota display ([#4061](#4061)) ([111e64b](111e64b)), closes [#4060](#4060)
* **billing:** meter gauges display % primary, compute units in overflow tooltip ([#4139](#4139)) ([50498ba](50498ba))
* **billing:** meter UX refonte — drop drawer + subscriptions tab ([#4059](#4059)) ([891b5ae](891b5ae)), closes [#subscriptions](https://github.com/pierreb-devkit/Vue/issues/subscriptions) [#1](#1) [#2](#2) [#3](#3) [#4](#4)
* **billing:** post-grant upgrade prompt variant for depleted signupGrant ([#4128](#4128)) ([0ae8558](0ae8558))
* **billing:** pricing page redesign — multi-mode + auto-savings + sectioned features + FAQ ([#4102](#4102)) ([2c83ab6](2c83ab6))
* **billing:** pricingCard Free CTA — 'Sign up' for guests, route to /signup ([#4106](#4106)) ([cc92ac8](cc92ac8))
* **billing:** redesign Subscriptions view to match user-tabs aesthetic ([#4127](#4127)) ([15fd466](15fd466))
* **billing:** relocate billing under Organization settings ([#4175](#4175)) ([1f36137](1f36137))
* **billing:** sidenav compute gauge above sign-out row (meter mode) ([#4126](#4126)) ([fe1497f](fe1497f))
* **billing:** sidenav compute gauge redesign — button-shape above sign-out row ([#4140](#4140)) ([9d3a090](9d3a090))
* **billing:** sidenav compute gauge revamp — v-progress-circular + v-tooltip ([#4144](#4144)) ([5d60f1b](5d60f1b)), closes [#prepend](https://github.com/pierreb-devkit/Vue/issues/prepend)
* **billing:** subscriptions view 2-col layout — drop dup bar, CTA to /pricing#units ([#4141](#4141)) ([b7ef516](b7ef516)), closes [pricing#units](https://github.com/pricing/issues/units)
* **billing:** unified BillingCardComponent + annual toggle disabled state ([#4146](#4146)) ([474bb11](474bb11))
* **billing:** unified BillingCardComponent + annual toggle disabled state ([#4149](#4149)) ([d1403ff](d1403ff))
* **billing:** v4 hardening + Phase 3 polish — equivalences chips + UX gaps + a11y ([#4066](#4066)) ([dd40b2a](dd40b2a))
* **billing:** wire netRemainingRaw + overage into devkit components ([#4062](#4062)) ([61d6897](61d6897)), closes [#4061](#4061)
* **core:** reusable PageTabs component + Account view refactor ([#4183](#4183)) ([dc2504a](dc2504a))
* **core:** unified logo+title lockup in header and sidenav ([#4086](#4086)) ([ec457db](ec457db)), closes [#4083](#4083)
* **feature:** read ERRORS.md in Phase 0 before coding ([#4089](#4089)) ([ddf8f60](ddf8f60))
* **legal:** Add legal module + cookie consent (RGPD) ([#4097](#4097)) ([595e6c4](595e6c4)), closes [#3204116570](https://github.com/pierreb-devkit/Vue/issues/3204116570) [#3204116650](https://github.com/pierreb-devkit/Vue/issues/3204116650) [#1](#1) [#19](#19) [#18](#18) [#13](#13) [#11](#11) [#18](#18) [#3](#3) [#10](#10) [#12](#12) [#14](#14) [#15](#15) [#17](#17) [#20](#20) [#4](#4) [#5](#5) [#6](#6) [#7](#7) [#8](#8) [#9](#9) [#16](#16) [#3](#3) [#16](#16) [#22](#22)
* **legal:** liquid glass cookie banner — Vuetify-only with friendlier copy ([#4115](#4115)) ([3d74789](3d74789)), closes [#4114](#4114)
* **monitoring:** single-source PostHog Error Tracking (drop Sentry) ([#4118](#4118)) ([82dfca6](82dfca6))
* **organizations:** add Organization tab + rename General route to tab-addressable ([#4184](#4184)) ([fdfbf0b](fdfbf0b))
* **organizations:** soft suggestedJoin onboarding banner + recovery-screen copy ([#4176](#4176)) ([25a3ceb](25a3ceb))
* **seo:** enrich seoInjectPlugin — multi-schemas + rich SoftwareApplication + themeColor ([#4092](#4092)) ([#4111](#4111)) ([7f8bd09](7f8bd09))
* **skill/feature:** add Phase 0.0 issue claim-on-start ([#4117](#4117)) ([a582430](a582430)), closes [pierreb-projects/infra#28](https://github.com/pierreb-projects/infra/issues/28)
* **skills/update-stack:** block on undeclared drift vs upstream ([#4228](#4228)) ([ab8ee68](ab8ee68)), closes [#4227](#4227)
* **users:** subs full-width + delete account danger zone + halo full-bleed fix ([#4143](#4143)) ([981f073](981f073))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants