billing
This commit is contained in:
1 parent
5e9c85f372
commit
92f7a929fa
6 files changed
+626
-576
No files matched your search
@@ -2,5 +2,6 @@ window.__ENV = {
|
||||
GLOBAL_REFRESH: "60000",
|
||||
APP_HOST: "http://localhost:5173",
|
||||
APP_API_BASE_URL: "http://localhost:52345/api/v1",
|
||||
APP_DEPLOYMENT_MODE: "self_hosted",
|
||||
// APP_DEPLOYMENT_MODE: "self_hosted",
|
||||
APP_DEPLOYMENT_MODE: "saas",
|
||||
};
|
||||
@@ -34,7 +34,7 @@ export const HeaderCreate = ({
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Tooltip
|
||||
title={limitReached ? "Upgrade your plan to create more teams" : ""}
|
||||
title={limitReached ? t("common.tooltips.limitReached") : ""}
|
||||
disableHoverListener={!limitReached}
|
||||
>
|
||||
<span>
|
||||
@@ -46,7 +46,7 @@ export const HeaderCreate = ({
|
||||
onClick={() => navigate(path)}
|
||||
>
|
||||
{limitReached
|
||||
? "Your plan's limit has been reached"
|
||||
? t("common.buttons.limitReached")
|
||||
: label || t("common.buttons.createNew")}
|
||||
</Button>
|
||||
</span>
|
||||
|
||||
@@ -3,29 +3,14 @@ import Typography from "@mui/material/Typography";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { ChevronRight, Home } from "lucide-react";
|
||||
|
||||
const pathNameMap: Record<string, string> = {
|
||||
uptime: "Uptime Monitors",
|
||||
pagespeed: "PageSpeed Monitors",
|
||||
infrastructure: "Infrastructure Monitors",
|
||||
diagnostics: "Diagnostics",
|
||||
incidents: "Incidents",
|
||||
maintenance: "Maintenance",
|
||||
"notification-channels": "Notification Channels",
|
||||
"status-pages": "Status Pages",
|
||||
teams: "Teams",
|
||||
settings: "Settings",
|
||||
create: "Create",
|
||||
configure: "Configure",
|
||||
invite: "Invite",
|
||||
"team-members": "Team Members",
|
||||
};
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const Breadcrumb = ({
|
||||
breadcrumbOverride,
|
||||
}: {
|
||||
breadcrumbOverride?: string[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const location = useLocation();
|
||||
|
||||
@@ -81,7 +66,8 @@ export const Breadcrumb = ({
|
||||
const to = `/${pathnames.slice(0, index + 1).join("/")}`;
|
||||
|
||||
let displayName =
|
||||
pathNameMap[value] || value.charAt(0).toUpperCase() + value.slice(1);
|
||||
t(`common.breadcrumbs.${value}`) ||
|
||||
value.charAt(0).toUpperCase() + value.slice(1);
|
||||
|
||||
// Paths that are IDs should be detail pages
|
||||
if (value.length === 24 || value.match(/^[a-f0-9-]{36}$/)) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import Tooltip from "@mui/material/Tooltip";
|
||||
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type { Entitlements, PlanKey } from "@/types/entitlements";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const PlanCard = ({
|
||||
plan,
|
||||
@@ -27,6 +28,7 @@ export const PlanCard = ({
|
||||
onClick?: (planKey: PlanKey) => void;
|
||||
loading?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const selected = plan.plan === currentPlan;
|
||||
const theme = useTheme();
|
||||
const checkColor =
|
||||
@@ -60,34 +62,34 @@ export const PlanCard = ({
|
||||
})();
|
||||
const featureItems = [
|
||||
{
|
||||
label: "Monitors",
|
||||
label: t("billing.plans.features.monitors.title"),
|
||||
value: plan.monitorsMax,
|
||||
tooltip: "Maximum number of monitors allowed.",
|
||||
tooltip: t("billing.plans.features.monitors.toolTip"),
|
||||
},
|
||||
{
|
||||
label: "Status pages",
|
||||
label: t("billing.plans.features.statusPages.title"),
|
||||
value: plan.statusPagesMax,
|
||||
tooltip: "Maximum public status pages you can create.",
|
||||
tooltip: t("billing.plans.features.statusPages.toolTip"),
|
||||
},
|
||||
{
|
||||
label: "Notification channels",
|
||||
label: t("billing.plans.features.notificationChannels.title"),
|
||||
value: plan.notificationChannelsMax,
|
||||
tooltip: "Maximum alerting channels (email, Slack, etc.).",
|
||||
tooltip: t("billing.plans.features.notificationChannels.toolTip"),
|
||||
},
|
||||
{
|
||||
label: "Teams",
|
||||
label: t("billing.plans.features.teams.title"),
|
||||
value: plan.teamsMax,
|
||||
tooltip: "Maximum number of teams in your org.",
|
||||
tooltip: t("billing.plans.features.teams.toolTip"),
|
||||
},
|
||||
{
|
||||
label: "Retention",
|
||||
label: t("billing.plans.features.retention.title"),
|
||||
value: `${plan.retentionDays} days`,
|
||||
tooltip: "Oldest checks are automatically removed after retention.",
|
||||
tooltip: t("billing.plans.features.retention.toolTip"),
|
||||
},
|
||||
{
|
||||
label: "Min interval",
|
||||
label: t("billing.plans.features.interval.title"),
|
||||
value: `${Math.round(plan.checksIntervalMsMin / 1000)}s`,
|
||||
tooltip: "Minimum check frequency allowed for this plan.",
|
||||
tooltip: t("billing.plans.features.interval.toolTip"),
|
||||
},
|
||||
];
|
||||
return (
|
||||
@@ -128,7 +130,7 @@ export const PlanCard = ({
|
||||
{plan.plan === "pro" && (
|
||||
<Chip
|
||||
size="small"
|
||||
label="Most popular"
|
||||
label={t("billing.plans.mostPopular")}
|
||||
sx={{
|
||||
bgcolor: theme.palette.primary.main,
|
||||
color: theme.palette.getContrastText(
|
||||
@@ -150,12 +152,20 @@ export const PlanCard = ({
|
||||
<Check size={16} color={checkColor} />
|
||||
{item.tooltip ? (
|
||||
<Tooltip title={item.tooltip} arrow placement="right">
|
||||
<Typography variant="body1" color="text.secondary" fontSize={18}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.secondary"
|
||||
fontSize={18}
|
||||
>
|
||||
{item.label}: {item.value}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Typography variant="body1" color="text.secondary" fontSize={18}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.secondary"
|
||||
fontSize={18}
|
||||
>
|
||||
{item.label}: {item.value}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
+588
-537
File diff suppressed because it is too large.
Load diff
@@ -3,6 +3,7 @@ import Grid from "@mui/material/Grid";
|
||||
import Box from "@mui/material/Box";
|
||||
import { Button } from "@/components/inputs";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppDispatch, useAppSelector } from "@/hooks/AppHooks";
|
||||
import { useGet, usePost } from "@/hooks/UseApi";
|
||||
import type { ApiResponse } from "@/hooks/UseApi";
|
||||
@@ -12,6 +13,7 @@ import type { IUser } from "@/types/user";
|
||||
import { setSelectedTeamId, setUser } from "@/features/authSlice";
|
||||
|
||||
const BillingPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { user } = useAppSelector((state) => state.auth);
|
||||
const { response } = useGet<ApiResponse<Entitlements[]>>(
|
||||
"/billing/plans",
|
||||
@@ -62,8 +64,8 @@ const BillingPage = () => {
|
||||
return (
|
||||
<BasePage>
|
||||
<InfoBox
|
||||
title="Choose a plan!"
|
||||
description="Choose a plan that fits your needs and start enjoying our services."
|
||||
title={t("billing.infoBox.title")}
|
||||
description={t("billing.infoBox.description")}
|
||||
/>
|
||||
<Grid container spacing={10} columns={{ xs: 1, sm: 2, md: 4 }}>
|
||||
{plans.map((plan) => (
|
||||
@@ -85,7 +87,7 @@ const BillingPage = () => {
|
||||
onClick={handleCancel}
|
||||
loading={isPosting}
|
||||
>
|
||||
Cancel subscription
|
||||
{t("buttons.cancelSubscription")}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user