-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Implement Redux for user authentication and integrate with Goog… #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'; | ||
|
|
||
| interface GoogleSheetFormClientProps { | ||
| initialData: { | ||
|
|
@@ -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) | ||
|
|
@@ -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); | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Accessibility and UX issues with credential creation trigger. Replacing the
🔎 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.
🤖 Prompt for AI Agents |
||
| ) : ( | ||
| <> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SessionSync implementation has type safety and state handling gaps. Two concerns:
Recommended fix:
🤖 Prompt for AI Agents |
||
|
|
||
| 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> | ||
| ) | ||
| } | ||
| 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; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ | |
| }, | ||
| "dependencies": { | ||
| "@radix-ui/react-dialog": "^1.1.15", | ||
| "@reduxjs/toolkit": "^2.11.2", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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 -iRepository: Dev-Pross/BuildFlow Length of output: 90 🏁 Script executed: #!/bin/bash
# Find and read the providers.tsx file
fd "providers.tsx" -t fRepository: Dev-Pross/BuildFlow Length of output: 100 🏁 Script executed: #!/bin/bash
# Search for next-redux-wrapper without specific type filter
rg "next-redux-wrapper" -iRepository: Dev-Pross/BuildFlow Length of output: 120 🏁 Script executed: #!/bin/bash
# Read the providers.tsx file
cat -n apps/web/app/components/providers.tsxRepository: Dev-Pross/BuildFlow Length of output: 1770 🏁 Script executed: #!/bin/bash
# Check the package.json for Redux-related dependencies
cat apps/web/package.jsonRepository: Dev-Pross/BuildFlow Length of output: 1525 Remove unused The 🤖 Prompt for AI Agents |
||
| "@repo/common": "workspace:*", | ||
| "@repo/db": "workspace:*", | ||
| "@repo/nodes": "workspace:*", | ||
|
|
@@ -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", | ||
|
|
||
| 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> |
| 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 | ||
| }) |
| 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 |
| 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused
userIdprop creates confusion.The component receives
userIdas 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:
userIdprop from the interface since it's not usedconst userId = useAppSelector(s=>s.user.userId) || props.userId || ""🔎 Option 1: Remove unused prop
Remove
userIdfrom 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