Skip to content

feat: Implement Redux for user authentication and integrate with Goog…#39

Merged
Vamsi-o merged 1 commit into
mainfrom
google-sheet-connected-to-node
Dec 20, 2025
Merged

feat: Implement Redux for user authentication and integrate with Goog…#39
Vamsi-o merged 1 commit into
mainfrom
google-sheet-connected-to-node

Conversation

@TejaBudumuru3

@TejaBudumuru3 TejaBudumuru3 commented Dec 19, 2025

Copy link
Copy Markdown
Contributor

…le Sheets API

Summary by CodeRabbit

  • New Features

    • Enhanced Google Sheets authentication with centered popup windows and fallback navigation if popups are blocked.
    • Improved user session synchronization to ensure consistent authentication state across the application.
  • Infrastructure

    • Integrated Redux state management for better user data handling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 19, 2025

Copy link
Copy Markdown

Walkthrough

This PR introduces Redux state management to the application. It establishes a Redux store with user authentication state, integrates Redux and session synchronization into the provider tree, adds typed Redux hooks, and updates components to retrieve user data from the Redux store. New dependencies include Redux Toolkit, react-redux, and next-redux-wrapper.

Changes

Cohort / File(s) Summary
Redux Store Configuration
apps/web/store/index.ts, apps/web/store/types.ts, apps/web/store/root-reducer.ts
Creates centralized Redux store setup with makeStore function, configures rootReducer combining user slice, and exports RootState and AppDispatch types for typed usage.
Redux User Slice
apps/web/store/slices/userSlice.ts
Defines AuthStatus type ('Authenticated' | 'Unauthenticated'), UserSlice interface, and creates a slice with reducers (setUserId, setUserStatus, clearUser) to manage user authentication state.
Redux Hooks
apps/web/app/hooks/redux.ts
Exports typed useAppDispatch and useAppSelector hooks providing type-safe access to Redux dispatch and state selectors.
Provider Integration
apps/web/app/components/providers.tsx, apps/web/app/layout.tsx
Introduces Providers wrapper with ReduxProvider, SessionProvider, and NextThemesProvider; adds SessionSync component to synchronize NextAuth session status with Redux store; wraps layout with Providers.
Dependencies
apps/web/package.json
Adds @reduxjs/toolkit, react-redux, and next-redux-wrapper as runtime dependencies.
Component Updates
apps/web/app/components/nodes/GoogleSheetFormClient.tsx
Retrieves userId from Redux store via useAppSelector (with fallback to empty string); introduces openAuthWindow(url) for centered OAuth popup with fallback navigation; replaces credential link with clickable div triggering popup; adds debugging logging.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Session synchronization logic in providers.tsx: Verify useSession hook integration, action dispatch logic on auth status changes, and state transitions between Authenticated/Unauthenticated.
  • Redux patterns and type safety: Confirm slice reducers, action payloads, and hook typing align with Redux Toolkit conventions.
  • OAuth popup handling in GoogleSheetFormClient.tsx: Validate window.open fallback logic, focus behavior, and popup blocking resilience.

Possibly related PRs

Suggested reviewers

  • Vamsi-o

Poem

🐰 Redux redux, state flows true,
Session syncs in every view!
From Redux store to popup bright,
OAuth windows, centered just right! 🪟✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Implement Redux for user authentication and integrate with Goog…' accurately describes the main changes: Redux setup for state management and integration with Google Sheets API authentication.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch google-sheet-connected-to-node

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (9)
apps/web/store/types.ts (1)

1-5: Potential circular dependency in type definitions.

The current setup creates an import cycle:

  • types.ts imports store from index.ts
  • index.ts imports rootReducer from root-reducer.ts

While TypeScript typically handles this, a cleaner pattern would be to derive AppDispatch from the AppStore type rather than the store instance:

🔎 Suggested refactor to eliminate circular import
 import { rootReducer } from "./root-reducer";
-import { store } from "./index";
+import type { AppStore } from "./index";

 export type RootState= ReturnType<typeof rootReducer>
-export type AppDispatch = typeof store.dispatch;
+export type AppDispatch = AppStore['dispatch'];

This avoids importing the store instance and relies only on the type.

apps/web/app/components/nodes/GoogleSheetFormClient.tsx (1)

48-65: OAuth popup implementation looks good, but consider completion handling.

The popup window creation and centering logic is well-implemented with appropriate fallbacks for blocked popups. However, there's no mechanism to detect when the OAuth flow completes and refresh the credentials list.

Consider adding:

  1. A message listener or polling mechanism to detect OAuth completion
  2. Automatic credential list refresh after successful authentication
  3. Window reference cleanup to avoid memory leaks
apps/web/store/slices/userSlice.ts (7)

1-1: Minor formatting: Add space after opening brace.

For consistency with TypeScript conventions, add a space after the opening brace in the import statement.

🔎 Proposed fix
-import {createSlice, PayloadAction } from '@reduxjs/toolkit'
+import { createSlice, PayloadAction } from '@reduxjs/toolkit'

3-3: Consider adding a loading state to AuthStatus.

The current AuthStatus type only includes 'Authenticated' and 'Unauthenticated'. In real-world applications, authentication often involves asynchronous operations (checking tokens, validating sessions, etc.). Adding a 'Loading' or 'Pending' state would improve UX by allowing components to display loading indicators during authentication checks.

🔎 Proposed enhancement
-export type AuthStatus = 'Authenticated' | 'Unauthenticated'
+export type AuthStatus = 'Authenticated' | 'Unauthenticated' | 'Loading'

You would then initialize with:

const initialState: UserState = {
    userId: null,
    status: 'Loading'  // or 'Unauthenticated' based on your needs
};

4-7: Rename interface to UserState for clarity.

The interface is named UserSlice, but it represents the state shape, not the slice itself. Renaming it to UserState would better convey its purpose and align with Redux conventions.

🔎 Proposed refactor
-export interface UserSlice {
+export interface UserState {
     userId: string | null;
     status: AuthStatus
 }

And update the usage:

-const initialState: UserSlice = {
+const initialState: UserState = {
     userId: null,
     status: 'Unauthenticated'
 };

17-23: Consider atomic user state updates.

Having separate setUserId and setUserStatus actions could lead to inconsistent state. For example, if userId is set to a value but status remains 'Unauthenticated' (or vice versa), the state becomes contradictory. Consider adding a single action that updates both fields atomically.

🔎 Proposed enhancement
 reducers: {
+    setUser(state, action: PayloadAction<{ userId: string; status: AuthStatus }>){
+        state.userId = action.payload.userId;
+        state.status = action.payload.status;
+    },
     setUserId(state, action: PayloadAction<string | null>){
         state.userId = action.payload;
     },

Then dispatch atomically:

dispatch(userActions.setUser({ userId: 'user123', status: 'Authenticated' }));

You can keep the individual setters for edge cases where you need to update fields separately.


24-26: Add state parameter to clearUser for consistency.

The clearUser reducer doesn't accept a state parameter, which is inconsistent with the other reducers. While Redux Toolkit allows this, adding the parameter makes the function signature consistent and clearer.

🔎 Proposed refactor
-    clearUser(){
+    clearUser(state){
         return initialState;
     }

Alternatively, you can use the void action pattern:

-    clearUser(){
-        return initialState;
+    clearUser(state, action: PayloadAction<void>){
+        return initialState;
     }

17-20: Minor formatting: Fix spacing inconsistencies.

There are minor formatting issues with spacing that should be addressed for consistency.

🔎 Proposed fixes
-    reducers:{
-        setUserId(state, action: PayloadAction<string |null>){
+    reducers: {
+        setUserId(state, action: PayloadAction<string | null>){
             state.userId = action.payload;
         },

30-31: Use plural naming for actions export.

The export is named userAction but it contains multiple actions. Following Redux conventions, it should be userActions (plural) to indicate it's a collection of action creators.

🔎 Proposed refactor
 export const userReducer = userSlice.reducer
-export const userAction = userSlice.actions
+export const userActions = userSlice.actions

This would make the usage more intuitive:

import { userActions } from './store/slices/userSlice';
dispatch(userActions.setUserId('user123'));
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a242d29 and 69c4e23.

📒 Files selected for processing (9)
  • apps/web/app/components/nodes/GoogleSheetFormClient.tsx (4 hunks)
  • apps/web/app/components/providers.tsx (1 hunks)
  • apps/web/app/hooks/redux.ts (1 hunks)
  • apps/web/app/layout.tsx (1 hunks)
  • apps/web/package.json (2 hunks)
  • apps/web/store/index.ts (1 hunks)
  • apps/web/store/root-reducer.ts (1 hunks)
  • apps/web/store/slices/userSlice.ts (1 hunks)
  • apps/web/store/types.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
apps/web/store/types.ts (2)
apps/web/store/root-reducer.ts (1)
  • rootReducer (4-6)
apps/web/store/index.ts (1)
  • store (9-9)
apps/web/store/root-reducer.ts (1)
apps/web/store/slices/userSlice.ts (1)
  • userReducer (30-30)
apps/web/app/components/providers.tsx (3)
apps/web/app/hooks/redux.ts (1)
  • useAppDispatch (6-6)
apps/web/store/slices/userSlice.ts (1)
  • userAction (31-31)
apps/web/store/index.ts (1)
  • store (9-9)
apps/web/app/hooks/redux.ts (1)
apps/web/store/types.ts (2)
  • AppDispatch (5-5)
  • RootState (4-4)
apps/web/app/layout.tsx (1)
apps/web/app/components/providers.tsx (1)
  • Providers (31-49)
apps/web/store/index.ts (1)
apps/web/store/root-reducer.ts (1)
  • rootReducer (4-6)
🔇 Additional comments (5)
apps/web/app/layout.tsx (1)

4-4: LGTM! Clean provider integration.

The wrapping of the application content with the Providers component is the correct approach for injecting Redux, session, and theme context into the App Router.

Also applies to: 9-12

apps/web/store/root-reducer.ts (1)

1-6: LGTM! Standard Redux Toolkit root reducer.

The root reducer setup follows Redux Toolkit best practices, correctly combining the user slice reducer.

apps/web/store/index.ts (1)

1-11: LGTM! Clean Redux Toolkit store configuration.

The store setup follows Redux Toolkit best practices with:

  • Factory function for testability
  • Conditional devTools for production builds
  • Proper type exports
apps/web/app/components/providers.tsx (1)

31-49: LGTM! Provider composition is correctly ordered.

The provider nesting follows the correct hierarchy:

  1. ReduxProvider (outermost) - makes store available
  2. SessionProvider - provides auth session
  3. NextThemesProvider - provides theme context
  4. SessionSync - can access both Redux dispatch and session hooks

This ordering ensures all hooks have access to their required contexts.

apps/web/app/hooks/redux.ts (1)

1-7: LGTM! Properly typed Redux hooks.

This follows the recommended Redux Toolkit pattern for creating typed versions of useDispatch and useSelector, providing type safety throughout the application.

import { handleSaveConfig } from './actions';
import { useCredentials } from '@/app/hooks/useCredential';
import { BACKEND_URL } from '@repo/common/zod';
import { useAppSelector } from '@/app/hooks/redux';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unused userId prop creates confusion.

The component receives userId as a prop (line 21) but immediately overrides it by reading from the Redux store (line 38). This makes the prop declaration misleading and unused.

Consider one of these approaches:

  1. Remove the userId prop from the interface since it's not used
  2. Use the prop as a fallback: const userId = useAppSelector(s=>s.user.userId) || props.userId || ""
  3. Rename the Redux-sourced value if both are needed
🔎 Option 1: Remove unused prop

Remove userId from the props interface at line 21:

 interface GoogleSheetFormClientProps {
   initialData: {
     credentials: Array<{ id: string }>;
     authUrl?: string;
     hasCredentials: boolean;
   };
-  userId: string;
   nodeId: string;
 }

Also applies to: 38-39

🤖 Prompt for AI Agents
In apps/web/app/components/nodes/GoogleSheetFormClient.tsx around lines 13, 21
and 38-39, the component declares a userId prop but then immediately overwrites
it by reading the same value from Redux, making the prop unused and confusing;
either remove userId from the props interface and from any call sites, or keep
the prop as a fallback by assigning: const userId = useAppSelector(s =>
s.user.userId) || props.userId || "" (or alternatively rename the Redux-sourced
variable if both values must be preserved); apply one of these fixes
consistently and update typings/usage accordingly.

Comment on lines 159 to 164
{authUrl ? (
// <SelectItem value="create-new">
<a href={authUrl} target="_blank" rel="noopener noreferrer" className="text-blue-500">
<div onClick={()=>{openAuthWindow(authUrl)}} className="text-blue-500 cursor-default">
+ Create new credential
</a>
</div>
// </SelectItem>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Accessibility and UX issues with credential creation trigger.

Replacing the SelectItem with a <div> breaks several interaction patterns:

  1. Accessibility: No keyboard navigation or focus support
  2. Cursor styling: cursor-default contradicts clickable behavior (should be cursor-pointer)
  3. Select integration: The div is rendered inside SelectContent but doesn't follow Select component patterns
🔎 Recommended fix for accessible clickable option
-            {authUrl ? (
-              // <SelectItem value="create-new">
-                <div onClick={()=>{openAuthWindow(authUrl)}} className="text-blue-500 cursor-default">
-                  + Create new credential
-                </div>
-              // </SelectItem>
-            ) : (
+            {authUrl && (
+              <SelectItem 
+                value="create-new" 
+                onSelect={(e) => {
+                  e.preventDefault();
+                  openAuthWindow(authUrl);
+                }}
+                className="text-blue-500 cursor-pointer"
+              >
+                + Create new credential
+              </SelectItem>
+            )}
+            {!authUrl && (
               <>
                 {
                 response?.map((cred: any) => (

Alternatively, use a button outside the Select component for better semantics.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/app/components/nodes/GoogleSheetFormClient.tsx around lines 159 to
164, the replacement of SelectItem with a non-interactive <div> breaks
accessibility and Select integration; restore an accessible clickable option by
either: 1) replacing the <div> with the original SelectItem (or a component
compatible with your Select library) and wire its onSelect/onClick to
openAuthWindow(authUrl), ensure it is focusable and keyboard-activatable, and
set cursor to pointer; or 2) move the "Create new credential" control outside
the SelectContent and render it as a semantic <button> that calls
openAuthWindow(authUrl) (focusable, keyboard operable, and styled with
cursor-pointer). Ensure the chosen approach keeps Select semantics, keyboard
navigation, and proper styling.

Comment on lines +11 to +29
function SessionSync(){
const { status, data } = useSession();
const dispatch = useAppDispatch();

React.useEffect(()=>{
if(status === 'authenticated'){
dispatch(userAction.setUserStatus('Authenticated'));
const id = (data?.user as any)?.id ?? null;
console.log("user from Session Sync ", id)
dispatch(userAction.setUserId(id))
}
if(status === 'unauthenticated'){
dispatch(userAction.setUserId(null))
dispatch(userAction.setUserStatus('Unauthenticated'))
}
}, [status, data, dispatch])

return null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

SessionSync implementation has type safety and state handling gaps.

Two concerns:

  1. Type assertion risk (line 18): (data?.user as any)?.id bypasses type safety since by default, the id property does not exist on session. Proper typing requires custom NextAuth configuration via callbacks.

  2. Missing loading state (line 16-25): The useEffect only handles 'authenticated' and 'unauthenticated' states but ignores 'loading'. Best practice is to check all three status states before syncing session data to Redux.

Recommended fix:

  • Add a check for status === 'loading' to prevent undefined behavior during initial session fetch
  • Configure NextAuth callbacks to properly expose the user id in the session object, then remove the type assertion
🤖 Prompt for AI Agents
In apps/web/app/components/providers.tsx around lines 11–29, SessionSync is
using a risky type assertion for the user id and ignores the 'loading' status;
update the code to early-return or skip dispatching when status === 'loading',
and stop using (data?.user as any)?.id by properly typing the session: add a
NextAuth session callback that injects user.id into the session, augment
NextAuth's Session/User types (or provide useSession generic) so session.user.id
is typed, then read session.user.id safely (nullable) and dispatch only when the
typed id is present or explicitly handle null.

Comment thread apps/web/package.json
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.15",
"@reduxjs/toolkit": "^2.11.2",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for usage of next-redux-wrapper
rg -n "next-redux-wrapper" --type=ts --type=tsx --type=js --type=jsx -i

Repository: Dev-Pross/BuildFlow

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Find and read the providers.tsx file
fd "providers.tsx" -t f

Repository: Dev-Pross/BuildFlow

Length of output: 100


🏁 Script executed:

#!/bin/bash
# Search for next-redux-wrapper without specific type filter
rg "next-redux-wrapper" -i

Repository: Dev-Pross/BuildFlow

Length of output: 120


🏁 Script executed:

#!/bin/bash
# Read the providers.tsx file
cat -n apps/web/app/components/providers.tsx

Repository: Dev-Pross/BuildFlow

Length of output: 1770


🏁 Script executed:

#!/bin/bash
# Check the package.json for Redux-related dependencies
cat apps/web/package.json

Repository: Dev-Pross/BuildFlow

Length of output: 1525


Remove unused next-redux-wrapper dependency.

The next-redux-wrapper package is not imported or used anywhere in the codebase. The Redux setup in apps/web/app/components/providers.tsx correctly uses the App Router pattern with ReduxProvider directly from react-redux, which does not require a wrapper library. Remove next-redux-wrapper from apps/web/package.json line 32.

🤖 Prompt for AI Agents
In apps/web/package.json around line 32 (dependency list), remove the unused
"next-redux-wrapper" entry from the dependencies array; update the package.json
so the dependency key/value pair is deleted and then run npm/yarn install (or
update lockfile) to keep lockfiles in sync.

@Vamsi-o Vamsi-o left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Accessibility and UX issues with credential creation trigger.

Replacing the SelectItem with a

breaks several interaction patterns:

Accessibility: No keyboard navigation or focus support
Cursor styling: cursor-default contradicts clickable behavior (should be cursor-pointer)
Select integration: The div is rendered inside SelectContent but doesn't follow Select component patterns

@Vamsi-o Vamsi-o merged commit c01cc49 into main Dec 20, 2025
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Dec 25, 2025
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.

3 participants