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
27 changes: 23 additions & 4 deletions apps/web/app/components/nodes/GoogleSheetFormClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { toast } from 'sonner';
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.


interface GoogleSheetFormClientProps {
initialData: {
Expand All @@ -34,7 +35,8 @@ export function GoogleSheetFormClient(type:{type: string}) {
const [result, setResult] = useState<any>(null);
const [credId, setCredId] = useState<string>();
// const [authUrl, setAuthUrl] = useState<string>()
const userId = ""// get it from redux
const userId = useAppSelector(s=>s.user.userId) || ""
console.log(userId, 'id from client')
const credType = type.type

const {cred: response, authUrl} = useCredentials(credType)
Expand All @@ -43,7 +45,24 @@ export function GoogleSheetFormClient(type:{type: string}) {
console.log(response," response from client after hook")
console.log(authUrl," authurl")


const openAuthWindow = (url: string) => {
if (!url) return;
const width = 520;
const height = 650;
const screenLeft = (window as any).screenLeft ?? window.screenX ?? 0;
const screenTop = (window as any).screenTop ?? window.screenY ?? 0;
const screenWidth = window.innerWidth ?? document.documentElement.clientWidth ?? screen.width;
const screenHeight = window.innerHeight ?? document.documentElement.clientHeight ?? screen.height;
const left = Math.round(screenLeft + (screenWidth - width) / 2);
const top = Math.round(screenTop + (screenHeight - height) / 2);
const features = `popup=yes,toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=no,width=${width},height=${height},top=${top},left=${left}`;
const win = window.open(url, 'google-oauth', features);
if (!win) {
window.location.href = url;
return;
}
try { win.focus?.(); } catch {}
};
// Fetch documents when credential is selected
const handleCredentialChange = async (credentialId: string) => {
setSelectedCredential(credentialId);
Expand Down Expand Up @@ -139,9 +158,9 @@ export function GoogleSheetFormClient(type:{type: string}) {
<SelectContent>
{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>
Comment on lines 159 to 164

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.

) : (
<>
Expand Down
49 changes: 39 additions & 10 deletions apps/web/app/components/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,48 @@

import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { Provider as ReduxProvider } from "react-redux"
import { store } from "@/store"
import { SessionProvider, useSession } from "next-auth/react"
import { useAppDispatch } from "../hooks/redux"
import { userAction } from "@/store/slices/userSlice"

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
}
Comment on lines +11 to +29

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.


export function Providers({ children }: { children: React.ReactNode }) {
return (


<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
enableColorScheme
>
{children}
</NextThemesProvider>
<ReduxProvider store={store}>
<SessionProvider>
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
enableColorScheme
>
<SessionSync />
{children}
</NextThemesProvider>
</SessionProvider>
</ReduxProvider>
)
}
7 changes: 7 additions & 0 deletions apps/web/app/hooks/redux.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client'

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from '@/store/types'

export const useAppDispatch: ()=> AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
3 changes: 3 additions & 0 deletions apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { ReactNode } from "react";
import "@workspace/ui/globals.css"
import { Toaster } from "sonner";
import { Providers } from "./components/providers";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body suppressHydrationWarning>
<Providers>
<Toaster position="top-right" />
{children}
</Providers>
</body>
</html>
);
Expand Down
3 changes: 3 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"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.

"@repo/common": "workspace:*",
"@repo/db": "workspace:*",
"@repo/nodes": "workspace:*",
Expand All @@ -28,10 +29,12 @@
"lucide-react": "^0.475.0",
"next": "^15.4.5",
"next-auth": "^4.24.13",
"next-redux-wrapper": "^8.1.0",
"next-themes": "^0.4.6",
"react": "^19.1.1",
"react-dialog": "link:@types/@radix-ui/react-dialog",
"react-dom": "^19.1.1",
"react-redux": "^9.2.0",
"server-only": "^0.0.1",
"sonner": "^2.0.7",
"trpc": "^0.11.3",
Expand Down
11 changes: 11 additions & 0 deletions apps/web/store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { configureStore } from "@reduxjs/toolkit";
import { rootReducer } from "./root-reducer";

export const makeStore = ()=> configureStore({
reducer: rootReducer,
devTools: process.env.NODE_ENV !== 'production'
})

export const store = makeStore();

export type AppStore = ReturnType<typeof makeStore>
6 changes: 6 additions & 0 deletions apps/web/store/root-reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { combineReducers } from "@reduxjs/toolkit";
import { userReducer } from "./slices/userSlice";

export const rootReducer = combineReducers({
user: userReducer
})
31 changes: 31 additions & 0 deletions apps/web/store/slices/userSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {createSlice, PayloadAction } from '@reduxjs/toolkit'

export type AuthStatus = 'Authenticated' | 'Unauthenticated'
export interface UserSlice {
userId: string | null;
status: AuthStatus
}

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

const userSlice = createSlice({
name: 'user',
initialState,
reducers:{
setUserId(state, action: PayloadAction<string |null>){
state.userId = action.payload;
},
setUserStatus(state, action: PayloadAction<AuthStatus>){
state.status = action.payload;
},
clearUser(){
return initialState;
}
}
})

export const userReducer = userSlice.reducer
export const userAction = userSlice.actions
5 changes: 5 additions & 0 deletions apps/web/store/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { rootReducer } from "./root-reducer";
import { store } from "./index";

export type RootState= ReturnType<typeof rootReducer>
export type AppDispatch = typeof store.dispatch;