Replaced Upgrade callout with a more generic InfoPanel component

This commit is contained in:
James Ritchie
2024-07-02 14:37:37 +01:00
parent cae0734cca
commit 18ab17eed4
4 changed files with 103 additions and 66 deletions
@@ -0,0 +1,71 @@
import { LinkButton } from "./Buttons";
import { Header3 } from "./Headers";
import { Paragraph } from "./Paragraph";
import { cn } from "~/utils/cn";
const variants = {
info: {
panelStyle: "border-grid-bright bg-background-bright",
},
upgrade: {
panelStyle: "border-indigo-400/20 bg-indigo-800/10",
},
};
type InfoPanelVariant = keyof typeof variants;
type Props = {
title?: string;
children: React.ReactNode;
to?: string;
buttonLabel?: string;
icon: React.ComponentType<any>;
iconClassName?: string;
variant?: InfoPanelVariant;
panelClassName?: string;
};
export function InfoPanel({
title,
children,
to,
buttonLabel,
icon,
iconClassName,
variant = "info",
panelClassName = "max-w-sm",
}: Props) {
const Icon = icon;
const variantStyle = variants[variant];
return (
<div
className={cn(
variantStyle.panelStyle,
title ? "flex-col" : "",
"flex h-fit max-w-sm items-start gap-3 rounded-md border p-4",
panelClassName
)}
>
<div className={cn("flex items-center gap-2", to ? "w-full justify-between" : "")}>
<Icon className={cn("size-5", iconClassName)} />
{to && (
<LinkButton to={to} variant="secondary/small">
{buttonLabel}
</LinkButton>
)}
</div>
<div className="flex flex-col gap-1">
{title && <Header3 className="text-text-bright">{title}</Header3>}
{typeof children === "string" ? (
<Paragraph variant={"small"} className="text-text-dimmed">
{children}
</Paragraph>
) : (
children
)}
</div>
</div>
);
}
@@ -1,42 +0,0 @@
import { LockOpenIcon } from "@heroicons/react/24/solid";
import { useOrganization } from "~/hooks/useOrganizations";
import { v3BillingPath } from "~/utils/pathBuilder";
import { LinkButton } from "./Buttons";
import { Header3 } from "./Headers";
import { Paragraph } from "./Paragraph";
type Props = {
title: string;
children: React.ReactNode;
to?: string;
};
export function UpgradeCallout({ title, children, to }: Props) {
const organization = useOrganization();
to = to || v3BillingPath(organization);
return (
<div
className="flex w-fit flex-col items-start justify-between gap-3 rounded-md border border-indigo-400/20
bg-indigo-800/10 p-4"
>
<div className="flex w-full items-center justify-between gap-2">
<LockOpenIcon className="size-6 text-indigo-500" />
<LinkButton to={to} variant="secondary/small">
Upgrade
</LinkButton>
</div>
<div className="flex flex-col gap-1">
<Header3 className="text-text-bright">{title}</Header3>
{typeof children === "string" ? (
<Paragraph variant={"small"} className="text-text-dimmed">
{children}
</Paragraph>
) : (
children
)}
</div>
</div>
);
}
@@ -1,5 +1,4 @@
import { BookOpenIcon } from "@heroicons/react/20/solid";
import { InformationCircleIcon } from "@heroicons/react/24/outline";
import { BookOpenIcon, InformationCircleIcon, LockOpenIcon } from "@heroicons/react/20/solid";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
@@ -9,7 +8,7 @@ import { PageBody, PageContainer } from "~/components/layout/AppLayout";
import { LinkButton } from "~/components/primitives/Buttons";
import { ClipboardField } from "~/components/primitives/ClipboardField";
import { DateTime } from "~/components/primitives/DateTime";
import { Header3 } from "~/components/primitives/Headers";
import { InfoPanel } from "~/components/primitives/InfoPanel";
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Property, PropertyTable } from "~/components/primitives/PropertyTable";
@@ -23,10 +22,10 @@ import {
TableRow,
} from "~/components/primitives/Table";
import { TextLink } from "~/components/primitives/TextLink";
import { UpgradeCallout } from "~/components/primitives/UpgradeCallout";
import { useOrganization } from "~/hooks/useOrganizations";
import { ApiKeysPresenter } from "~/presenters/v3/ApiKeysPresenter.server";
import { requireUserId } from "~/services/session.server";
import { ProjectParamSchema, docsPath } from "~/utils/pathBuilder";
import { ProjectParamSchema, docsPath, v3BillingPath } from "~/utils/pathBuilder";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
@@ -54,6 +53,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
export default function Page() {
const { environments, hasStaging } = useTypedLoaderData<typeof loader>();
const organization = useOrganization();
return (
<PageContainer>
@@ -124,25 +124,26 @@ export default function Page() {
</TableBody>
</Table>
<div className="flex items-start gap-3">
<div className="flex max-w-sm flex-col items-start justify-between gap-3 rounded-md border border-grid-bright p-4">
<div className="flex w-full items-center justify-between gap-2">
<InformationCircleIcon className="size-6 text-text-dimmed" />
</div>
<div className="flex flex-col gap-1">
<Header3 className="text-text-bright">Secret keys</Header3>
<Paragraph variant={"small"} className="text-text-dimmed">
Secret keys should be used on your server. They give full API access and allow you
to <TextLink to={docsPath("v3/triggering")}>trigger tasks</TextLink> from your
backend.
</Paragraph>
</div>
</div>
<div className="flex gap-3">
<InfoPanel icon={InformationCircleIcon} title="Secret keys">
<Paragraph variant="small">
Secret keys should be used on your server. They give full API access and allow you
to <TextLink to={docsPath("v3/triggering")}>trigger tasks</TextLink> from your
backend.
</Paragraph>
</InfoPanel>
{!hasStaging && (
<UpgradeCallout title="Unlock a Staging environment">
<InfoPanel
icon={LockOpenIcon}
variant="upgrade"
title="Unlock a Staging environment"
to={v3BillingPath(organization)}
buttonLabel="Upgrade"
iconClassName="text-indigo-500"
>
Upgrade your plan to add a Staging environment.
</UpgradeCallout>
</InfoPanel>
)}
</div>
</div>
@@ -1,6 +1,12 @@
import { useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { BookOpenIcon, PencilSquareIcon, PlusIcon, TrashIcon } from "@heroicons/react/20/solid";
import {
BookOpenIcon,
InformationCircleIcon,
PencilSquareIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/20/solid";
import { Form, Outlet, useActionData, useNavigation } from "@remix-run/react";
import {
ActionFunctionArgs,
@@ -22,6 +28,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "~/components
import { Fieldset } from "~/components/primitives/Fieldset";
import { FormButtons } from "~/components/primitives/FormButtons";
import { FormError } from "~/components/primitives/FormError";
import { InfoPanel } from "~/components/primitives/InfoPanel";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
@@ -256,10 +263,10 @@ export default function Page() {
</TableBody>
</Table>
<Callout variant="info" className="mb-4">
<InfoPanel icon={InformationCircleIcon} panelClassName="max-w-md">
Dev environment variables specified here will be overridden by ones in your .env file
when running locally.
</Callout>
</InfoPanel>
</div>
<Outlet />
</PageBody>