feat(settings): premium single-column layout, onboarding banner, and unit tests - #16
Conversation
…th project logo styles
…ent portal explainer
…ation for signup admins
…Settings checks, optimize dependency array
…fect dependency array
… invite test error
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 21 minutes and 24 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR enhances the Settings feature by introducing React Query test infrastructure, exporting Settings utilities, updating WorkspaceTab state logic to use the current user session for form defaults when settings are pristine, overhauling the WorkspaceTab and FeaturesTab UI with new card-based layouts, and adding comprehensive test coverage for the updated Settings route. ChangesSettings Feature Enhancement
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6263bf0e06
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| setSaveStatus("saving"); | ||
| try { | ||
| const portalUrl = `https://useclientra.com/portal/${portalPath}`; | ||
| const portalUrl = `https://useclientra.vercel.app/portal/${portalPath}`; |
There was a problem hiding this comment.
Keep portal links on the production domain
When an admin saves workspace settings, this now persists a portalUrl on useclientra.vercel.app instead of the previous branded useclientra.com domain, and the same hardcoded host is used for the copied/displayed URL. In production this publishes and shares the Vercel deployment URL for any workspace profile edit, which is a regression from the existing production-facing portal link and should stay on the canonical app host or come from configuration.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR redesigns the Settings page to a centered single-column layout (
Confidence Score: 4/5Safe to merge; all changes are UI/UX and test infrastructure with no data-loss or auth risk. The core settings save/load flow is unchanged and the new onboarding logic works correctly for the primary scenario. The intentional omission of No files require special attention; Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[WorkspaceTab mounts] --> B{settings loaded?}
B -- No --> C[form = empty strings]
B -- Yes --> D{isDefaultSettings?}
D -- Yes + currentUser --> E[useEffect: pre-fill with currentUser.name / email]
D -- No --> F[useEffect: fill from settings.workspaceName / supportEmail]
E --> G[Onboarding banner visible]
F --> H[No banner]
G --> I[User reviews / edits form]
H --> I
I --> J[Save Changes]
J --> K[mutateAsync → cache invalidated → settings refetch]
K --> L{isDefaultSettings changed?}
L -- Yes: now false --> M[useEffect re-runs → else branch → form synced]
L -- No: still false --> N[Effect does NOT re-run, form retains user edits]
M --> O[Banner hidden]
N --> P[Form stays as-is]
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
src/__tests__/settings.test.tsx:48-54
**Module-scoped QueryClient shared across tests**
`queryClient` is created once at module scope and never cleared between test cases. If React Query writes anything to the cache during a test (even from intercepted hooks that partially execute before mocks kick in), stale entries persist into the next test. The `afterEach(cleanup)` handles the DOM but not the query cache. Adding `queryClient.clear()` to the `afterEach` block would prevent this. The same pattern also appears in `client-pending-invites.test.tsx` at the same scope level.
### Issue 2 of 2
src/routes/settings.tsx:158-170
**Stale `settings` closure in non-default branch of `useEffect`**
`settings` is referenced inside the effect's `else` branch but deliberately excluded from the dependency array. When settings are NOT at factory defaults, the effect only re-runs if `isDefaultSettings`, `currentUser?.name`, or `currentUser?.email` changes. If React Query performs a background refetch that updates `settings.workspaceName` or `settings.supportEmail` while `isDefaultSettings` stays `false` and the user identity hasn't changed, the effect won't re-run, so the form fields silently diverge from the server state. The biome-ignore comment says "Only trigger on defaults and user credential changes," but the consequence is that server-side changes to a non-default workspace (e.g., from another tab or admin update) are never reflected in the form until a full remount.
```suggestion
// Sync local state when settings load.
// `settings` is intentionally excluded so background refetches do not
// clobber in-progress edits. `isDefaultSettings` is derived from
// `settings`, so any change that affects default detection still triggers
// the effect. The tradeoff is that external changes to a non-default
// workspace (e.g. from another tab) won't be reflected until remount.
// biome-ignore lint/correctness/useExhaustiveDependencies: intentional – see comment above
useEffect(() => {
if (settings) {
if (isDefaultSettings && currentUser) {
setWorkspaceName(currentUser.name ?? "My Workspace");
setSupportEmail(currentUser.email ?? DEFAULT_SUPPORT_EMAIL);
} else {
setWorkspaceName(settings.workspaceName);
setSupportEmail(settings.supportEmail);
}
}
}, [isDefaultSettings, currentUser?.name, currentUser?.email]);
```
Reviews (1): Last reviewed commit: "test(settings): add workspace tab and sl..." | Re-trigger Greptile |
| const queryClient = new QueryClient({ | ||
| defaultOptions: { | ||
| queries: { | ||
| retry: false, | ||
| }, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Module-scoped QueryClient shared across tests
queryClient is created once at module scope and never cleared between test cases. If React Query writes anything to the cache during a test (even from intercepted hooks that partially execute before mocks kick in), stale entries persist into the next test. The afterEach(cleanup) handles the DOM but not the query cache. Adding queryClient.clear() to the afterEach block would prevent this. The same pattern also appears in client-pending-invites.test.tsx at the same scope level.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/__tests__/settings.test.tsx
Line: 48-54
Comment:
**Module-scoped QueryClient shared across tests**
`queryClient` is created once at module scope and never cleared between test cases. If React Query writes anything to the cache during a test (even from intercepted hooks that partially execute before mocks kick in), stale entries persist into the next test. The `afterEach(cleanup)` handles the DOM but not the query cache. Adding `queryClient.clear()` to the `afterEach` block would prevent this. The same pattern also appears in `client-pending-invites.test.tsx` at the same scope level.
How can I resolve this? If you propose a fix, please make it concise.| // Sync local state when settings load | ||
| // biome-ignore lint/correctness/useExhaustiveDependencies: Only trigger on defaults and user credential changes | ||
| useEffect(() => { | ||
| if (settings) { | ||
| setWorkspaceName(settings.workspaceName); | ||
| setSupportEmail(settings.supportEmail); | ||
| if (isDefaultSettings && currentUser) { | ||
| setWorkspaceName(currentUser.name ?? "My Workspace"); | ||
| setSupportEmail(currentUser.email ?? DEFAULT_SUPPORT_EMAIL); | ||
| } else { | ||
| setWorkspaceName(settings.workspaceName); | ||
| setSupportEmail(settings.supportEmail); | ||
| } | ||
| } | ||
| }, [settings]); | ||
| }, [isDefaultSettings, currentUser?.name, currentUser?.email]); |
There was a problem hiding this comment.
Stale
settings closure in non-default branch of useEffect
settings is referenced inside the effect's else branch but deliberately excluded from the dependency array. When settings are NOT at factory defaults, the effect only re-runs if isDefaultSettings, currentUser?.name, or currentUser?.email changes. If React Query performs a background refetch that updates settings.workspaceName or settings.supportEmail while isDefaultSettings stays false and the user identity hasn't changed, the effect won't re-run, so the form fields silently diverge from the server state. The biome-ignore comment says "Only trigger on defaults and user credential changes," but the consequence is that server-side changes to a non-default workspace (e.g., from another tab or admin update) are never reflected in the form until a full remount.
| // Sync local state when settings load | |
| // biome-ignore lint/correctness/useExhaustiveDependencies: Only trigger on defaults and user credential changes | |
| useEffect(() => { | |
| if (settings) { | |
| setWorkspaceName(settings.workspaceName); | |
| setSupportEmail(settings.supportEmail); | |
| if (isDefaultSettings && currentUser) { | |
| setWorkspaceName(currentUser.name ?? "My Workspace"); | |
| setSupportEmail(currentUser.email ?? DEFAULT_SUPPORT_EMAIL); | |
| } else { | |
| setWorkspaceName(settings.workspaceName); | |
| setSupportEmail(settings.supportEmail); | |
| } | |
| } | |
| }, [settings]); | |
| }, [isDefaultSettings, currentUser?.name, currentUser?.email]); | |
| // Sync local state when settings load. | |
| // `settings` is intentionally excluded so background refetches do not | |
| // clobber in-progress edits. `isDefaultSettings` is derived from | |
| // `settings`, so any change that affects default detection still triggers | |
| // the effect. The tradeoff is that external changes to a non-default | |
| // workspace (e.g. from another tab) won't be reflected until remount. | |
| // biome-ignore lint/correctness/useExhaustiveDependencies: intentional – see comment above | |
| useEffect(() => { | |
| if (settings) { | |
| if (isDefaultSettings && currentUser) { | |
| setWorkspaceName(currentUser.name ?? "My Workspace"); | |
| setSupportEmail(currentUser.email ?? DEFAULT_SUPPORT_EMAIL); | |
| } else { | |
| setWorkspaceName(settings.workspaceName); | |
| setSupportEmail(settings.supportEmail); | |
| } | |
| } | |
| }, [isDefaultSettings, currentUser?.name, currentUser?.email]); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/settings.tsx
Line: 158-170
Comment:
**Stale `settings` closure in non-default branch of `useEffect`**
`settings` is referenced inside the effect's `else` branch but deliberately excluded from the dependency array. When settings are NOT at factory defaults, the effect only re-runs if `isDefaultSettings`, `currentUser?.name`, or `currentUser?.email` changes. If React Query performs a background refetch that updates `settings.workspaceName` or `settings.supportEmail` while `isDefaultSettings` stays `false` and the user identity hasn't changed, the effect won't re-run, so the form fields silently diverge from the server state. The biome-ignore comment says "Only trigger on defaults and user credential changes," but the consequence is that server-side changes to a non-default workspace (e.g., from another tab or admin update) are never reflected in the form until a full remount.
```suggestion
// Sync local state when settings load.
// `settings` is intentionally excluded so background refetches do not
// clobber in-progress edits. `isDefaultSettings` is derived from
// `settings`, so any change that affects default detection still triggers
// the effect. The tradeoff is that external changes to a non-default
// workspace (e.g. from another tab) won't be reflected until remount.
// biome-ignore lint/correctness/useExhaustiveDependencies: intentional – see comment above
useEffect(() => {
if (settings) {
if (isDefaultSettings && currentUser) {
setWorkspaceName(currentUser.name ?? "My Workspace");
setSupportEmail(currentUser.email ?? DEFAULT_SUPPORT_EMAIL);
} else {
setWorkspaceName(settings.workspaceName);
setSupportEmail(settings.supportEmail);
}
}
}, [isDefaultSettings, currentUser?.name, currentUser?.email]);
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/routes/settings.tsx`:
- Around line 159-170: The useEffect that sets workspaceName/supportEmail
(useEffect) currently only depends on isDefaultSettings and currentUser fields,
so when non-default settings change the effect won't run; update the dependency
array to include settings (or at minimum settings.workspaceName and
settings.supportEmail) so that when settings updates the effect reruns and calls
setWorkspaceName and setSupportEmail accordingly, keeping portalPath/hasChanges
and inputs in sync; retain the existing isDefaultSettings/currentUser checks and
keep the biome-ignore comment if needed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 75f53d38-8afc-46c6-841b-60d0adf19012
📒 Files selected for processing (3)
src/__tests__/client-pending-invites.test.tsxsrc/__tests__/settings.test.tsxsrc/routes/settings.tsx
… useEffect closure
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 20e3bd3549
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| vi.mock("@/lib/api", () => ({ | ||
| useSettingsData: () => ({ | ||
| data: mockSettings, |
There was a problem hiding this comment.
Avoid closing hoisted mocks over local fixtures
In Vitest, vi.mock factories are hoisted and executed before the module-scope fixtures here are initialized, so this mock closes over mockSettings (and the auth mock similarly closes over mockUser) while those const bindings are still in the temporal dead zone. Running this test file will fail during module mocking before any assertions run; move these fixtures into vi.hoisted, inline them inside the factory, or return them from a hoisted setup.
Useful? React with 👍 / 👎.
…vi.mock factories
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e163116aa8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| vi.mock("@/lib/api", () => ({ | ||
| useSettingsData: () => ({ | ||
| data: mockSettings, | ||
| isLoading: false, | ||
| error: null, | ||
| }), | ||
| useUpdateSettingsMutation: () => ({ | ||
| mutateAsync: vi.fn(), | ||
| isPending: false, | ||
| }), |
There was a problem hiding this comment.
Provide all route imports in the API mock
When this test imports @/routes/settings, the route module also imports ensureSettingsData to construct the TanStack route loader, but the full @/lib/api mock only exposes the two hooks here. Under Vitest this causes the settings module import to fail with a missing mocked export before any assertions run; include ensureSettingsData (or use vi.importActual for unmocked exports) so the route can be evaluated.
Useful? React with 👍 / 👎.
Description
This PR delivers the premium Settings page redesign alongside custom settings onboarding, pre-population of logged-in user details, fully optimized state synchronization, and robust Vitest unit tests.
Changes Included
Verification Results
Summary by CodeRabbit
New Features
Bug Fixes
Style