feat: add AppBreadcrumbs and new provider pages

This commit is contained in:
Adrian Pilarczyk 2023-05-18 13:37:00 +02:00
parent bac7d970b8
commit 4a34a4b0c6
9 changed files with 231 additions and 47 deletions

View file

@ -7,3 +7,4 @@ export const providersSchema = z.array(providerSchema);
export type ProvidersConfig = z.infer<typeof providersSchema>;
export type ProviderConfig = z.infer<typeof providerSchema>;
export type ProviderName = ProviderConfig["provider"];

View file

@ -1,14 +0,0 @@
import { AvataxIcon, TaxJarIcon } from "../../assets";
export const providerConfig = {
taxjar: {
label: "TaxJar",
icon: TaxJarIcon,
},
avatax: {
label: "Avatax",
icon: AvataxIcon,
},
};
export type TaxProviderName = keyof typeof providerConfig;

View file

@ -0,0 +1,61 @@
import { Breadcrumbs } from "@saleor/apps-ui";
import { useRouter } from "next/router";
type Breadcrumb = {
label: string;
href?: string;
};
const newProviderBreadcrumbs = [
{
href: "/configuration",
label: "Configuration",
},
{
label: "Add provider",
href: "/providers",
},
] as Breadcrumb[];
const breadcrumbsForRoute = {
"/configuration": [
{
href: "/configuration",
label: "Configuration",
},
],
"/providers": [...newProviderBreadcrumbs],
"/providers/taxjar": [
...newProviderBreadcrumbs,
{
label: "TaxJar",
href: "/providers/taxjar",
},
],
"/providers/avatax": [
...newProviderBreadcrumbs,
{
label: "Avatax",
href: "/providers/avatax",
},
],
} as Record<string, Breadcrumb[]>;
const useBreadcrumbs = () => {
const router = useRouter();
const breadcrumbs = breadcrumbsForRoute[router.pathname];
return breadcrumbs;
};
export const AppBreadcrumbs = () => {
const breadcrumbs = useBreadcrumbs();
return (
<Breadcrumbs>
{breadcrumbs?.map((breadcrumb) => (
<Breadcrumbs.Item href={breadcrumb.href}>{breadcrumb.label}</Breadcrumbs.Item>
))}
</Breadcrumbs>
);
};

View file

@ -1,17 +1,7 @@
import { Box, BoxProps, Button, Text } from "@saleor/macaw-ui/next";
import { useRouter } from "next/router";
import { trpcClient } from "../trpc/trpc-client";
const Table = {
Container: (props: BoxProps) => <Box __textAlign={"left"} width="100%" {...props} as="table" />,
THead: (props: BoxProps) => <Box {...props} as="thead" />,
TR: (props: BoxProps) => <Box {...props} as="tr" />,
TH: (props: BoxProps) => (
<Box fontWeight={"captionSmall"} fontSize={"captionSmall"} {...props} as="th" />
),
TBody: (props: BoxProps) => <Box {...props} as="tbody" />,
TD: (props: BoxProps) => <Box fontSize="bodyMedium" {...props} as="td" />,
};
import { AppCard } from "./app-card";
const AddProvider = () => {
const router = useRouter();
@ -28,19 +18,31 @@ const AddProvider = () => {
<Text variant="body" __fontWeight={"400"}>
No providers configured yet
</Text>
<Button onClick={() => router.push("/providers/new")}>Add first provider</Button>
<Button onClick={() => router.push("/providers")}>Add first provider</Button>
</Box>
);
};
const Skeleton = () => {
// todo: replace with skeleton
return (
<Box height={"100%"} display={"flex"} alignItems={"center"} justifyContent={"center"}>
Skeleton...
Loading...
</Box>
);
};
const Table = {
Container: (props: BoxProps) => <Box __textAlign={"left"} width="100%" {...props} as="table" />,
THead: (props: BoxProps) => <Box {...props} as="thead" />,
TR: (props: BoxProps) => <Box {...props} as="tr" />,
TH: (props: BoxProps) => (
<Box fontWeight={"captionSmall"} fontSize={"captionSmall"} {...props} as="th" />
),
TBody: (props: BoxProps) => <Box {...props} as="tbody" />,
TD: (props: BoxProps) => <Box fontSize="bodyMedium" {...props} as="td" />,
};
const ProvidersTable = () => {
const { data } = trpcClient.providersConfiguration.getAll.useQuery();
@ -75,25 +77,17 @@ export const Providers = () => {
const isNoResult = isFetched && !isProvider;
return (
<Box
borderRadius={4}
borderWidth={1}
borderColor={"neutralPlain"}
borderStyle={"solid"}
padding={8}
__minHeight={"320px"}
height="100%"
>
<AppCard __minHeight={"320px"} height="100%">
{isFetching && <Skeleton />}
{isNoResult && <AddProvider />}
{isResult && (
<>
<ProvidersTable />
<Box>
<Button onClick={() => router.push("/providers/new")}>Add new</Button>
<Button onClick={() => router.push("/providers")}>Add new</Button>
</Box>
</>
)}
</Box>
</AppCard>
);
};

View file

@ -4,10 +4,7 @@ import { Providers } from "../modules/ui/providers";
const Header = () => {
return (
<Box as="header" display="flex" flexDirection={"column"} gap={6}>
<Text as="h1" variant="hero">
Configuration
</Text>
<Box>
<Text as="p" variant="body" __fontWeight={"400"}>
Please configure the app by connecting one of the supported tax providers.
</Text>

View file

@ -0,0 +1,11 @@
import React from "react";
/*
* * placeholder
* // todo: add new avatax config view
*/
const NewAvataxPage = () => {
return <main>Avatax</main>;
};
export default NewAvataxPage;

View file

@ -0,0 +1,128 @@
import { Box, Button, Text } from "@saleor/macaw-ui/next";
import Image from "next/image";
import { useRouter } from "next/router";
import { AvataxIcon, StripeTaxIcon, TaxJarIcon } from "../../assets";
import { AppCard } from "../../modules/ui/app-card";
import { AppColumns } from "../../modules/ui/app-columns";
const Header = () => {
return (
<Box>
<Text __maxWidth={"360px"} __fontWeight={"400"} variant="body">
Select and configure providers to connect Saleor with selected services.
</Text>
</Box>
);
};
const Intro = () => {
return (
<Box gap={6} display="flex" flexDirection={"column"}>
<Text variant="heading" as="h3">
Choose provider
</Text>
</Box>
);
};
type ProviderProps = {
label: string;
icon: string;
description: React.ReactNode;
isComingSoon?: boolean;
};
const providerConfig = {
taxjar: {
label: "TaxJar",
icon: TaxJarIcon,
description: (
<p>
TaxJar is a cloud-based tax automation platform designed to simplify and streamline sales
tax management for online sellers.
</p>
),
},
avatax: {
label: "Avatax",
icon: AvataxIcon,
description: (
<p>
Avatax is a comprehensive tax automation software service that helps businesses calculate
and manage sales tax accurately and efficiently.
</p>
),
},
stripeTax: {
label: "Stripe Tax",
icon: StripeTaxIcon,
isComingSoon: true,
description: (
<p>
Stripe Tax lets you calculate, collect, and report tax on global payments with a single
integration.
</p>
),
},
} satisfies Record<string, ProviderProps>;
const ProviderCard = ({
label,
icon,
description,
provider,
isComingSoon,
}: ProviderProps & { provider: string }) => {
const router = useRouter();
return (
<AppCard>
<Box display={"flex"} flexDirection={"column"} gap={8}>
<Box display={"flex"} justifyContent={"space-between"}>
<Box alignItems={"center"} display={"flex"} gap={6}>
<Image src={icon} width={20} height={20} alt={`provider icon`} />
<Text variant="bodyStrong">{label}</Text>
</Box>
{isComingSoon && (
<Text
variant="body"
fontSize={"headingSmall"}
color={"textNeutralSubdued"}
textTransform={"uppercase"}
>
Coming soon
</Text>
)}
</Box>
<Text __fontWeight={"400"} variant="body" __maxWidth={"480px"}>
{description}
</Text>
</Box>
<Box display={"flex"} justifyContent={"flex-end"} marginTop={12}>
{!isComingSoon && (
<Button onClick={() => router.push(`/providers/${provider}`)}>Choose</Button>
)}
</Box>
</AppCard>
);
};
const ChooseProvider = () => {
return (
<Box gap={6} display="flex" flexDirection={"column"}>
{Object.entries(providerConfig).map(([provider, description]) => {
return <ProviderCard {...description} provider={provider} />;
})}
</Box>
);
};
const NewProviderPage = () => {
return (
<main>
<AppColumns top={<Header />} bottomLeft={<Intro />} bottomRight={<ChooseProvider />} />
</main>
);
};
export default NewProviderPage;

View file

@ -1,5 +0,0 @@
const NewProviderPage = () => {
return <div>new</div>;
};
export default NewProviderPage;

View file

@ -0,0 +1,11 @@
import React from "react";
/*
* * placeholder
* // todo: add new taxjar config view
*/
const NewTaxJarPage = () => {
return <main>TaxJar</main>;
};
export default NewTaxJarPage;