This commit is contained in:
chamikaJ
2025-04-17 18:28:54 +05:30
parent f583291d8a
commit 8825b0410a
2837 changed files with 241385 additions and 127578 deletions

View File

@@ -0,0 +1,9 @@
import { RouteObject } from 'react-router-dom';
import AccountSetup from '@/pages/account-setup/account-setup';
const accountSetupRoute: RouteObject = {
path: '/worklenz/setup',
element: <AccountSetup />,
};
export default accountSetupRoute;

View File

@@ -0,0 +1,32 @@
import { RouteObject } from 'react-router-dom';
import AdminCenterLayout from '@/layouts/admin-center-layout';
import { adminCenterItems } from '@/pages/admin-center/admin-center-constants';
import { Navigate } from 'react-router-dom';
import { useAuthService } from '@/hooks/useAuth';
const AdminCenterGuard = ({ children }: { children: React.ReactNode }) => {
const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin();
if (!isOwnerOrAdmin) {
return <Navigate to="/worklenz/unauthorized" replace />;
}
return <>{children}</>;
};
const adminCenterRoutes: RouteObject[] = [
{
path: 'admin-center',
element: (
<AdminCenterGuard>
<AdminCenterLayout />
</AdminCenterGuard>
),
children: adminCenterItems.map(item => ({
path: item.endpoint,
element: item.element,
})),
},
];
export default adminCenterRoutes;

View File

@@ -0,0 +1,51 @@
import AuthLayout from '@/layouts/AuthLayout';
import LoginPage from '@/pages/auth/login-page';
import SignupPage from '@/pages/auth/signup-page';
import ForgotPasswordPage from '@/pages/auth/forgot-password-page';
import LoggingOutPage from '@/pages/auth/logging-out';
import AuthenticatingPage from '@/pages/auth/authenticating';
import { Navigate } from 'react-router-dom';
import VerifyResetEmailPage from '@/pages/auth/verify-reset-email';
import { Suspense } from 'react';
import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
const authRoutes = [
{
path: '/auth',
element: (
<AuthLayout />
),
children: [
{
path: '',
element: <Navigate to="login" replace />,
},
{
path: 'login',
element: <LoginPage />,
},
{
path: 'signup',
element: <SignupPage />,
},
{
path: 'forgot-password',
element: <ForgotPasswordPage />,
},
{
path: 'logging-out',
element: <LoggingOutPage />,
},
{
path: 'authenticating',
element: <AuthenticatingPage />,
},
{
path: 'verify-reset-email/:user/:hash',
element: <VerifyResetEmailPage />,
},
],
},
];
export default authRoutes;

View File

@@ -0,0 +1,230 @@
import { createBrowserRouter, Navigate, RouteObject, useLocation } from 'react-router-dom';
import rootRoutes from './root-routes';
import authRoutes from './auth-routes';
import mainRoutes, { licenseExpiredRoute } from './main-routes';
import notFoundRoute from './not-found-route';
import accountSetupRoute from './account-setup-routes';
import reportingRoutes from './reporting-routes';
import { useAuthService } from '@/hooks/useAuth';
import { AuthenticatedLayout } from '@/layouts/AuthenticatedLayout';
import ErrorBoundary from '@/components/ErrorBoundary';
import NotFoundPage from '@/pages/404-page/404-page';
import { ISUBSCRIPTION_TYPE } from '@/shared/constants';
import LicenseExpired from '@/pages/license-expired/license-expired';
interface GuardProps {
children: React.ReactNode;
}
export const AuthGuard = ({ children }: GuardProps) => {
const isAuthenticated = useAuthService().isAuthenticated();
const location = useLocation();
if (!isAuthenticated) {
return <Navigate to="/auth" state={{ from: location }} replace />;
}
return <>{children}</>;
};
export const AdminGuard = ({ children }: GuardProps) => {
const isAuthenticated = useAuthService().isAuthenticated();
const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin();
const currentSession = useAuthService().getCurrentSession();
const isFreePlan = currentSession?.subscription_type === ISUBSCRIPTION_TYPE.FREE;
const location = useLocation();
if (!isAuthenticated) {
return <Navigate to="/auth" state={{ from: location }} replace />;
}
if (!isOwnerOrAdmin || isFreePlan) {
return <Navigate to="/worklenz/unauthorized" replace />;
}
return <>{children}</>;
};
export const LicenseExpiryGuard = ({ children }: GuardProps) => {
const isAuthenticated = useAuthService().isAuthenticated();
const currentSession = useAuthService().getCurrentSession();
const location = useLocation();
const isAdminCenterRoute = location.pathname.includes('/worklenz/admin-center');
const isLicenseExpiredRoute = location.pathname === '/worklenz/license-expired';
// Don't check or redirect if we're already on the license-expired page
if (isLicenseExpiredRoute) {
return <>{children}</>;
}
// Check if trial is expired more than 7 days or if is_expired flag is set
const isLicenseExpiredMoreThan7Days = () => {
// Quick bail if no session data is available
if (!currentSession) {
return false;
}
// Check is_expired flag first
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) {
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;
};
// Add this explicit check and log the result
const shouldRedirect = isAuthenticated && isLicenseExpiredMoreThan7Days() && !isAdminCenterRoute;
if (shouldRedirect) {
return <Navigate to="/worklenz/license-expired" replace />;
}
return <>{children}</>;
};
export const SetupGuard = ({ children }: GuardProps) => {
const isAuthenticated = useAuthService().isAuthenticated();
const location = useLocation();
if (!isAuthenticated) {
return <Navigate to="/auth" state={{ from: location }} replace />;
}
return <>{children}</>;
};
// Helper to wrap routes with guards
const wrapRoutes = (
routes: RouteObject[],
Guard: React.ComponentType<{ children: React.ReactNode }>
): RouteObject[] => {
return routes.map(route => {
const wrappedRoute = {
...route,
element: <Guard>{route.element}</Guard>,
};
if (route.children) {
wrappedRoute.children = wrapRoutes(route.children, Guard);
}
if (route.index) {
delete wrappedRoute.children;
}
return wrappedRoute;
});
};
// Static license expired component that doesn't rely on translations or authentication
const StaticLicenseExpired = () => {
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'
}}>
<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
style={{
backgroundColor: '#1890ff',
color: 'white',
border: 'none',
padding: '8px 16px',
borderRadius: '4px',
fontSize: '16px',
cursor: 'pointer'
}}
onClick={() => window.location.href = '/worklenz/admin-center/billing'}
>
Upgrade now
</button>
</div>
</div>
);
};
const publicRoutes = [
...rootRoutes,
...authRoutes,
notFoundRoute
];
const protectedMainRoutes = wrapRoutes(mainRoutes, AuthGuard);
const adminRoutes = wrapRoutes(reportingRoutes, AdminGuard);
const setupRoutes = wrapRoutes([accountSetupRoute], SetupGuard);
// Apply LicenseExpiryGuard to all protected routes
const withLicenseExpiryCheck = (routes: RouteObject[]): RouteObject[] => {
return routes.map(route => {
const wrappedRoute = {
...route,
element: <LicenseExpiryGuard>{route.element}</LicenseExpiryGuard>,
};
if (route.children) {
wrappedRoute.children = withLicenseExpiryCheck(route.children);
}
return wrappedRoute;
});
};
const licenseCheckedMainRoutes = withLicenseExpiryCheck(protectedMainRoutes);
const router = createBrowserRouter([
{
element: <ErrorBoundary><AuthenticatedLayout /></ErrorBoundary>,
errorElement: <ErrorBoundary><NotFoundPage /></ErrorBoundary>,
children: [
...licenseCheckedMainRoutes,
...adminRoutes,
...setupRoutes,
licenseExpiredRoute,
],
},
...publicRoutes,
]);
export default router;

View File

@@ -0,0 +1,64 @@
import { RouteObject } from 'react-router-dom';
import MainLayout from '@/layouts/MainLayout';
import HomePage from '@/pages/home/home-page';
import ProjectList from '@/pages/projects/project-list';
import settingsRoutes from './settings-routes';
import adminCenterRoutes from './admin-center-routes';
import Schedule from '@/pages/schedule/schedule';
import ProjectTemplateEditView from '@/pages/settings/project-templates/projectTemplateEditView/ProjectTemplateEditView';
import LicenseExpired from '@/pages/license-expired/license-expired';
import ProjectView from '@/pages/projects/projectView/project-view';
import Unauthorized from '@/pages/unauthorized/unauthorized';
import { useAuthService } from '@/hooks/useAuth';
import { Navigate, useLocation } from 'react-router-dom';
// Define AdminGuard component first
const AdminGuard = ({ children }: { children: React.ReactNode }) => {
const isAuthenticated = useAuthService().isAuthenticated();
const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin();
const location = useLocation();
if (!isAuthenticated) {
return <Navigate to="/auth" state={{ from: location }} replace />;
}
if (!isOwnerOrAdmin) {
return <Navigate to="/worklenz/unauthorized" replace />;
}
return <>{children}</>;
};
const mainRoutes: RouteObject[] = [
{
path: '/worklenz',
element: <MainLayout />,
children: [
{ path: 'home', element: <HomePage /> },
{ path: 'projects', element: <ProjectList /> },
{
path: 'schedule',
element: <AdminGuard><Schedule /></AdminGuard>
},
{ path: `projects/:projectId`, element: <ProjectView /> },
{
path: `settings/project-templates/edit/:templateId/:templateName`,
element: <ProjectTemplateEditView />,
},
{ path: 'unauthorized', element: <Unauthorized /> },
...settingsRoutes,
...adminCenterRoutes,
],
},
];
// License expired route should be separate to avoid being wrapped in LicenseExpiryGuard
export const licenseExpiredRoute: RouteObject = {
path: '/worklenz',
element: <MainLayout />,
children: [
{ path: 'license-expired', element: <LicenseExpired /> }
]
};
export default mainRoutes;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import { RouteObject } from 'react-router-dom';
import NotFoundPage from '@/pages/404-page/404-page';
const notFoundRoute: RouteObject = {
path: '*',
element: <NotFoundPage />,
};
export default notFoundRoute;

View File

@@ -0,0 +1,21 @@
import { Navigate, useLocation } from 'react-router-dom';
interface ProtectedRouteProps {
children: React.ReactNode;
isAllowed: boolean;
redirectPath?: string;
}
const ProtectedRoute = ({
children,
isAllowed,
redirectPath = '/worklenz/login',
}: ProtectedRouteProps) => {
const location = useLocation();
if (!isAllowed) {
return <Navigate to={redirectPath} replace state={{ from: location }} />;
}
return <>{children}</>;
};

View File

@@ -0,0 +1,28 @@
import { RouteObject } from 'react-router-dom';
import ReportingLayout from '@/layouts/ReportingLayout';
import { ReportingMenuItems, reportingsItems } from '@/lib/reporting/reporting-constants';
// function to flatten nested menu items
const flattenItems = (items: ReportingMenuItems[]): ReportingMenuItems[] => {
return items.reduce<ReportingMenuItems[]>((acc, item) => {
if (item.children) {
return [...acc, ...flattenItems(item.children)];
}
return [...acc, item];
}, []);
};
const flattenedItems = flattenItems(reportingsItems);
const reportingRoutes: RouteObject[] = [
{
path: 'worklenz/reporting',
element: <ReportingLayout />,
children: flattenedItems.map(item => ({
path: item.endpoint,
element: item.element,
})),
},
];
export default reportingRoutes;

View File

@@ -0,0 +1,10 @@
import { Navigate, RouteObject } from 'react-router-dom';
const rootRoutes: RouteObject[] = [
{
path: '/',
element: <Navigate to="/auth/login" replace />,
},
];
export default rootRoutes;

View File

@@ -0,0 +1,32 @@
import { RouteObject } from 'react-router-dom';
import { Navigate } from 'react-router-dom';
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 isOwnerOrAdmin = useAuthService().isOwnerOrAdmin();
if (adminRequired && !isOwnerOrAdmin) {
return <Navigate to="/worklenz/unauthorized" replace />;
}
return <>{children}</>;
};
const settingsRoutes: RouteObject[] = [
{
path: 'settings',
element: <SettingsLayout />,
children: settingsItems.map(item => ({
path: item.endpoint,
element: (
<SettingsGuard adminRequired={!!item.adminOnly}>
{item.element}
</SettingsGuard>
),
})),
},
];
export default settingsRoutes;