Merge branch 'develop' into ui-fix

This commit is contained in:
Rohit
2025-01-08 12:50:46 +05:30
committed by GitHub
67 changed files with 7978 additions and 1189 deletions

View File

@@ -1,4 +1,4 @@
import { useReducer, createContext, useEffect } from 'react';
import { useReducer, createContext, useEffect, useCallback } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import { apiUrl } from "../apiConfig";
@@ -14,12 +14,16 @@ interface ActionType {
type InitialStateType = {
user: any;
lastActivityTime?: number;
};
const initialState = {
user: null,
lastActivityTime: Date.now(),
};
const AUTO_LOGOUT_TIME = 4 * 60 * 60 * 1000; // 4 hours in milliseconds
const AuthContext = createContext<{
state: InitialStateType;
dispatch: React.Dispatch<ActionType>;
@@ -34,11 +38,13 @@ const reducer = (state: InitialStateType, action: ActionType) => {
return {
...state,
user: action.payload,
lastActivityTime: Date.now(),
};
case 'LOGOUT':
return {
...state,
user: null,
lastActivityTime: undefined,
};
default:
return state;
@@ -50,6 +56,39 @@ const AuthProvider = ({ children }: AuthProviderProps) => {
const navigate = useNavigate();
axios.defaults.withCredentials = true;
const handleLogout = useCallback(async () => {
try {
await axios.get(`${apiUrl}/auth/logout`);
dispatch({ type: 'LOGOUT' });
window.localStorage.removeItem('user');
navigate('/login');
} catch (err) {
console.error('Logout error:', err);
}
}, [navigate]);
const checkAutoLogout = useCallback(() => {
if (state.user && state.lastActivityTime) {
const currentTime = Date.now();
const timeSinceLastActivity = currentTime - state.lastActivityTime;
if (timeSinceLastActivity >= AUTO_LOGOUT_TIME) {
handleLogout();
}
}
}, [state.user, state.lastActivityTime, handleLogout]);
// Update last activity time on user interactions
const updateActivityTime = useCallback(() => {
if (state.user) {
dispatch({
type: 'LOGIN',
payload: state.user // Reuse existing user data
});
}
}, [state.user]);
// Initialize user from localStorage
useEffect(() => {
const storedUser = window.localStorage.getItem('user');
if (storedUser) {
@@ -57,21 +96,54 @@ const AuthProvider = ({ children }: AuthProviderProps) => {
}
}, []);
// Set up activity listeners
useEffect(() => {
if (state.user) {
// List of events to track for user activity
const events = ['mousedown', 'keydown', 'scroll', 'touchstart'];
// Throttled event handler
let timeoutId: NodeJS.Timeout;
const handleActivity = () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(updateActivityTime, 1000);
};
// Add event listeners
events.forEach(event => {
window.addEventListener(event, handleActivity);
});
// Set up periodic check for auto logout
const checkInterval = setInterval(checkAutoLogout, 60000); // Check every minute
// Cleanup
return () => {
events.forEach(event => {
window.removeEventListener(event, handleActivity);
});
clearInterval(checkInterval);
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}
}, [state.user, updateActivityTime, checkAutoLogout]);
axios.interceptors.response.use(
function (response) {
return response;
},
function (error) {
const res = error.response;
if (res.status === 401 && res.config && !res.config.__isRetryRequest) {
return new Promise((resolve, reject) => {
axios
.get(`${apiUrl}/auth/logout`)
if (res?.status === 401 && res.config && !res.config.__isRetryRequest) {
return new Promise((_, reject) => {
handleLogout()
.then(() => {
console.log('/401 error > logout');
dispatch({ type: 'LOGOUT' });
window.localStorage.removeItem('user');
navigate('/login');
reject(error);
})
.catch((err) => {
console.error('AXIOS INTERCEPTORS ERROR:', err);

View File

@@ -14,8 +14,8 @@ interface ActionContextProps {
paginationType: PaginationType;
limitType: LimitType;
customLimit: string;
captureStage: CaptureStage; // New captureStage property
setCaptureStage: (stage: CaptureStage) => void; // Setter for captureStage
captureStage: CaptureStage;
setCaptureStage: (stage: CaptureStage) => void;
startPaginationMode: () => void;
startGetText: () => void;
stopGetText: () => void;
@@ -53,6 +53,7 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => {
const startPaginationMode = () => {
setPaginationMode(true);
setCaptureStage('pagination');
socket?.emit('setGetList', { getList: false });
};
const stopPaginationMode = () => setPaginationMode(false);
@@ -75,7 +76,6 @@ export const ActionProvider = ({ children }: { children: ReactNode }) => {
const stopGetList = () => {
setGetList(false);
socket?.emit('setGetList', { getList: false });
setPaginationType('');
setLimitType('');
setCustomLimit('');

View File

@@ -32,6 +32,7 @@ export interface SelectorObject {
selector: string;
tag?: string;
attribute?: string;
shadow?: boolean;
[key: string]: any;
}
@@ -62,26 +63,35 @@ export const BrowserStepsProvider: React.FC<{ children: React.ReactNode }> = ({
const addListStep = (listSelector: string, newFields: { [key: string]: TextStep }, listId: number, pagination?: { type: string; selector: string }, limit?: number) => {
setBrowserSteps(prevSteps => {
const existingListStepIndex = prevSteps.findIndex(step => step.type === 'list' && step.id === listId);
if (existingListStepIndex !== -1) {
const updatedSteps = [...prevSteps];
const existingListStep = updatedSteps[existingListStepIndex] as ListStep;
const filteredNewFields = Object.entries(newFields).reduce((acc, [key, value]) => {
// Preserve existing labels for fields
const mergedFields = Object.entries(newFields).reduce((acc, [key, field]) => {
if (!discardedFields.has(`${listId}-${key}`)) {
acc[key] = value;
// If field exists, preserve its label
if (existingListStep.fields[key]) {
acc[key] = {
...field,
label: existingListStep.fields[key].label
};
} else {
acc[key] = field;
}
}
return acc;
}, {} as { [key: string]: TextStep });
updatedSteps[existingListStepIndex] = {
...existingListStep,
fields: { ...existingListStep.fields, ...filteredNewFields },
pagination: pagination,
limit: limit,
fields: mergedFields,
pagination: pagination || existingListStep.pagination,
limit: limit
};
return updatedSteps;
} else {
// Create a new ListStep
return [
...prevSteps,
{ id: listId, type: 'list', listSelector, fields: newFields, pagination, limit }

View File

@@ -32,6 +32,8 @@ interface GlobalInfo {
hasScreenshotAction: boolean;
hasScrapeSchemaAction: boolean;
}) => void;
shouldResetInterpretationLog: boolean;
resetInterpretationLog: () => void;
};
class GlobalInfoStore implements Partial<GlobalInfo> {
@@ -53,6 +55,7 @@ class GlobalInfoStore implements Partial<GlobalInfo> {
hasScreenshotAction: false,
hasScrapeSchemaAction: false,
};
shouldResetInterpretationLog = false;
};
const globalInfoStore = new GlobalInfoStore();
@@ -71,6 +74,7 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
const [recordingName, setRecordingName] = useState<string>(globalInfoStore.recordingName);
const [recordingUrl, setRecordingUrl] = useState<string>(globalInfoStore.recordingUrl);
const [currentWorkflowActionsState, setCurrentWorkflowActionsState] = useState(globalInfoStore.currentWorkflowActionsState);
const [shouldResetInterpretationLog, setShouldResetInterpretationLog] = useState<boolean>(globalInfoStore.shouldResetInterpretationLog);
const notify = (severity: 'error' | 'warning' | 'info' | 'success', message: string) => {
setNotification({ severity, message, isOpen: true });
@@ -87,6 +91,14 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
}
}
const resetInterpretationLog = () => {
setShouldResetInterpretationLog(true);
// Reset the flag after a short delay to allow components to respond
setTimeout(() => {
setShouldResetInterpretationLog(false);
}, 100);
}
return (
<globalInfoContext.Provider
value={{
@@ -111,6 +123,8 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
setRecordingUrl,
currentWorkflowActionsState,
setCurrentWorkflowActionsState,
shouldResetInterpretationLog,
resetInterpretationLog,
}}
>
{children}