Skip to content
Snippets Groups Projects
Unverified Commit 93a2cdde authored by github-automation-metabase's avatar github-automation-metabase Committed by GitHub
Browse files

Unify (app) banners (#50356) (#50397)


* Unify banners

* Fix and update unit tests

Co-authored-by: default avatarNemanja Glumac <31325167+nemanjaglumac@users.noreply.github.com>
parent c1519558
No related branches found
Tags v0.51.4.4 v1.51.4.4
No related merge requests found
import Markdown from "metabase/core/components/Markdown";
import { Flex, type FlexProps } from "metabase/ui";
import type { ReactNode } from "react";
export const Banner = ({ children, ...props }: FlexProps) => {
const content =
typeof children === "string" ? <Markdown>{children}</Markdown> : children;
import CS from "metabase/css/core/index.css";
import type { FlexProps, IconName } from "metabase/ui";
import { Flex, Group, Icon } from "metabase/ui";
interface BaseBannerProps extends FlexProps {
icon?: IconName;
body: ReactNode;
}
type BannerProps =
| (BaseBannerProps & { closable: true; onClose: () => void })
| (BaseBannerProps & { closable?: false; onClose?: never });
export const Banner = ({
icon,
body,
closable,
onClose,
bg,
...flexProps
}: BannerProps) => {
return (
<Flex
bg="bg-light"
c="text-medium"
data-testid="app-banner"
p="0.75rem"
{...props}
align="center"
bg={bg || "bg-medium"}
py="sm"
justify="space-between"
pl="1.325rem"
pr="md"
{...flexProps}
>
{content}
<Group spacing="xs">
{icon && <Icon name={icon} w={36} />}
{body}
</Group>
{closable && (
<Icon
className={CS.cursorPointer}
name="close"
onClick={onClose}
w={36}
/>
)}
</Flex>
);
};
import { render, screen } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import { Banner } from "./Banner";
describe("Banner", () => {
it("should render banner with content", () => {
render(<Banner>Content</Banner>);
it("should render non-closable banner with content", () => {
render(<Banner icon="warning" body="Foobar" />);
expect(screen.getByTestId("app-banner")).toBeInTheDocument();
expect(screen.getByText("Content")).toBeInTheDocument();
expect(screen.getByLabelText("warning icon")).toBeInTheDocument();
expect(screen.queryByLabelText("close icon")).not.toBeInTheDocument();
expect(screen.getByText("Foobar")).toBeInTheDocument();
});
it("should render closable banner with content", () => {
const closeMock = jest.fn();
render(
<Banner icon="warning" body="Foobar" closable onClose={closeMock} />,
);
expect(screen.getByTestId("app-banner")).toBeInTheDocument();
expect(screen.getByLabelText("warning icon")).toBeInTheDocument();
expect(screen.getByText("Foobar")).toBeInTheDocument();
fireEvent.click(screen.getByLabelText("close icon"));
expect(closeMock).toHaveBeenCalledTimes(1);
});
});
......@@ -5,6 +5,7 @@ import { Banner } from "metabase/components/Banner";
import ExternalLink from "metabase/core/components/ExternalLink";
import CS from "metabase/css/core/index.css";
import { getStoreUrl } from "metabase/selectors/settings";
import { Text } from "metabase/ui";
import type { TokenStatus } from "metabase-types/api";
interface PaymentBannerProps {
......@@ -14,35 +15,48 @@ interface PaymentBannerProps {
export const PaymentBanner = ({ tokenStatus }: PaymentBannerProps) => {
return match(tokenStatus.status)
.with("past-due", () => (
<Banner>
{jt`⚠️ We couldn't process payment for your account. Please ${(
<ExternalLink
key="payment-past-due"
className={CS.link}
href={getStoreUrl()}
>
{t`review your payment settings`}
</ExternalLink>
)} to avoid service interruptions.`}
</Banner>
<Banner
icon="warning"
body={
<Text>
{jt`We couldn't process payment for your account. Please ${(
<ExternalLink
key="payment-past-due"
className={CS.link}
href={getStoreUrl()}
>
{t`review your payment settings`}
</ExternalLink>
)} to avoid service interruptions.`}
</Text>
}
></Banner>
))
.with("unpaid", () => (
<Banner>
{jt`⚠️ Pro features won't work right now due to lack of payment. ${(
<ExternalLink
key="payment-unpaid"
className={CS.link}
href={getStoreUrl()}
>
{t`Review your payment settings`}
</ExternalLink>
)} to restore Pro functionality.`}
</Banner>
<Banner
icon="warning"
body={
<Text>{jt`Pro features won't work right now due to lack of payment. ${(
<ExternalLink
key="payment-unpaid"
className={CS.link}
href={getStoreUrl()}
>
{t`Review your payment settings`}
</ExternalLink>
)} to restore Pro functionality.`}</Text>
}
></Banner>
))
.with("invalid", () => (
<Banner>
{jt`⚠️ Pro features error. ` + (tokenStatus["error-details"] || "")}
</Banner>
<Banner
icon="warning"
body={
<Text>
{jt`Pro features error. ` + (tokenStatus["error-details"] || "")}
</Text>
}
></Banner>
))
.otherwise(() => null);
};
import { t } from "ttag";
import { Banner } from "metabase/components/Banner";
import { Icon, Text, useMantineTheme } from "metabase/ui";
import { Text } from "metabase/ui";
export const ReadOnlyBanner = () => {
const theme = useMantineTheme();
return (
<Banner
py="0.75rem"
px="1rem"
bg={theme.fn.themeColor("accent4")}
gap="md"
align="center"
>
<Icon color="text-dark" name="info_filled" />
<Text fw="bold" color="text-dark">
{/* eslint-disable-next-line no-literal-metabase-strings -- correct usage */}
{t`Metabase is under maintenance and is operating in read-only mode. It should only take up to 30 minutes.`}
</Text>
</Banner>
bg="warning"
body={
<Text fw="bold" color="text-dark">
{/* eslint-disable-next-line no-literal-metabase-strings -- correct usage */}
{t`Metabase is under maintenance and is operating in read-only mode. It should only take up to 30 minutes.`}
</Text>
}
icon="info_filled"
/>
);
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment