Skip to content

Commit

Permalink
feat: frontend infrastructure changes for app context and workspace s…
Browse files Browse the repository at this point in the history
…uspended
  • Loading branch information
vikrantgupta25 committed Nov 30, 2024
1 parent c082eba commit 5949d5d
Show file tree
Hide file tree
Showing 17 changed files with 251 additions and 46 deletions.
2 changes: 2 additions & 0 deletions ee/query-service/app/api/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ func (ah *APIHandler) getActiveLicenseV3(w http.ResponseWriter, r *http.Request)
return
}

// TODO deprecate this when we move away from key for stripe
activeLicense.Data["key"] = activeLicense.Key
render.Success(w, http.StatusOK, activeLicense.Data)
}

Expand Down
30 changes: 30 additions & 0 deletions frontend/src/AppRoutes/Private.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { isEmpty, isNull } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
import { ReactChild, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
Expand All @@ -20,6 +21,7 @@ import { AppState } from 'store/reducers';
import { getInitialUserTokenRefreshToken } from 'store/utils';
import AppActions from 'types/actions';
import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
import { LicenseState } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization';
import AppReducer from 'types/reducer/app';
import { isCloudUser } from 'utils/app';
Expand Down Expand Up @@ -49,6 +51,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
isFetchingOrgPreferences,
} = useSelector<AppState, AppReducer>((state) => state.app);

const { activeLicenseV3, isFetchingActiveLicenseV3 } = useAppContext();

const mapRoutes = useMemo(
() =>
new Map(
Expand Down Expand Up @@ -249,6 +253,32 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
}
}, [isFetchingLicensesData]);

const navigateToWorkSpaceSuspended = (route: any): void => {
const { path } = route;

if (path && path !== ROUTES.WORKSPACE_SUSPENDED) {
history.push(ROUTES.WORKSPACE_SUSPENDED);

dispatch({
type: UPDATE_USER_IS_FETCH,
payload: {
isUserFetching: false,
},
});
}
};

useEffect(() => {
if (!isFetchingActiveLicenseV3 && activeLicenseV3) {
const shouldSuspendWorkspace =
activeLicenseV3.state === LicenseState.SUSPENDED;

if (shouldSuspendWorkspace) {
navigateToWorkSpaceSuspended(currentRoute);
}
}
}, [isFetchingActiveLicenseV3, activeLicenseV3]);

useEffect(() => {
if (org && org.length > 0 && org[0].id !== undefined) {
setOrgData(org[0]);
Expand Down
79 changes: 39 additions & 40 deletions frontend/src/AppRoutes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import ROUTES from 'constants/routes';
import AppLayout from 'container/AppLayout';
import useAnalytics from 'hooks/analytics/useAnalytics';
import { KeyboardHotkeysProvider } from 'hooks/hotkeys/useKeyboardHotkeys';
import useActiveLicenseV3 from 'hooks/useActiveLicenseV3/useActiveLicenseV3';
import { useIsDarkMode, useThemeConfig } from 'hooks/useDarkMode';
import { THEME_MODE } from 'hooks/useDarkMode/constant';
import useFeatureFlags from 'hooks/useFeatureFlag';
Expand All @@ -23,6 +22,7 @@ import history from 'lib/history';
import { identity, pick, pickBy } from 'lodash-es';
import posthog from 'posthog-js';
import AlertRuleProvider from 'providers/Alert';
import { AppProvider } from 'providers/App/App';
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useEffect, useState } from 'react';
Expand Down Expand Up @@ -58,9 +58,6 @@ function App(): JSX.Element {
AppReducer
>((state) => state.app);

const { data: activeLicenseV3 } = useActiveLicenseV3();
console.log(activeLicenseV3);

const dispatch = useDispatch<Dispatch<AppActions>>();

const { trackPageView } = useAnalytics();
Expand Down Expand Up @@ -307,42 +304,44 @@ function App(): JSX.Element {
}, []);

return (
<ConfigProvider theme={themeConfig}>
<Router history={history}>
<CompatRouter>
<NotificationProvider>
<PrivateRoute>
<ResourceProvider>
<QueryBuilderProvider>
<DashboardProvider>
<KeyboardHotkeysProvider>
<AlertRuleProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Switch>
{routes.map(({ path, component, exact }) => (
<Route
key={`${path}`}
exact={exact}
path={path}
component={component}
/>
))}

<Route path="*" component={NotFound} />
</Switch>
</Suspense>
</AppLayout>
</AlertRuleProvider>
</KeyboardHotkeysProvider>
</DashboardProvider>
</QueryBuilderProvider>
</ResourceProvider>
</PrivateRoute>
</NotificationProvider>
</CompatRouter>
</Router>
</ConfigProvider>
<AppProvider>
<ConfigProvider theme={themeConfig}>
<Router history={history}>
<CompatRouter>
<NotificationProvider>
<PrivateRoute>
<ResourceProvider>
<QueryBuilderProvider>
<DashboardProvider>
<KeyboardHotkeysProvider>
<AlertRuleProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Switch>
{routes.map(({ path, component, exact }) => (
<Route
key={`${path}`}
exact={exact}
path={path}
component={component}
/>
))}

<Route path="*" component={NotFound} />
</Switch>
</Suspense>
</AppLayout>
</AlertRuleProvider>
</KeyboardHotkeysProvider>
</DashboardProvider>
</QueryBuilderProvider>
</ResourceProvider>
</PrivateRoute>
</NotificationProvider>
</CompatRouter>
</Router>
</ConfigProvider>
</AppProvider>
);
}

Expand Down
7 changes: 7 additions & 0 deletions frontend/src/AppRoutes/pageComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ export const WorkspaceBlocked = Loadable(
import(/* webpackChunkName: "WorkspaceLocked" */ 'pages/WorkspaceLocked'),
);

export const WorkspaceSuspended = Loadable(
() =>
import(
/* webpackChunkName: "WorkspaceSuspended" */ 'pages/WorkspaceSuspended/WorkspaceSuspended'
),
);

export const ShortcutsPage = Loadable(
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Shortcuts'),
);
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/AppRoutes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
UnAuthorized,
UsageExplorerPage,
WorkspaceBlocked,
WorkspaceSuspended,
} from './pageComponents';

const routes: AppRoutes[] = [
Expand Down Expand Up @@ -364,6 +365,13 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'WORKSPACE_LOCKED',
},
{
path: ROUTES.WORKSPACE_SUSPENDED,
exact: true,
component: WorkspaceSuspended,
isPrivate: true,
key: 'WORKSPACE_LOCKED',
},
{
path: ROUTES.SHORTCUTS,
exact: true,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const ROUTES = {
LOGS_SAVE_VIEWS: '/logs/saved-views',
TRACES_SAVE_VIEWS: '/traces/saved-views',
WORKSPACE_LOCKED: '/workspace-locked',
WORKSPACE_SUSPENDED: '/workspace-suspended',
SHORTCUTS: '/shortcuts',
INTEGRATIONS: '/integrations',
MESSAGING_QUEUES: '/messaging-queues',
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/container/AppLayout/AppLayout.styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@
text-align: center;
}

.payment-failed-banner {
padding: 8px;
background-color: var(--bg-sakura-500);
color: white;
text-align: center;
}

.upgrade-link {
padding: 0px;
padding-right: 4px;
Expand Down
91 changes: 89 additions & 2 deletions frontend/src/container/AppLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import './AppLayout.styles.scss';

import * as Sentry from '@sentry/react';
import { Flex } from 'antd';
import manageCreditCardApi from 'api/billing/manage';
import getUserLatestVersion from 'api/user/getLatestVersion';
import getUserVersion from 'api/user/getVersion';
import cx from 'classnames';
import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes';
import SideNav from 'container/SideNav';
Expand All @@ -19,11 +21,13 @@ import useFeatureFlags from 'hooks/useFeatureFlag';
import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { isNull } from 'lodash-es';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { useAppContext } from 'providers/App/App';
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useMutation, useQueries } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { Dispatch } from 'redux';
Expand All @@ -35,6 +39,9 @@ import {
UPDATE_LATEST_VERSION,
UPDATE_LATEST_VERSION_ERROR,
} from 'types/actions/app';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import { LicenseEvent } from 'types/api/licensesV3/getActive';
import AppReducer from 'types/reducer/app';
import { isCloudUser } from 'utils/app';
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
Expand All @@ -48,8 +55,42 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
(state) => state.app,
);

const { activeLicenseV3, isFetchingActiveLicenseV3 } = useAppContext();
const { notifications } = useNotifications();

const [
showPaymentFailedWarning,
setShowPaymentFailedWarning,
] = useState<boolean>(false);

const handleBillingOnSuccess = (
data: ErrorResponse | SuccessResponse<CheckoutSuccessPayloadProps, unknown>,
): void => {
if (data?.payload?.redirectURL) {
const newTab = document.createElement('a');
newTab.href = data.payload.redirectURL;
newTab.target = '_blank';
newTab.rel = 'noopener noreferrer';
newTab.click();
}
};

const handleBillingOnError = (): void => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
};

const {
mutate: manageCreditCard,
isLoading: isLoadingManageBilling,
} = useMutation(manageCreditCardApi, {
onSuccess: (data) => {
handleBillingOnSuccess(data);
},
onError: handleBillingOnError,
});

const isDarkMode = useIsDarkMode();

const { data: licenseData, isFetching } = useLicense();
Expand Down Expand Up @@ -212,6 +253,16 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
}
}, [licenseData, isFetching]);

useEffect(() => {
if (
!isFetchingActiveLicenseV3 &&
!isNull(activeLicenseV3) &&
activeLicenseV3?.event_queue?.event === LicenseEvent.FAILED_PAYMENT
) {
setShowPaymentFailedWarning(true);
}
}, [activeLicenseV3, isFetchingActiveLicenseV3]);

useEffect(() => {
// after logging out hide the trial expiry banner
if (!isLoggedIn) {
Expand All @@ -225,6 +276,14 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
}
};

const handleFailedPayment = (): void => {
manageCreditCard({
licenseKey: activeLicenseV3?.key || '',
successURL: window.location.href,
cancelURL: window.location.href,
});
};

const isLogsView = (): boolean =>
routeKey === 'LOGS' ||
routeKey === 'LOGS_EXPLORER' ||
Expand Down Expand Up @@ -269,7 +328,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
<title>{pageTitle}</title>
</Helmet>

{showTrialExpiryBanner && (
{showTrialExpiryBanner && !showPaymentFailedWarning && (
<div className="trial-expiry-banner">
You are in free trial period. Your free trial will end on{' '}
<span>
Expand All @@ -289,6 +348,34 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
)}
</div>
)}
{/** TODO correct the formatted date below */}
{!showTrialExpiryBanner && showPaymentFailedWarning && (
<div className="payment-failed-banner">
Your bill payment has failed. Your workspace will get suspended on{' '}
<span>
{getFormattedDate(licenseData?.payload?.trialEnd || Date.now())}.
</span>
{role === 'ADMIN' ? (
<span>
{' '}
Please{' '}
<a
className="upgrade-link"
onClick={(): void => {
if (!isLoadingManageBilling) {
handleFailedPayment();
}
}}
>
pay the bill
</a>
to continue using SigNoz features.
</span>
) : (
'Please contact your administrator to pay the bill.'
)}
</div>
)}

<Flex className={cx('app-layout', isDarkMode ? 'darkMode' : 'lightMode')}>
{isToDisplayLayout && !renderFullScreen && (
Expand Down
1 change: 1 addition & 0 deletions frontend/src/container/TopNav/Breadcrumbs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const breadcrumbNameMap: Record<string, string> = {
[ROUTES.BILLING]: 'Billing',
[ROUTES.SUPPORT]: 'Support',
[ROUTES.WORKSPACE_LOCKED]: 'Workspace Locked',
[ROUTES.WORKSPACE_SUSPENDED]: 'Workspace Suspended',
[ROUTES.MESSAGING_QUEUES]: 'Messaging Queues',
};

Expand Down
Loading

0 comments on commit 5949d5d

Please sign in to comment.