expand sub tasks

This commit is contained in:
chamiakJ
2025-07-03 01:31:05 +05:30
parent 3bef18901a
commit ecd4d29a38
435 changed files with 13150 additions and 11087 deletions

View File

@@ -15,7 +15,7 @@ class ReduxPerformanceMonitor {
logMetric(metric: PerformanceMetrics) {
this.metrics.push(metric);
// Keep only recent metrics
if (this.metrics.length > this.maxMetrics) {
this.metrics = this.metrics.slice(-this.maxMetrics);
@@ -49,14 +49,14 @@ class ReduxPerformanceMonitor {
export const performanceMonitor = new ReduxPerformanceMonitor();
// Redux middleware for performance monitoring
export const performanceMiddleware: Middleware = (store) => (next) => (action: any) => {
export const performanceMiddleware: Middleware = store => next => (action: any) => {
const start = performance.now();
const result = next(action);
const end = performance.now();
const duration = end - start;
// Calculate approximate state size (in development only)
let stateSize = 0;
if (process.env.NODE_ENV === 'development') {
@@ -101,7 +101,7 @@ export function analyzeReduxPerformance() {
// Count action frequencies
metrics.forEach(m => {
analysis.mostFrequentActions[m.actionType] =
analysis.mostFrequentActions[m.actionType] =
(analysis.mostFrequentActions[m.actionType] || 0) + 1;
});
@@ -109,14 +109,15 @@ export function analyzeReduxPerformance() {
if (analysis.slowActions > analysis.totalActions * 0.1) {
analysis.recommendations.push('Consider optimizing selectors with createSelector');
}
if (analysis.largestStateSize > 1000000) { // 1MB
if (analysis.largestStateSize > 1000000) {
// 1MB
analysis.recommendations.push('State size is large - consider normalizing data');
}
if (analysis.averageActionTime > 20) {
analysis.recommendations.push('Average action time is high - check for expensive reducers');
}
return analysis;
}
}

View File

@@ -62,10 +62,12 @@ export const AdminGuard = memo(({ children }: GuardProps) => {
const guardResult = useMemo(() => {
try {
// Defensive checks to ensure authService and its methods exist
if (!authService ||
typeof authService.isAuthenticated !== 'function' ||
typeof authService.isOwnerOrAdmin !== 'function' ||
typeof authService.getCurrentSession !== 'function') {
if (
!authService ||
typeof authService.isAuthenticated !== 'function' ||
typeof authService.isOwnerOrAdmin !== 'function' ||
typeof authService.getCurrentSession !== 'function'
) {
return null; // Don't redirect if auth service is not ready
}
@@ -75,7 +77,7 @@ export const AdminGuard = memo(({ children }: GuardProps) => {
const currentSession = authService.getCurrentSession();
const isFreePlan = currentSession?.subscription_type === ISUBSCRIPTION_TYPE.FREE;
if (!authService.isOwnerOrAdmin() || isFreePlan) {
return { redirect: '/worklenz/unauthorized' };
}
@@ -103,9 +105,11 @@ export const LicenseExpiryGuard = memo(({ children }: GuardProps) => {
const shouldRedirect = useMemo(() => {
try {
// Defensive checks to ensure authService and its methods exist
if (!authService ||
typeof authService.isAuthenticated !== 'function' ||
typeof authService.getCurrentSession !== 'function') {
if (
!authService ||
typeof authService.isAuthenticated !== 'function' ||
typeof authService.getCurrentSession !== 'function'
) {
return false; // Don't redirect if auth service is not ready
}
@@ -120,37 +124,40 @@ export const LicenseExpiryGuard = memo(({ children }: GuardProps) => {
const currentSession = authService.getCurrentSession();
// Check if trial is expired more than 7 days or if is_expired flag is set
const isLicenseExpiredMoreThan7Days = () => {
const isLicenseExpiredMoreThan7Days = () => {
// Quick bail if no session data is available
if (!currentSession) return false;
// Check is_expired flag first
if (currentSession.is_expired) {
if (currentSession.is_expired) {
// If no trial_expire_date exists but is_expired is true, defer to backend check
if (!currentSession.trial_expire_date) return true;
// If there is a trial_expire_date, check if it's more than 7 days past
const today = new Date();
const expiryDate = new Date(currentSession.trial_expire_date);
const diffTime = today.getTime() - expiryDate.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
// Redirect if more than 7 days past expiration
return diffDays > 7;
}
// If not marked as expired but has trial_expire_date, do a date check
if (currentSession.subscription_type === ISUBSCRIPTION_TYPE.TRIAL && currentSession.trial_expire_date) {
if (
currentSession.subscription_type === ISUBSCRIPTION_TYPE.TRIAL &&
currentSession.trial_expire_date
) {
const today = new Date();
const expiryDate = new Date(currentSession.trial_expire_date);
const diffTime = today.getTime() - expiryDate.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
// If expired more than 7 days, redirect
return diffDays > 7;
}
// No expiration data found
return false;
};
@@ -227,30 +234,34 @@ const wrapRoutes = (
// Optimized static license expired component
const StaticLicenseExpired = memo(() => {
return (
<div style={{
marginTop: 65,
minHeight: '90vh',
backgroundColor: '#f5f5f5',
padding: '20px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}>
<div style={{
background: 'white',
padding: '30px',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
textAlign: 'center',
maxWidth: '600px'
}}>
<div
style={{
marginTop: 65,
minHeight: '90vh',
backgroundColor: '#f5f5f5',
padding: '20px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div
style={{
background: 'white',
padding: '30px',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
textAlign: 'center',
maxWidth: '600px',
}}
>
<h1 style={{ fontSize: '24px', color: '#faad14', marginBottom: '16px' }}>
Your Worklenz trial has expired!
</h1>
<p style={{ fontSize: '16px', color: '#555', marginBottom: '24px' }}>
Please upgrade now to continue using Worklenz.
</p>
<button
<button
style={{
backgroundColor: '#1890ff',
color: 'white',
@@ -258,9 +269,9 @@ const StaticLicenseExpired = memo(() => {
padding: '8px 16px',
borderRadius: '4px',
fontSize: '16px',
cursor: 'pointer'
cursor: 'pointer',
}}
onClick={() => window.location.href = '/worklenz/admin-center/billing'}
onClick={() => (window.location.href = '/worklenz/admin-center/billing')}
>
Upgrade now
</button>
@@ -272,11 +283,7 @@ const StaticLicenseExpired = memo(() => {
StaticLicenseExpired.displayName = 'StaticLicenseExpired';
// Create route arrays (moved outside of useMemo to avoid hook violations)
const publicRoutes = [
...rootRoutes,
...authRoutes,
notFoundRoute
];
const publicRoutes = [...rootRoutes, ...authRoutes, notFoundRoute];
const protectedMainRoutes = wrapRoutes(mainRoutes, AuthGuard);
const adminRoutes = wrapRoutes(reportingRoutes, AdminGuard);
@@ -305,37 +312,35 @@ const withLicenseExpiryCheck = (routes: RouteObject[]): RouteObject[] => {
const licenseCheckedMainRoutes = withLicenseExpiryCheck(protectedMainRoutes);
// Create optimized router with future flags for better performance
const router = createBrowserRouter([
const router = createBrowserRouter(
[
{
element: (
<ErrorBoundary>
<AuthenticatedLayout />
</ErrorBoundary>
),
errorElement: (
<ErrorBoundary>
<Suspense fallback={<SuspenseFallback />}>
<NotFoundPage />
</Suspense>
</ErrorBoundary>
),
children: [...licenseCheckedMainRoutes, ...adminRoutes, ...setupRoutes, licenseExpiredRoute],
},
...publicRoutes,
],
{
element: (
<ErrorBoundary>
<AuthenticatedLayout />
</ErrorBoundary>
),
errorElement: (
<ErrorBoundary>
<Suspense fallback={<SuspenseFallback />}>
<NotFoundPage />
</Suspense>
</ErrorBoundary>
),
children: [
...licenseCheckedMainRoutes,
...adminRoutes,
...setupRoutes,
licenseExpiredRoute,
],
},
...publicRoutes,
], {
// Enable React Router future features for better performance
future: {
v7_relativeSplatPath: true,
v7_fetcherPersist: true,
v7_normalizeFormMethod: true,
v7_partialHydration: true,
v7_skipActionErrorRevalidation: true
// Enable React Router future features for better performance
future: {
v7_relativeSplatPath: true,
v7_fetcherPersist: true,
v7_normalizeFormMethod: true,
v7_partialHydration: true,
v7_skipActionErrorRevalidation: true,
},
}
});
);
export default router;

View File

@@ -11,7 +11,9 @@ import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallba
const HomePage = lazy(() => import('@/pages/home/home-page'));
const ProjectList = lazy(() => import('@/pages/projects/project-list'));
const Schedule = lazy(() => import('@/pages/schedule/schedule'));
const ProjectTemplateEditView = lazy(() => import('@/pages/settings/project-templates/projectTemplateEditView/ProjectTemplateEditView'));
const ProjectTemplateEditView = lazy(
() => import('@/pages/settings/project-templates/projectTemplateEditView/ProjectTemplateEditView')
);
const LicenseExpired = lazy(() => import('@/pages/license-expired/license-expired'));
const ProjectView = lazy(() => import('@/pages/projects/projectView/project-view'));
const Unauthorized = lazy(() => import('@/pages/unauthorized/unauthorized'));
@@ -23,9 +25,11 @@ const AdminGuard = ({ children }: { children: React.ReactNode }) => {
try {
// Defensive checks to ensure authService and its methods exist
if (!authService ||
typeof authService.isAuthenticated !== 'function' ||
typeof authService.isOwnerOrAdmin !== 'function') {
if (
!authService ||
typeof authService.isAuthenticated !== 'function' ||
typeof authService.isOwnerOrAdmin !== 'function'
) {
// If auth service is not ready, render children (don't block)
return <>{children}</>;
}
@@ -52,21 +56,21 @@ const mainRoutes: RouteObject[] = [
element: <MainLayout />,
children: [
{ index: true, element: <Navigate to="home" replace /> },
{
path: 'home',
{
path: 'home',
element: (
<Suspense fallback={<SuspenseFallback />}>
<HomePage />
</Suspense>
)
),
},
{
path: 'projects',
{
path: 'projects',
element: (
<Suspense fallback={<SuspenseFallback />}>
<ProjectList />
</Suspense>
)
),
},
{
path: 'schedule',
@@ -76,15 +80,15 @@ const mainRoutes: RouteObject[] = [
<Schedule />
</AdminGuard>
</Suspense>
)
),
},
{
path: `projects/:projectId`,
{
path: `projects/:projectId`,
element: (
<Suspense fallback={<SuspenseFallback />}>
<ProjectView />
</Suspense>
)
),
},
{
path: `settings/project-templates/edit/:templateId/:templateName`,
@@ -94,13 +98,13 @@ const mainRoutes: RouteObject[] = [
</Suspense>
),
},
{
path: 'unauthorized',
{
path: 'unauthorized',
element: (
<Suspense fallback={<SuspenseFallback />}>
<Unauthorized />
</Suspense>
)
),
},
...settingsRoutes,
...adminCenterRoutes,
@@ -113,15 +117,15 @@ export const licenseExpiredRoute: RouteObject = {
path: '/worklenz',
element: <MainLayout />,
children: [
{
path: 'license-expired',
{
path: 'license-expired',
element: (
<Suspense fallback={<SuspenseFallback />}>
<LicenseExpired />
</Suspense>
)
}
]
),
},
],
};
export default mainRoutes;

View File

@@ -4,7 +4,13 @@ import SettingsLayout from '@/layouts/SettingsLayout';
import { settingsItems } from '@/lib/settings/settings-constants';
import { useAuthService } from '@/hooks/useAuth';
const SettingsGuard = ({ children, adminRequired }: { children: React.ReactNode; adminRequired: boolean }) => {
const SettingsGuard = ({
children,
adminRequired,
}: {
children: React.ReactNode;
adminRequired: boolean;
}) => {
const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin();
if (adminRequired && !isOwnerOrAdmin) {
@@ -20,11 +26,7 @@ const settingsRoutes: RouteObject[] = [
element: <SettingsLayout />,
children: settingsItems.map(item => ({
path: item.endpoint,
element: (
<SettingsGuard adminRequired={!!item.adminOnly}>
{item.element}
</SettingsGuard>
),
element: <SettingsGuard adminRequired={!!item.adminOnly}>{item.element}</SettingsGuard>,
})),
},
];

View File

@@ -7,10 +7,7 @@ import { RootState } from './store';
// Auth selectors
export const selectAuth = (state: RootState) => state.auth;
export const selectUser = (state: RootState) => state.userReducer;
export const selectIsAuthenticated = createSelector(
[selectAuth],
(auth) => !!auth.user
);
export const selectIsAuthenticated = createSelector([selectAuth], auth => !!auth.user);
// Project selectors
export const selectProjects = (state: RootState) => state.projectsReducer;
@@ -69,13 +66,10 @@ export const selectGroupByFilter = (state: RootState) => state.groupByFilterDrop
// Memoized computed selectors for common use cases
export const selectHasActiveProject = createSelector(
[selectCurrentProject],
(project) => !!project && Object.keys(project).length > 0
project => !!project && Object.keys(project).length > 0
);
export const selectIsLoading = createSelector(
[selectTasks, selectProjects],
(tasks, projects) => {
// Check if any major feature is loading
return (tasks as any)?.loading || (projects as any)?.loading;
}
);
export const selectIsLoading = createSelector([selectTasks, selectProjects], (tasks, projects) => {
// Check if any major feature is loading
return (tasks as any)?.loading || (projects as any)?.loading;
});

View File

@@ -122,7 +122,7 @@ export const store = configureStore({
taskListCustomColumnsReducer: taskListCustomColumnsReducer,
boardReducer: boardReducer,
projectDrawerReducer: projectDrawerReducer,
projectViewReducer: projectViewReducer,
// Project Lookups