import { DialogClose } from "@radix-ui/react-dialog"; import { Form, useNavigation } from "@remix-run/react"; import { motion } from "framer-motion"; import { ArrowUpCircleIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/20/solid"; import { useEffect, useState } from "react"; import { type ShortcutDefinition } from "~/hooks/useShortcutKeys"; import { type MatchedOrganization, useDashboardLimits } from "~/hooks/useOrganizations"; import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route"; import { Feedback } from "~/components/Feedback"; import { Button, LinkButton } from "../primitives/Buttons"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTrigger, } from "../primitives/Dialog"; import { FormButtons } from "../primitives/FormButtons"; import { Input } from "../primitives/Input"; import { InputGroup } from "../primitives/InputGroup"; import { Label } from "../primitives/Label"; import { Paragraph } from "../primitives/Paragraph"; import { TextLink } from "../primitives/TextLink"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip"; import { useThemeColor } from "~/hooks/useThemeColor"; import { v3BillingPath } from "~/utils/pathBuilder"; import { type SideMenuEnvironment, type SideMenuProject } from "./SideMenu"; function useCreateDashboard({ organization, project, environment, }: { organization: { slug: string }; project: { slug: string }; environment: { slug: string }; }) { const [isOpen, setIsOpen] = useState(false); const navigation = useNavigation(); const limits = useDashboardLimits(); const plan = useCurrentPlan(); const isAtLimit = limits.used >= limits.limit; const planLimits = (plan?.v3Subscription?.plan?.limits as any)?.metricDashboards; const canExceed = typeof planLimits === "object" && planLimits.canExceed === true; const canUpgrade = plan?.v3Subscription?.plan && !canExceed; const isFreePlan = plan?.v3Subscription?.isPaying === false; const formAction = `/resources/orgs/${organization.slug}/projects/${project.slug}/env/${environment.slug}/dashboards/create`; useEffect(() => { if (navigation.formAction === formAction && navigation.state === "loading") { setIsOpen(false); } }, [navigation.formAction, navigation.state, formAction]); return { isOpen, setIsOpen, isAtLimit, canUpgrade: !!canUpgrade, isFreePlan, formAction, limits, organization, }; } export function CreateDashboardButton({ organization, project, environment, isCollapsed, }: { organization: MatchedOrganization; project: SideMenuProject; environment: SideMenuEnvironment; isCollapsed: boolean; }) { const dashboard = useCreateDashboard({ organization, project, environment }); if (isCollapsed) return null; return ( Create dashboard {dashboard.isAtLimit ? ( ) : ( )} ); } export function CreateDashboardPageButton({ organization, project, environment, shortcut, children, }: { organization: { slug: string }; project: { slug: string }; environment: { slug: string }; shortcut?: ShortcutDefinition; /** Custom trigger element. When omitted, a default primary button is rendered. */ children?: React.ReactNode; }) { const dashboard = useCreateDashboard({ organization, project, environment }); return ( {children ?? ( )} {dashboard.isAtLimit ? ( ) : ( )} ); } const PROGRESS_RING_R = 27.5; const PROGRESS_RING_CIRCUMFERENCE = 2 * Math.PI * PROGRESS_RING_R; function CreateDashboardUpgradeDialog({ limits, canUpgrade, isFreePlan, organization, }: { limits: { used: number; limit: number }; canUpgrade: boolean; isFreePlan: boolean; organization: { slug: string }; }) { // Resolved to concrete colors - framer-motion can't interpolate var() strings. // Must be called before the early return below (rules of hooks). const progressColorSuccess = useThemeColor("--color-success", "#28bf5c"); const progressColorError = useThemeColor("--color-error", "#e11d48"); if (isFreePlan) { return ( Upgrade to unlock dashboards
Custom metric dashboards are available on paid plans. Upgrade to create dashboards and track your task metrics.
Upgrade plan
); } const percentage = Math.min(limits.used / limits.limit, 1); const filled = percentage * PROGRESS_RING_CIRCUMFERENCE; return ( Dashboard limit reached
{limits.limit}
{canUpgrade ? ( <> {limits.limit === 1 ? "Your plan includes 1 custom dashboard and it's already in use." : `You've used all ${limits.limit} of your custom dashboards.`}{" "} Upgrade your plan to create more. ) : ( <> {limits.limit === 1 ? "Your plan includes 1 custom dashboard and it's already in use." : `You've used all ${limits.limit} of your custom dashboards.`}{" "} To create more, request a limit increase or visit the{" "} billing page for pricing details. )}
{canUpgrade ? ( Upgrade plan ) : ( Request more…} defaultValue="help" /> )}
); } function CreateDashboardDialog({ formAction, limits, }: { formAction: string; limits: { used: number; limit: number }; }) { const navigation = useNavigation(); const [title, setTitle] = useState(""); const isLoading = navigation.formAction === formAction; return ( Create dashboard
setTitle(e.target.value)} placeholder="My Dashboard" required /> {limits.used}/{limits.limit} dashboards used {isLoading ? "Creating..." : "Create"} } cancelButton={ } />
); }