From 69c4e23c814471f6596f80e1107ba34759a86754 Mon Sep 17 00:00:00 2001 From: BUDUMURU SRINIVAS SAI SARAN TEJA Date: Fri, 19 Dec 2025 23:07:07 +0530 Subject: [PATCH] feat: Implement Redux for user authentication and integrate with Google Sheets API --- .../nodes/GoogleSheetFormClient.tsx | 27 ++++++++-- apps/web/app/components/providers.tsx | 49 +++++++++++++++---- apps/web/app/hooks/redux.ts | 7 +++ apps/web/app/layout.tsx | 3 ++ apps/web/package.json | 3 ++ apps/web/store/index.ts | 11 +++++ apps/web/store/root-reducer.ts | 6 +++ apps/web/store/slices/userSlice.ts | 31 ++++++++++++ apps/web/store/types.ts | 5 ++ 9 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 apps/web/app/hooks/redux.ts create mode 100644 apps/web/store/index.ts create mode 100644 apps/web/store/root-reducer.ts create mode 100644 apps/web/store/slices/userSlice.ts create mode 100644 apps/web/store/types.ts diff --git a/apps/web/app/components/nodes/GoogleSheetFormClient.tsx b/apps/web/app/components/nodes/GoogleSheetFormClient.tsx index 9c309b1..79e4b9a 100644 --- a/apps/web/app/components/nodes/GoogleSheetFormClient.tsx +++ b/apps/web/app/components/nodes/GoogleSheetFormClient.tsx @@ -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(null); const [credId, setCredId] = useState(); // const [authUrl, setAuthUrl] = useState() - 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}) { {authUrl ? ( // - + // ) : ( <> diff --git a/apps/web/app/components/providers.tsx b/apps/web/app/components/providers.tsx index 70637cb..2a8b25c 100644 --- a/apps/web/app/components/providers.tsx +++ b/apps/web/app/components/providers.tsx @@ -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 +} export function Providers({ children }: { children: React.ReactNode }) { return ( - - - {children} - + + + + + {children} + + + ) } diff --git a/apps/web/app/hooks/redux.ts b/apps/web/app/hooks/redux.ts new file mode 100644 index 0000000..b86ae81 --- /dev/null +++ b/apps/web/app/hooks/redux.ts @@ -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 = useSelector; \ No newline at end of file diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index b3dd532..6314265 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -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 ( + {children} + ); diff --git a/apps/web/package.json b/apps/web/package.json index a4e2568..910a271 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@radix-ui/react-dialog": "^1.1.15", + "@reduxjs/toolkit": "^2.11.2", "@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", diff --git a/apps/web/store/index.ts b/apps/web/store/index.ts new file mode 100644 index 0000000..664a43a --- /dev/null +++ b/apps/web/store/index.ts @@ -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 \ No newline at end of file diff --git a/apps/web/store/root-reducer.ts b/apps/web/store/root-reducer.ts new file mode 100644 index 0000000..015536a --- /dev/null +++ b/apps/web/store/root-reducer.ts @@ -0,0 +1,6 @@ +import { combineReducers } from "@reduxjs/toolkit"; +import { userReducer } from "./slices/userSlice"; + +export const rootReducer = combineReducers({ + user: userReducer +}) \ No newline at end of file diff --git a/apps/web/store/slices/userSlice.ts b/apps/web/store/slices/userSlice.ts new file mode 100644 index 0000000..186167f --- /dev/null +++ b/apps/web/store/slices/userSlice.ts @@ -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){ + state.userId = action.payload; + }, + setUserStatus(state, action: PayloadAction){ + state.status = action.payload; + }, + clearUser(){ + return initialState; + } + } +}) + +export const userReducer = userSlice.reducer +export const userAction = userSlice.actions \ No newline at end of file diff --git a/apps/web/store/types.ts b/apps/web/store/types.ts new file mode 100644 index 0000000..56e4f98 --- /dev/null +++ b/apps/web/store/types.ts @@ -0,0 +1,5 @@ +import { rootReducer } from "./root-reducer"; +import { store } from "./index"; + +export type RootState= ReturnType +export type AppDispatch = typeof store.dispatch; \ No newline at end of file