0}
+ condition={!isPAYG}
show={
- ${planPrice.toFixed(2)}
+ ${baseProPrice.toFixed(2)}
}
/>
diff --git a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/useOverageCost.ts b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/useOverageCost.ts
index 80ae41455c..1f29f95158 100644
--- a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/useOverageCost.ts
+++ b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/useOverageCost.ts
@@ -5,7 +5,8 @@ import {
calculateOverageCost,
calculateTotalUsage,
} from 'utils/traffic-calculations';
-import { BILLING_TRAFFIC_BUNDLE_PRICE } from './BillingPlan';
+import { BILLING_TRAFFIC_PRICE } from './BillingPlan';
+import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
export const useOverageCost = (includedTraffic: number) => {
if (!includedTraffic) {
@@ -17,6 +18,12 @@ export const useOverageCost = (includedTraffic: number) => {
const from = formatDate(startOfMonth(now));
const to = formatDate(endOfMonth(now));
+ const { instanceStatus } = useInstanceStatus();
+ const trafficPrice =
+ instanceStatus?.prices?.[
+ instanceStatus?.billing === 'pay-as-you-go' ? 'payg' : 'pro'
+ ]?.traffic ?? BILLING_TRAFFIC_PRICE;
+
const { result } = useTrafficSearch('daily', { from, to });
const overageCost = useMemo(() => {
if (result.state !== 'success') {
@@ -24,12 +31,8 @@ export const useOverageCost = (includedTraffic: number) => {
}
const totalUsage = calculateTotalUsage(result.data);
- return calculateOverageCost(
- totalUsage,
- includedTraffic,
- BILLING_TRAFFIC_BUNDLE_PRICE,
- );
- }, [includedTraffic, JSON.stringify(result)]);
+ return calculateOverageCost(totalUsage, includedTraffic, trafficPrice);
+ }, [includedTraffic, JSON.stringify(result), trafficPrice]);
return overageCost;
};
diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useStats.ts b/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useStats.ts
index c34f4ab910..eec6922988 100644
--- a/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useStats.ts
+++ b/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useStats.ts
@@ -12,10 +12,11 @@ import {
calculateOverageCost,
calculateTotalUsage,
} from 'utils/traffic-calculations';
-import { BILLING_TRAFFIC_BUNDLE_PRICE } from '../../../billing/BillingDashboard/BillingPlan/BillingPlan';
+import { BILLING_TRAFFIC_PRICE } from '../../../billing/BillingDashboard/BillingPlan/BillingPlan';
import { averageTrafficPreviousMonths } from '../average-traffic-previous-months';
import { useConnectionsConsumption } from 'hooks/api/getters/useConnectionsConsumption/useConnectionsConsumption';
import { useRequestsConsumption } from 'hooks/api/getters/useRequestsConsumption/useRequestsConsumption';
+import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
export const useTrafficStats = (
includedTraffic: number,
@@ -26,6 +27,12 @@ export const useTrafficStats = (
chartDataSelection.grouping,
toDateRange(chartDataSelection, currentDate),
);
+ const { instanceStatus } = useInstanceStatus();
+ const trafficPrice =
+ instanceStatus?.prices?.[
+ instanceStatus?.billing === 'pay-as-you-go' ? 'payg' : 'pro'
+ ]?.traffic ?? BILLING_TRAFFIC_PRICE;
+
const results = useMemo(() => {
if (result.state !== 'success') {
return {
@@ -43,14 +50,14 @@ export const useTrafficStats = (
const overageCost = calculateOverageCost(
usageTotal,
includedTraffic,
- BILLING_TRAFFIC_BUNDLE_PRICE,
+ trafficPrice,
);
const estimatedMonthlyCost = calculateEstimatedMonthlyCost(
traffic.apiData,
includedTraffic,
currentDate,
- BILLING_TRAFFIC_BUNDLE_PRICE,
+ trafficPrice,
);
const requestSummaryUsage =
@@ -69,6 +76,7 @@ export const useTrafficStats = (
JSON.stringify(result),
includedTraffic,
JSON.stringify(chartDataSelection),
+ trafficPrice,
]);
return results;
diff --git a/frontend/src/component/admin/users/CreateUser/SeatCostWarning/SeatCostWarning.tsx b/frontend/src/component/admin/users/CreateUser/SeatCostWarning/SeatCostWarning.tsx
index 0ef6df601a..62d20034c8 100644
--- a/frontend/src/component/admin/users/CreateUser/SeatCostWarning/SeatCostWarning.tsx
+++ b/frontend/src/component/admin/users/CreateUser/SeatCostWarning/SeatCostWarning.tsx
@@ -2,11 +2,15 @@ import type { VFC } from 'react';
import { Alert } from '@mui/material';
import { useUsersPlan } from 'hooks/useUsersPlan';
import { useUsers } from 'hooks/api/getters/useUsers/useUsers';
-import { BILLING_PRO_USER_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
+import { BILLING_PRO_SEAT_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
+import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
export const SeatCostWarning: VFC = () => {
const { users } = useUsers();
const { isBillingUsers, seats, planUsers } = useUsersPlan(users);
+ const { instanceStatus } = useInstanceStatus();
+ const seatPrice =
+ instanceStatus?.prices?.pro?.seat ?? BILLING_PRO_SEAT_PRICE;
if (!isBillingUsers || planUsers.length < seats) {
return null;
@@ -20,9 +24,8 @@ export const SeatCostWarning: VFC = () => {
Heads up! You are exceeding your allocated free
members included in your plan ({planUsers.length} of {seats}).
- Creating this user will add{' '}
- ${BILLING_PRO_USER_PRICE}/month to your
- invoice, starting with your next payment.
+ Creating this user will add ${seatPrice}/month{' '}
+ to your invoice, starting with your next payment.
);
diff --git a/frontend/src/component/demo/DemoDialog/DemoDialogPlans/DemoDialogPlans.tsx b/frontend/src/component/demo/DemoDialog/DemoDialogPlans/DemoDialogPlans.tsx
index 6728914ea5..c038ef3e27 100644
--- a/frontend/src/component/demo/DemoDialog/DemoDialogPlans/DemoDialogPlans.tsx
+++ b/frontend/src/component/demo/DemoDialog/DemoDialogPlans/DemoDialogPlans.tsx
@@ -6,11 +6,11 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUiFlag } from 'hooks/useUiFlag';
import {
BILLING_PAYG_DEFAULT_MINIMUM_SEATS,
- BILLING_PAYG_USER_PRICE,
- BILLING_PLAN_PRICES,
+ BILLING_PAYG_SEAT_PRICE,
+ BILLING_PRO_BASE_PRICE,
BILLING_PRO_DEFAULT_INCLUDED_SEATS,
} from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
-import { InstancePlan } from 'interfaces/instance';
+import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
const StyledDemoDialog = styled(DemoDialog)(({ theme }) => ({
'& .MuiDialog-paper': {
@@ -91,6 +91,12 @@ interface IDemoDialogPlansProps {
export const DemoDialogPlans = ({ open, onClose }: IDemoDialogPlansProps) => {
const { trackEvent } = usePlausibleTracker();
const isEnterprisePaygEnabled = useUiFlag('enterprise-payg');
+ const { instanceStatus } = useInstanceStatus();
+
+ const paygSeatPrice =
+ instanceStatus?.prices?.payg?.seat ?? BILLING_PAYG_SEAT_PRICE;
+ const proBasePrice =
+ instanceStatus?.prices?.pro?.base ?? BILLING_PRO_BASE_PRICE;
return (
@@ -139,7 +145,7 @@ export const DemoDialogPlans = ({ open, onClose }: IDemoDialogPlansProps) => {
- ${BILLING_PAYG_USER_PRICE} per user/month
+ ${paygSeatPrice} per user/month
{BILLING_PAYG_DEFAULT_MINIMUM_SEATS} users
@@ -174,7 +180,7 @@ export const DemoDialogPlans = ({ open, onClose }: IDemoDialogPlansProps) => {
- ${BILLING_PLAN_PRICES[InstancePlan.PRO]}/month
+ ${proBasePrice}/month
includes {BILLING_PRO_DEFAULT_INCLUDED_SEATS}{' '}
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/ProjectEnvironmentStrategyDraggableItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/ProjectEnvironmentStrategyDraggableItem.tsx
index 19de8335cf..cd0a413dee 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/ProjectEnvironmentStrategyDraggableItem.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/ProjectEnvironmentStrategyDraggableItem.tsx
@@ -1,5 +1,5 @@
import { type DragEventHandler, type RefObject, useRef } from 'react';
-import { Box, useMediaQuery, useTheme } from '@mui/material';
+import { useMediaQuery, useTheme } from '@mui/material';
import type { IFeatureEnvironment } from 'interfaces/featureToggle';
import type { IFeatureStrategy } from 'interfaces/strategy';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
@@ -34,8 +34,6 @@ type ProjectEnvironmentStrategyDraggableItemProps = {
onDragEnd?: () => void;
};
-const onDragNoOp = () => () => {};
-
export const ProjectEnvironmentStrategyDraggableItem = ({
className,
strategy,
@@ -43,9 +41,9 @@ export const ProjectEnvironmentStrategyDraggableItem = ({
environmentName,
otherEnvironments,
isDragging,
- onDragStartRef = onDragNoOp,
- onDragOver = onDragNoOp,
- onDragEnd = onDragNoOp,
+ onDragStartRef,
+ onDragOver,
+ onDragEnd,
}: ProjectEnvironmentStrategyDraggableItemProps) => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
@@ -75,67 +73,59 @@ export const ProjectEnvironmentStrategyDraggableItem = ({
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
return (
-
-
- {draftChange && !isSmallScreen ? (
-
- ) : null}
+
+ {draftChange && !isSmallScreen ? (
+
+ ) : null}
- {scheduledChanges &&
- scheduledChanges.length > 0 &&
- !isSmallScreen ? (
- scheduledChange.id)}
- />
- ) : null}
- {otherEnvironments && otherEnvironments?.length > 0 ? (
-
- ) : null}
-
-
-
- 0 &&
+ !isSmallScreen ? (
+ scheduledChange.id)}
+ />
+ ) : null}
+ {otherEnvironments && otherEnvironments?.length > 0 ? (
+
- >
- }
- />
-
+ ) : null}
+
+
+
+
+ >
+ }
+ />
);
};
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx
index 0ca7ca554a..6e2d857c6f 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx
@@ -8,8 +8,6 @@ import { Box } from '@mui/material';
import type { IFeatureStrategy } from 'interfaces/strategy';
import { StrategyItem } from './StrategyItem/StrategyItem';
-const onDragNoOp = () => () => {};
-
type StrategyDraggableItemProps = {
headerItemsRight: ReactNode;
strategy: IFeatureStrategy;
@@ -30,9 +28,9 @@ export const StrategyDraggableItem = ({
strategy,
index,
isDragging,
- onDragStartRef = onDragNoOp,
- onDragOver = onDragNoOp,
- onDragEnd = onDragNoOp,
+ onDragStartRef,
+ onDragOver,
+ onDragEnd,
headerItemsRight,
}: StrategyDraggableItemProps) => {
const ref = useRef(null);
@@ -41,13 +39,13 @@ export const StrategyDraggableItem = ({
diff --git a/frontend/src/interfaces/instance.ts b/frontend/src/interfaces/instance.ts
index 5a03e1e91b..c06eef0e63 100644
--- a/frontend/src/interfaces/instance.ts
+++ b/frontend/src/interfaces/instance.ts
@@ -1,3 +1,17 @@
+type InstancePrices = {
+ pro?: {
+ base?: number;
+ seat?: number;
+ traffic?: number;
+ };
+ payg?: {
+ seat?: number;
+ traffic?: number;
+ };
+};
+
+type InstanceBilling = 'pay-as-you-go' | 'subscription';
+
export interface IInstanceStatus {
plan: InstancePlan;
trialExpiry?: string;
@@ -8,6 +22,8 @@ export interface IInstanceStatus {
seats?: number;
minSeats?: number;
isCustomBilling?: boolean;
+ prices?: InstancePrices;
+ billing?: InstanceBilling;
}
export enum InstanceState {
diff --git a/src/lib/services/access-service.ts b/src/lib/services/access-service.ts
index 4cd15f3b11..388fc35cfb 100644
--- a/src/lib/services/access-service.ts
+++ b/src/lib/services/access-service.ts
@@ -457,8 +457,9 @@ export class AccessService {
async getRootRoleForUser(userId: number): Promise {
const rootRole = await this.store.getRootRoleForUser(userId);
if (!rootRole) {
- const defaultRole = await this.getPredefinedRole(RoleName.VIEWER);
- return defaultRole;
+ // this should never happen, but before breaking we want to know if it does.
+ this.logger.warn(`Could not find root role for user=${userId}.`);
+ return this.getPredefinedRole(RoleName.VIEWER);
}
return rootRole;
}
diff --git a/src/migrations/20250320121200-all-users-have-a-root-role.js b/src/migrations/20250320121200-all-users-have-a-root-role.js
new file mode 100644
index 0000000000..8f1ab0ad27
--- /dev/null
+++ b/src/migrations/20250320121200-all-users-have-a-root-role.js
@@ -0,0 +1,20 @@
+exports.up = function (db, cb) {
+ // add root role Viewer (id 3) to all users who don't have a root role
+ db.runSql(
+ `INSERT INTO role_user(role_id, user_id, project) SELECT 3, u.id, 'default'
+FROM users u
+WHERE u.id > 0 AND u.deleted_at IS NULL AND NOT EXISTS (
+ SELECT 1
+ FROM role_user ru
+ JOIN roles r ON ru.role_id = r.id
+ WHERE ru.user_id = u.id
+ AND r.type IN ('root', 'root-custom')
+);`,
+ cb,
+ );
+};
+
+exports.down = function (db, callback) {
+ // No rollback
+ callback();
+};
\ No newline at end of file
diff --git a/website/yarn.lock b/website/yarn.lock
index 36a5ec6a41..5c9b6237c8 100644
--- a/website/yarn.lock
+++ b/website/yarn.lock
@@ -235,7 +235,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.8.3":
+"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.26.2, @babel/code-frame@npm:^7.8.3":
version: 7.26.2
resolution: "@babel/code-frame@npm:7.26.2"
dependencies:
@@ -494,12 +494,12 @@ __metadata:
linkType: hard
"@babel/helpers@npm:^7.26.0":
- version: 7.26.0
- resolution: "@babel/helpers@npm:7.26.0"
+ version: 7.26.10
+ resolution: "@babel/helpers@npm:7.26.10"
dependencies:
- "@babel/template": "npm:^7.25.9"
- "@babel/types": "npm:^7.26.0"
- checksum: 10c0/343333cced6946fe46617690a1d0789346960910225ce359021a88a60a65bc0d791f0c5d240c0ed46cf8cc63b5fd7df52734ff14e43b9c32feae2b61b1647097
+ "@babel/template": "npm:^7.26.9"
+ "@babel/types": "npm:^7.26.10"
+ checksum: 10c0/f99e1836bcffce96db43158518bb4a24cf266820021f6461092a776cba2dc01d9fc8b1b90979d7643c5c2ab7facc438149064463a52dd528b21c6ab32509784f
languageName: node
linkType: hard
@@ -514,6 +514,17 @@ __metadata:
languageName: node
linkType: hard
+"@babel/parser@npm:^7.26.9":
+ version: 7.26.10
+ resolution: "@babel/parser@npm:7.26.10"
+ dependencies:
+ "@babel/types": "npm:^7.26.10"
+ bin:
+ parser: ./bin/babel-parser.js
+ checksum: 10c0/c47f5c0f63cd12a663e9dc94a635f9efbb5059d98086a92286d7764357c66bceba18ccbe79333e01e9be3bfb8caba34b3aaebfd8e62c3d5921c8cf907267be75
+ languageName: node
+ linkType: hard
+
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9":
version: 7.25.9
resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9"
@@ -1482,6 +1493,17 @@ __metadata:
languageName: node
linkType: hard
+"@babel/template@npm:^7.26.9":
+ version: 7.26.9
+ resolution: "@babel/template@npm:7.26.9"
+ dependencies:
+ "@babel/code-frame": "npm:^7.26.2"
+ "@babel/parser": "npm:^7.26.9"
+ "@babel/types": "npm:^7.26.9"
+ checksum: 10c0/019b1c4129cc01ad63e17529089c2c559c74709d225f595eee017af227fee11ae8a97a6ab19ae6768b8aa22d8d75dcb60a00b28f52e9fa78140672d928bc1ae9
+ languageName: node
+ linkType: hard
+
"@babel/traverse@npm:^7.25.9":
version: 7.25.9
resolution: "@babel/traverse@npm:7.25.9"
@@ -1507,6 +1529,16 @@ __metadata:
languageName: node
linkType: hard
+"@babel/types@npm:^7.26.10, @babel/types@npm:^7.26.9":
+ version: 7.26.10
+ resolution: "@babel/types@npm:7.26.10"
+ dependencies:
+ "@babel/helper-string-parser": "npm:^7.25.9"
+ "@babel/helper-validator-identifier": "npm:^7.25.9"
+ checksum: 10c0/7a7f83f568bfc3dfabfaf9ae3a97ab5c061726c0afa7dcd94226d4f84a81559da368ed79671e3a8039d16f12476cf110381a377ebdea07587925f69628200dac
+ languageName: node
+ linkType: hard
+
"@braintree/sanitize-url@npm:^7.0.1":
version: 7.1.1
resolution: "@braintree/sanitize-url@npm:7.1.1"