Extract shared packages (#948)
* [skip ci] tRPC shared package * [skip ci] tRPC shared package - fix * [skip ci] shared package - app sections * [skip ci] segment - implement shared components * [skip ci] extract theme synchronizer * extract components and implement them in apps * cms - extract shared packages * Fix imports * remove urql from peer deps
This commit is contained in:
parent
ceddcf96eb
commit
e8660e8bb9
78 changed files with 679 additions and 670 deletions
5
.changeset/afraid-eagles-joke.md
Normal file
5
.changeset/afraid-eagles-joke.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-cms-v2": patch
|
||||
---
|
||||
|
||||
Extracted UI components and use shared package
|
5
.changeset/afraid-wolves-shave.md
Normal file
5
.changeset/afraid-wolves-shave.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-search": patch
|
||||
---
|
||||
|
||||
Implemented shared getApBaseUrl
|
5
.changeset/dull-lobsters-relate.md
Normal file
5
.changeset/dull-lobsters-relate.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@saleor/apps-shared": minor
|
||||
---
|
||||
|
||||
Added GraphQL Provider component that build client side graphql context
|
5
.changeset/dull-mayflies-arrive.md
Normal file
5
.changeset/dull-mayflies-arrive.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-segment": patch
|
||||
---
|
||||
|
||||
Implemented ButtonsBox and SkeletonLayout from shared package. This should not have visual effect other than better looking Skeleton animation
|
6
.changeset/fast-bobcats-sip.md
Normal file
6
.changeset/fast-bobcats-sip.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"saleor-app-segment": patch
|
||||
"saleor-app-search": patch
|
||||
---
|
||||
|
||||
Replaced GraphQL provider with shared package
|
5
.changeset/flat-apples-divide.md
Normal file
5
.changeset/flat-apples-divide.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-search": patch
|
||||
---
|
||||
|
||||
Replaced AppSections implementation with the shared package
|
5
.changeset/late-cherries-sparkle.md
Normal file
5
.changeset/late-cherries-sparkle.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@saleor/apps-ui": minor
|
||||
---
|
||||
|
||||
Added two new components: ButtonsBox, which is a simple grid wrapper for horizontal buttons and SkeletonSection which composes Macaw Skeletons and make them looking more like a layout
|
5
.changeset/olive-ducks-grow.md
Normal file
5
.changeset/olive-ducks-grow.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@saleor/apps-shared": patch
|
||||
---
|
||||
|
||||
Removed ThemeProvider that was legacy for the older Macaw/Material UI
|
6
.changeset/polite-cobras-relax.md
Normal file
6
.changeset/polite-cobras-relax.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"saleor-app-segment": patch
|
||||
"saleor-app-search": patch
|
||||
---
|
||||
|
||||
Implemented ThemeSynchronizer from shared package
|
5
.changeset/sharp-ducks-peel.md
Normal file
5
.changeset/sharp-ducks-peel.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@saleor/trpc": major
|
||||
---
|
||||
|
||||
Introduced shared tRPC package. It contains reusable utils that are shared among apps
|
5
.changeset/silly-geese-shout.md
Normal file
5
.changeset/silly-geese-shout.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@saleor/apps-ui": minor
|
||||
---
|
||||
|
||||
Added Layout.AppSection and Layout.AppSectionCard components to build standard app layouts
|
5
.changeset/slow-bears-own.md
Normal file
5
.changeset/slow-bears-own.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@saleor/apps-shared": minor
|
||||
---
|
||||
|
||||
Added getAppBaseUrl function that infers apps own URL
|
5
.changeset/small-vans-rhyme.md
Normal file
5
.changeset/small-vans-rhyme.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-segment": patch
|
||||
---
|
||||
|
||||
Extracted some tRPC utilities to shared package
|
5
.changeset/tender-cycles-float.md
Normal file
5
.changeset/tender-cycles-float.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@saleor/apps-shared": minor
|
||||
---
|
||||
|
||||
Added ThemeSynchronizer component that automatically syncs AppBridge and Macaw theme
|
5
.changeset/wet-moles-admire.md
Normal file
5
.changeset/wet-moles-admire.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-search": patch
|
||||
---
|
||||
|
||||
Fixed broken configuration form when legacy metadata was fetched
|
7
.github/dependabot.yaml
vendored
7
.github/dependabot.yaml
vendored
|
@ -37,6 +37,13 @@ updates:
|
|||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "[skip ci]"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/trpc"
|
||||
open-pull-requests-limit: 1
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "[skip ci]"
|
||||
|
||||
# Apps
|
||||
- package-ecosystem: "npm"
|
||||
|
|
|
@ -6,26 +6,20 @@ import { Select } from "@saleor/react-hook-form-macaw";
|
|||
import { useRouter } from "next/router";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { ButtonsBox } from "../ui/buttons-box";
|
||||
import { ProvidersResolver } from "../providers/providers-resolver";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
|
||||
|
||||
const FormSchema = z.object({
|
||||
connectionId: z.string().min(7),
|
||||
});
|
||||
|
||||
const EmptyState = () => (
|
||||
<Box
|
||||
display="flex"
|
||||
paddingY={4}
|
||||
flexDirection={"column"}
|
||||
gap={4}
|
||||
alignItems={"center"}
|
||||
justifyContent={"center"}
|
||||
>
|
||||
<Text variant="heading">No connections configured</Text>
|
||||
<Layout.AppSectionCard>
|
||||
<Box display="flex" flexDirection={"column"} gap={4} justifyContent={"center"}>
|
||||
<Text variant="heading">Bulk products synchronization</Text>
|
||||
<Text>Create a channel connection above to enable bulk synchronization.</Text>
|
||||
</Box>
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
|
||||
export const BulkSyncSection = () => {
|
||||
|
@ -42,7 +36,7 @@ export const BulkSyncSection = () => {
|
|||
});
|
||||
|
||||
if (!connections || !providers) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
if (connections.length === 0) {
|
||||
|
@ -50,7 +44,7 @@ export const BulkSyncSection = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Layout.AppSectionCard>
|
||||
<Text as="h2" marginBottom={6} variant="heading">
|
||||
Bulk products synchronization
|
||||
</Text>
|
||||
|
@ -88,6 +82,6 @@ export const BulkSyncSection = () => {
|
|||
<Button type="submit">Start sync</Button>
|
||||
</ButtonsBox>
|
||||
</Box>
|
||||
</Box>
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { Breadcrumbs } from "@saleor/apps-ui";
|
||||
import { Breadcrumbs, ButtonsBox, Layout } from "@saleor/apps-ui";
|
||||
import { ArrowRightIcon, Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ChannelProviderConnectionConfig, ProvidersConfig } from "../configuration";
|
||||
import { AppHeader } from "../ui/app-header";
|
||||
import { AppSection } from "../ui/app-section";
|
||||
import { ButtonsBox } from "../ui/buttons-box";
|
||||
|
||||
import { useBulkSyncProductsState } from "./use-bulk-sync-products-state";
|
||||
import { useFetchAllProducts } from "./use-fetch-all-products";
|
||||
|
@ -14,16 +12,19 @@ import { useDashboardNotification } from "@saleor/apps-shared";
|
|||
|
||||
const FetchProductsStep = (props: { onButtonClick(): void }) => {
|
||||
return (
|
||||
<Box>
|
||||
<Layout.AppSectionCard
|
||||
footer={
|
||||
<ButtonsBox>
|
||||
<Button onClick={props.onButtonClick}>Prefetch products</Button>
|
||||
</ButtonsBox>
|
||||
}
|
||||
>
|
||||
<Text variant="heading" as="h2" marginBottom={4}>
|
||||
Saleor products fetch
|
||||
</Text>
|
||||
<Text as="p">Click the button to start fetching products from Saleor API</Text>
|
||||
<Text as="p">After products are fetched, you will be able to upload them to the CMS</Text>
|
||||
<ButtonsBox>
|
||||
<Button onClick={props.onButtonClick}>Prefetch products</Button>
|
||||
</ButtonsBox>
|
||||
</Box>
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -66,7 +67,7 @@ export const BulkSyncView = ({
|
|||
|
||||
const { products, finished: saleorProductsFetchFinished } = useFetchAllProducts(
|
||||
state === "fetching",
|
||||
connection.channelSlug
|
||||
connection.channelSlug,
|
||||
);
|
||||
|
||||
const { productsStatusList, setInitialProducts, setItemStatus, finished } =
|
||||
|
@ -121,9 +122,14 @@ export const BulkSyncView = ({
|
|||
]}
|
||||
/>
|
||||
|
||||
<AppSection
|
||||
<Layout.AppSection
|
||||
marginBottom={8}
|
||||
mainContent={(() => {
|
||||
heading="1. Fetch products"
|
||||
sideContent={
|
||||
<Text>First pre-fetch all Product Variants from Saleor. Do not close the app.</Text>
|
||||
}
|
||||
>
|
||||
{(() => {
|
||||
switch (state) {
|
||||
case "initial": {
|
||||
return (
|
||||
|
@ -149,18 +155,15 @@ export const BulkSyncView = ({
|
|||
}
|
||||
}
|
||||
})()}
|
||||
heading="1. Fetch products"
|
||||
sideContent={
|
||||
<Text>First pre-fetch all Product Variants from Saleor. Do not close the app.</Text>
|
||||
}
|
||||
/>
|
||||
</Layout.AppSection>
|
||||
|
||||
{(state === "fetched" || state === "uploading") && productsStatusList && (
|
||||
<AppSection
|
||||
<Layout.AppSection
|
||||
marginTop={14}
|
||||
heading="2. Upload to the CMS"
|
||||
sideContent={<Text>Send listed variants to the CMS</Text>}
|
||||
mainContent={
|
||||
<Box>
|
||||
>
|
||||
<Layout.AppSectionCard>
|
||||
<Text as="h2" marginBottom={4} variant="heading">
|
||||
Upload products
|
||||
</Text>
|
||||
|
@ -173,9 +176,8 @@ export const BulkSyncView = ({
|
|||
</Box>
|
||||
)}
|
||||
<VariantsSyncStatusList marginTop={8} variants={productsStatusList} />
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Layout.AppSectionCard>
|
||||
</Layout.AppSection>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Button, Text } from "@saleor/macaw-ui/next";
|
||||
import { ButtonsBox } from "../ui/buttons-box";
|
||||
import { Modal } from "../ui/modal";
|
||||
import {
|
||||
AddConnectionForm,
|
||||
|
@ -7,7 +6,7 @@ import {
|
|||
AddConnectionFormSchema,
|
||||
} from "./add-connection-form";
|
||||
import { trpcClient } from "../trpc/trpc-client";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import { ButtonsBox, SkeletonLayout } from "@saleor/apps-ui";
|
||||
|
||||
const defaultValues: AddConnectionFormSchema = { channelSlug: "", providerId: "" };
|
||||
|
||||
|
@ -15,7 +14,7 @@ export const AddConnectionModal = (props: { onSuccess(): void; onClose(): void }
|
|||
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
|
||||
|
||||
if (!providers) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
const { mutateAsync: addProviderMutate, isLoading } =
|
||||
|
|
|
@ -1,35 +1,31 @@
|
|||
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
|
||||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||
import { useState } from "react";
|
||||
import { trpcClient } from "../trpc/trpc-client";
|
||||
import { ButtonsBox } from "../ui/buttons-box";
|
||||
import { AddConnectionFormSchema } from "./add-connection-form";
|
||||
import { AddConnectionModal } from "./add-connection-modal";
|
||||
import { ChanelProviderConnectionsSectionHeader } from "./channel-provider-connections-section-header";
|
||||
import { ConnectionsList } from "./connections-list";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
|
||||
const NoConnections = (props: { onCreate(): void; enabled: boolean }) => (
|
||||
<Box>
|
||||
<ChanelProviderConnectionsSectionHeader />
|
||||
<Text marginBottom={4} as="p">
|
||||
<Text as="p">
|
||||
No channels connected yet.{" "}
|
||||
{!props.enabled &&
|
||||
"Ensure you have created a provider configuration that can be connected first."}
|
||||
</Text>
|
||||
{props.enabled && (
|
||||
<ButtonsBox>
|
||||
<Button onClick={props.onCreate}>Create first connection</Button>
|
||||
</ButtonsBox>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const ChannelProviderConnectionList = () => {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
|
||||
const { data: connectionsData, refetch: refetchConnections } =
|
||||
trpcClient.channelsProvidersConnection.fetchConnections.useQuery();
|
||||
const {
|
||||
data: connectionsData,
|
||||
refetch: refetchConnections,
|
||||
isLoading,
|
||||
} = trpcClient.channelsProvidersConnection.fetchConnections.useQuery();
|
||||
|
||||
const { mutate: removeConnection } =
|
||||
trpcClient.channelsProvidersConnection.removeConnection.useMutation({
|
||||
|
@ -45,19 +41,33 @@ export const ChannelProviderConnectionList = () => {
|
|||
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
|
||||
|
||||
if (!providers) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
const handleDelete = (connectionId: string) => {
|
||||
removeConnection({ id: connectionId });
|
||||
};
|
||||
|
||||
if (!connectionsData) {
|
||||
return <Text>Loading</Text>;
|
||||
if (isLoading || !connectionsData) {
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Layout.AppSectionCard
|
||||
footer={
|
||||
providers.length > 0 && (
|
||||
<ButtonsBox>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
Add connection
|
||||
</Button>
|
||||
</ButtonsBox>
|
||||
)
|
||||
}
|
||||
>
|
||||
{dialogOpen && (
|
||||
<AddConnectionModal
|
||||
onClose={() => {
|
||||
|
@ -79,17 +89,6 @@ export const ChannelProviderConnectionList = () => {
|
|||
/>
|
||||
)}
|
||||
{connectionsData.length > 0 && <ConnectionsList onRemove={handleDelete} />}
|
||||
{connectionsData.length > 0 && (
|
||||
<ButtonsBox marginTop={6}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
Add connection
|
||||
</Button>
|
||||
</ButtonsBox>
|
||||
)}
|
||||
</Box>
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import React from "react";
|
|||
import { trpcClient } from "../trpc/trpc-client";
|
||||
import { ChanelProviderConnectionsSectionHeader } from "./channel-provider-connections-section-header";
|
||||
import { ProvidersResolver } from "../providers/providers-resolver";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import { SkeletonLayout } from "@saleor/apps-ui";
|
||||
|
||||
export const ConnectionsList = (props: { onRemove(connectionId: string): void }) => {
|
||||
const { data } = trpcClient.channelsProvidersConnection.fetchConnections.useQuery();
|
||||
|
@ -12,7 +12,7 @@ export const ConnectionsList = (props: { onRemove(connectionId: string): void })
|
|||
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
|
||||
|
||||
if (!data || !providers) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -5,8 +5,8 @@ import { ProvidersConfig } from "../configuration";
|
|||
|
||||
import { ProvidersResolver } from "../providers/providers-resolver";
|
||||
import { trpcClient } from "../trpc/trpc-client";
|
||||
import { ButtonsBox } from "../ui/buttons-box";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
|
||||
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
|
||||
|
||||
const ProvidersTable = (props: { providers: ProvidersConfig.AnyFullShape[] }) => {
|
||||
const { push } = useRouter();
|
||||
|
@ -47,15 +47,13 @@ export const ProvidersList = () => {
|
|||
const { push } = useRouter();
|
||||
|
||||
if (!data) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box>
|
||||
<Text as="p" marginBottom={4}>
|
||||
No configurations yet
|
||||
</Text>
|
||||
<Layout.AppSectionCard
|
||||
footer={
|
||||
<ButtonsBox>
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
@ -65,21 +63,19 @@ export const ProvidersList = () => {
|
|||
Add first CMS configuration
|
||||
</Button>
|
||||
</ButtonsBox>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Text as="p" marginBottom={4}>
|
||||
No configurations yet
|
||||
</Text>
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{data.length && (
|
||||
<Box>
|
||||
<Text variant="heading" as="h2" marginBottom={4}>
|
||||
Providers configurations
|
||||
</Text>
|
||||
<ProvidersTable providers={data} />
|
||||
</Box>
|
||||
)}
|
||||
<ButtonsBox marginTop={8}>
|
||||
<Layout.AppSectionCard
|
||||
footer={
|
||||
<ButtonsBox>
|
||||
<Button
|
||||
onClick={() => {
|
||||
push("/add-provider");
|
||||
|
@ -88,6 +84,16 @@ export const ProvidersList = () => {
|
|||
Add CMS configuration
|
||||
</Button>
|
||||
</ButtonsBox>
|
||||
}
|
||||
>
|
||||
{data.length && (
|
||||
<Box>
|
||||
<Text variant="heading" as="h2" marginBottom={4}>
|
||||
Providers configurations
|
||||
</Text>
|
||||
<ProvidersTable providers={data} />
|
||||
</Box>
|
||||
)}
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,9 +7,7 @@ import { useForm } from "react-hook-form";
|
|||
import { BuilderIoProviderConfig, SaleorProviderFieldsMappingKeys } from "../../configuration";
|
||||
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { ButtonsBox } from "../../ui/buttons-box";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
import { Skeleton } from "@/modules/ui/skeleton";
|
||||
import { ButtonsBox, SkeletonLayout, TextLink } from "@saleor/apps-ui";
|
||||
|
||||
type FormShape = Omit<BuilderIoProviderConfig.InputShape, "type">;
|
||||
const FormSchema = BuilderIoProviderConfig.Schema.Input.omit({ type: true });
|
||||
|
@ -207,7 +205,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
|||
});
|
||||
|
||||
if (!data) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
if (data.type !== "builder.io") {
|
||||
|
|
|
@ -9,8 +9,7 @@ import { useDashboardNotification } from "@saleor/apps-shared";
|
|||
import { ContentfulProviderConfig } from "../../configuration/schemas/contentful-provider.schema";
|
||||
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ButtonsBox } from "../../ui/buttons-box";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
import { ButtonsBox, TextLink } from "@saleor/apps-ui";
|
||||
import { SaleorProviderFieldsMappingKeys } from "@/modules/configuration";
|
||||
|
||||
type FormSchema = Omit<ContentfulProviderConfig.InputShape, "type">;
|
||||
|
|
|
@ -8,8 +8,8 @@ import React, { useEffect, useMemo } from "react";
|
|||
import { useForm } from "react-hook-form";
|
||||
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { ButtonsBox } from "../../ui/buttons-box";
|
||||
import { DatocmsProviderConfig } from "@/modules/configuration/schemas/datocms-provider.schema";
|
||||
import { ButtonsBox } from "@saleor/apps-ui";
|
||||
|
||||
type FormShape = Omit<DatocmsProviderConfig.InputShape, "type">;
|
||||
|
||||
|
|
|
@ -9,8 +9,7 @@ import React from "react";
|
|||
import { useForm } from "react-hook-form";
|
||||
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { ButtonsBox } from "../../ui/buttons-box";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
import { ButtonsBox, TextLink } from "@saleor/apps-ui";
|
||||
|
||||
type FormShape = Omit<PayloadCmsProviderConfig.InputShape, "type">;
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@ import { useForm } from "react-hook-form";
|
|||
import { SaleorProviderFieldsMappingKeys, StrapiProviderConfig } from "../../configuration";
|
||||
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { ButtonsBox } from "../../ui/buttons-box";
|
||||
import { Skeleton } from "@/modules/ui/skeleton";
|
||||
import { ButtonsBox, SkeletonLayout } from "@saleor/apps-ui";
|
||||
|
||||
type FormShape = Omit<StrapiProviderConfig.InputShape, "type">;
|
||||
|
||||
|
@ -189,7 +188,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
|||
});
|
||||
|
||||
if (!data) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
if (data.type !== "strapi") {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as trpcNext from "@trpc/server/adapters/next";
|
||||
import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_API_URL_HEADER } from "@saleor/app-sdk/const";
|
||||
import { inferAsyncReturnType } from "@trpc/server";
|
||||
import { getBaseUrl } from "@/modules/shared/get-base-url";
|
||||
import { getAppBaseUrl } from "@saleor/apps-shared";
|
||||
|
||||
export const createTrpcContext = async ({ res, req }: trpcNext.CreateNextContextOptions) => {
|
||||
const baseUrl = getBaseUrl(req.headers);
|
||||
const baseUrl = getAppBaseUrl(req.headers);
|
||||
|
||||
return {
|
||||
token: req.headers[SALEOR_AUTHORIZATION_BEARER_HEADER] as string | undefined,
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import { Box, PropsWithBox, Text } from "@saleor/macaw-ui/next";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
// todo move to shared
|
||||
export const AppSection = ({
|
||||
heading,
|
||||
sideContent,
|
||||
mainContent,
|
||||
includePadding = true,
|
||||
...props
|
||||
}: PropsWithBox<{
|
||||
heading: string;
|
||||
sideContent?: ReactNode;
|
||||
mainContent: ReactNode;
|
||||
includePadding?: boolean;
|
||||
}>) => {
|
||||
return (
|
||||
<Box
|
||||
as="section"
|
||||
__gridTemplateColumns={"400px auto"}
|
||||
display={"grid"}
|
||||
gap={10}
|
||||
__maxWidth={"1200px"}
|
||||
{...props}
|
||||
>
|
||||
<Box>
|
||||
<Text as="h2" variant={"heading"} size={"large"} marginBottom={1.5}>
|
||||
{heading}
|
||||
</Text>
|
||||
{sideContent}
|
||||
</Box>
|
||||
<Box
|
||||
borderStyle={"solid"}
|
||||
borderColor={"neutralPlain"}
|
||||
borderWidth={1}
|
||||
padding={includePadding ? 5 : 0}
|
||||
borderRadius={4}
|
||||
>
|
||||
{mainContent}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
import { Box, BoxProps } from "@saleor/macaw-ui/next";
|
||||
|
||||
// TODO: Make it more generic, move to shared or contribute to macaw
|
||||
const Section = (props: BoxProps) => {
|
||||
return (
|
||||
<Box display="grid" gap={2} {...props}>
|
||||
<Box
|
||||
__height="10px"
|
||||
backgroundColor="surfaceNeutralHighlight"
|
||||
borderRadius={2}
|
||||
__width="50%"
|
||||
/>
|
||||
<Box
|
||||
__height="10px"
|
||||
backgroundColor="surfaceNeutralHighlight"
|
||||
borderRadius={2}
|
||||
__width="70%"
|
||||
/>
|
||||
<Box
|
||||
__height="10px"
|
||||
backgroundColor="surfaceNeutralHighlight"
|
||||
borderRadius={2}
|
||||
__width="60%"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Skeleton = { Section };
|
|
@ -1,7 +1,6 @@
|
|||
import { CMSProviders } from "@/modules/providers/providers-registry";
|
||||
import { AppHeader } from "@/modules/ui/app-header";
|
||||
import { AppSection } from "@/modules/ui/app-section";
|
||||
import { Breadcrumbs } from "@saleor/apps-ui";
|
||||
import { Breadcrumbs, Layout } from "@saleor/apps-ui";
|
||||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||
import { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
|
@ -16,14 +15,15 @@ const AddProviderPage: NextPage = () => {
|
|||
text="Connect CMS platforms to the App."
|
||||
breadcrumbs={[<Breadcrumbs.Item key="provider">Add Provider</Breadcrumbs.Item>]}
|
||||
/>
|
||||
<AppSection
|
||||
<Layout.AppSection
|
||||
heading="Select CMS provider"
|
||||
sideContent={
|
||||
<Box>
|
||||
<Text>App allows to connect one or more CMS platforms. You can add more later.</Text>
|
||||
</Box>
|
||||
}
|
||||
mainContent={
|
||||
>
|
||||
<Layout.AppSectionCard>
|
||||
<Box
|
||||
display="grid"
|
||||
__gridTemplateColumns="auto auto auto"
|
||||
|
@ -54,8 +54,8 @@ const AddProviderPage: NextPage = () => {
|
|||
</React.Fragment>
|
||||
))}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Layout.AppSectionCard>
|
||||
</Layout.AppSection>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,8 +2,7 @@ import { CMSType } from "@/modules/providers/providers-registry";
|
|||
import { ProvidersResolver } from "@/modules/providers/providers-resolver";
|
||||
|
||||
import { AppHeader } from "@/modules/ui/app-header";
|
||||
import { AppSection } from "@/modules/ui/app-section";
|
||||
import { Breadcrumbs } from "@saleor/apps-ui";
|
||||
import { Breadcrumbs, Layout } from "@saleor/apps-ui";
|
||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||
import { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
|
@ -32,7 +31,7 @@ const AddProviderPage: NextPage = () => {
|
|||
]}
|
||||
/>
|
||||
|
||||
<AppSection
|
||||
<Layout.AppSection
|
||||
heading={`Set up ${provider.displayName}`}
|
||||
sideContent={
|
||||
<Box>
|
||||
|
@ -40,8 +39,11 @@ const AddProviderPage: NextPage = () => {
|
|||
{provider.formSideInfo && <Box marginTop={6}>{provider.formSideInfo}</Box>}
|
||||
</Box>
|
||||
}
|
||||
mainContent={<FormComponent />}
|
||||
/>
|
||||
>
|
||||
<Layout.AppSectionCard>
|
||||
<FormComponent />
|
||||
</Layout.AppSectionCard>
|
||||
</Layout.AppSection>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { BulkSyncView } from "@/modules/bulk-sync/bulk-sync-view";
|
||||
import { trpcClient } from "@/modules/trpc/trpc-client";
|
||||
import { SkeletonLayout } from "@saleor/apps-ui";
|
||||
import { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { z } from "zod";
|
||||
import { Text } from "@saleor/macaw-ui/next";
|
||||
import { Skeleton } from "@/modules/ui/skeleton";
|
||||
|
||||
const BulkSyncPage: NextPage = () => {
|
||||
const { query } = useRouter();
|
||||
|
@ -23,7 +22,7 @@ const BulkSyncPage: NextPage = () => {
|
|||
},
|
||||
{
|
||||
enabled: !!parsedID,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
|
@ -36,7 +35,7 @@ const BulkSyncPage: NextPage = () => {
|
|||
},
|
||||
{
|
||||
enabled: !!connection,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if ((providerFetched && !provider) || (connectionFetched && !connection)) {
|
||||
|
@ -45,7 +44,7 @@ const BulkSyncPage: NextPage = () => {
|
|||
}
|
||||
|
||||
if (connectionLoading || providerLoading) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
if (!(provider && connection)) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { BulkSyncSection } from "@/modules/bulk-sync/bulk-sync-section";
|
|||
import { ChannelProviderConnectionList } from "@/modules/channel-provider-connection/channels-provider-connection-list";
|
||||
import { ProvidersList } from "@/modules/providers-listing/providers-list";
|
||||
import { AppHeader } from "@/modules/ui/app-header";
|
||||
import { AppSection } from "@/modules/ui/app-section";
|
||||
import { Layout } from "@saleor/apps-ui";
|
||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||
import { NextPage } from "next";
|
||||
|
||||
|
@ -10,7 +10,7 @@ const ConfigurationPage: NextPage = () => {
|
|||
return (
|
||||
<Box>
|
||||
<AppHeader />
|
||||
<AppSection
|
||||
<Layout.AppSection
|
||||
marginBottom={14}
|
||||
heading="Providers configuration"
|
||||
sideContent={
|
||||
|
@ -18,9 +18,10 @@ const ConfigurationPage: NextPage = () => {
|
|||
<Text>Configure one or more CMS providers to synchronize Saleor products.</Text>
|
||||
</Box>
|
||||
}
|
||||
mainContent={<ProvidersList />}
|
||||
/>
|
||||
<AppSection
|
||||
>
|
||||
<ProvidersList />
|
||||
</Layout.AppSection>
|
||||
<Layout.AppSection
|
||||
marginBottom={14}
|
||||
heading="Automatic synchronization"
|
||||
sideContent={
|
||||
|
@ -31,9 +32,10 @@ const ConfigurationPage: NextPage = () => {
|
|||
</Text>
|
||||
</Box>
|
||||
}
|
||||
mainContent={<ChannelProviderConnectionList />}
|
||||
/>
|
||||
<AppSection
|
||||
>
|
||||
<ChannelProviderConnectionList />
|
||||
</Layout.AppSection>
|
||||
<Layout.AppSection
|
||||
heading="Initial sync"
|
||||
sideContent={
|
||||
<Box>
|
||||
|
@ -44,8 +46,9 @@ const ConfigurationPage: NextPage = () => {
|
|||
<Text as="p">Its recommended to run this flow initially, once app is configured.</Text>
|
||||
</Box>
|
||||
}
|
||||
mainContent={<BulkSyncSection />}
|
||||
/>
|
||||
>
|
||||
<BulkSyncSection />
|
||||
</Layout.AppSection>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,9 +2,7 @@ import { ProvidersResolver } from "@/modules/providers/providers-resolver";
|
|||
|
||||
import { trpcClient } from "@/modules/trpc/trpc-client";
|
||||
import { AppHeader } from "@/modules/ui/app-header";
|
||||
import { AppSection } from "@/modules/ui/app-section";
|
||||
import { Skeleton } from "@/modules/ui/skeleton";
|
||||
import { Breadcrumbs } from "@saleor/apps-ui";
|
||||
import { Breadcrumbs, Layout, SkeletonLayout } from "@saleor/apps-ui";
|
||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||
import { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
|
@ -20,7 +18,7 @@ const EditProviderPage: NextPage = () => {
|
|||
},
|
||||
{
|
||||
enabled: !!configId,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const provider = useMemo(() => {
|
||||
|
@ -28,7 +26,7 @@ const EditProviderPage: NextPage = () => {
|
|||
}, [data]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
if (isFetched && !data) {
|
||||
|
@ -38,7 +36,7 @@ const EditProviderPage: NextPage = () => {
|
|||
}
|
||||
|
||||
if (!provider) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
const EditForm = ProvidersResolver.getEditProviderFormComponent(provider.type);
|
||||
|
@ -53,13 +51,16 @@ const EditProviderPage: NextPage = () => {
|
|||
<Breadcrumbs.Item key="configname">{data?.configName}</Breadcrumbs.Item>,
|
||||
]}
|
||||
/>
|
||||
<AppSection
|
||||
<Layout.AppSection
|
||||
heading="Edit CMS configuration"
|
||||
mainContent={<EditForm configId={configId} />}
|
||||
sideContent={
|
||||
<Box>{provider.formSideInfo && <Box marginTop={6}>{provider.formSideInfo}</Box>}</Box>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<Layout.AppSectionCard>
|
||||
<EditForm configId={configId} />
|
||||
</Layout.AppSectionCard>
|
||||
</Layout.AppSection>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from "../modules/configuration/configuration";
|
||||
import { AlgoliaSearchProvider } from "../lib/algolia/algoliaSearchProvider";
|
||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
||||
import { Layout } from "@saleor/apps-ui";
|
||||
|
||||
export const AlgoliaConfigurationForm = () => {
|
||||
const { notifyError, notifySuccess } = useDashboardNotification();
|
||||
|
@ -76,20 +77,26 @@ export const AlgoliaConfigurationForm = () => {
|
|||
const isFormDisabled = isMutationLoading || isQueryLoading;
|
||||
|
||||
return (
|
||||
<Layout.AppSectionCard
|
||||
as="form"
|
||||
onSubmit={onFormSubmit}
|
||||
footer={
|
||||
<Box display={"flex"} justifyContent={"flex-end"}>
|
||||
<Button disabled={isFormDisabled} type="submit" variant="primary">
|
||||
{isFormDisabled ? "Loading..." : "Save"}
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box>
|
||||
<form onSubmit={onFormSubmit}>
|
||||
<Box padding={5}>
|
||||
<Box marginBottom={5}>
|
||||
<Input
|
||||
control={control}
|
||||
name="appId"
|
||||
disabled={isFormDisabled}
|
||||
required
|
||||
label="Application ID"
|
||||
/* cspell:disable-next-line */
|
||||
helperText="Usually 10 characters, e.g. XYZAAABB00"
|
||||
/>
|
||||
</Box>
|
||||
<Box marginBottom={5} key={"secret"} /* todo why is this "key" here? */>
|
||||
<Input
|
||||
control={control}
|
||||
|
@ -117,15 +124,6 @@ export const AlgoliaConfigurationForm = () => {
|
|||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Divider margin={0} marginTop={5} />
|
||||
|
||||
<Box paddingX={5} paddingY={3} display={"flex"} justifyContent={"flex-end"}>
|
||||
<Button disabled={isFormDisabled} type="submit" variant="primary">
|
||||
{isFormDisabled ? "Loading..." : "Save"}
|
||||
</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useEffect } from "react";
|
||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||
import { ButtonsBox, Layout } from "@saleor/apps-ui";
|
||||
|
||||
export const AlgoliaFieldsSelectionForm = () => {
|
||||
const { notifySuccess } = useDashboardNotification();
|
||||
|
@ -35,8 +36,8 @@ export const AlgoliaFieldsSelectionForm = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<form
|
||||
<Layout.AppSectionCard
|
||||
as="form"
|
||||
onSubmit={handleSubmit((values) => {
|
||||
const selectedValues = Object.entries(values)
|
||||
.filter(([key, selected]) => selected)
|
||||
|
@ -46,8 +47,13 @@ export const AlgoliaFieldsSelectionForm = () => {
|
|||
enabledAlgoliaFields: selectedValues,
|
||||
});
|
||||
})}
|
||||
footer={
|
||||
<ButtonsBox>
|
||||
<Button type="submit">Save</Button>
|
||||
</ButtonsBox>
|
||||
}
|
||||
>
|
||||
<Box padding={5}>
|
||||
<Box>
|
||||
{AlgoliaRootFieldsKeys.map((field) => (
|
||||
<Box key={field} marginBottom={5}>
|
||||
<Controller
|
||||
|
@ -70,11 +76,6 @@ export const AlgoliaFieldsSelectionForm = () => {
|
|||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
<Divider margin={0} marginTop={5} />
|
||||
<Box padding={5} display="flex" justifyContent="flex-end">
|
||||
<Button type="submit">Save</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</Box>
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import { Box, PropsWithBox, Text } from "@saleor/macaw-ui/next";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
// todo move to shared
|
||||
export const AppSection = ({
|
||||
heading,
|
||||
sideContent,
|
||||
mainContent,
|
||||
includePadding = false,
|
||||
...props
|
||||
}: PropsWithBox<{
|
||||
heading: string;
|
||||
sideContent?: ReactNode;
|
||||
mainContent: ReactNode;
|
||||
includePadding?: boolean;
|
||||
}>) => {
|
||||
return (
|
||||
<Box as="section" __gridTemplateColumns={"400px auto"} display={"grid"} gap={10} {...props}>
|
||||
<Box>
|
||||
<Text as="h2" variant={"heading"} size={"large"} marginBottom={1.5}>
|
||||
{heading}
|
||||
</Text>
|
||||
{sideContent}
|
||||
</Box>
|
||||
<Box
|
||||
borderStyle={"solid"}
|
||||
borderColor={"neutralPlain"}
|
||||
borderWidth={1}
|
||||
padding={includePadding ? 5 : 0}
|
||||
borderRadius={4}
|
||||
>
|
||||
{mainContent}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|||
import { AlgoliaSearchProvider } from "../lib/algolia/algoliaSearchProvider";
|
||||
import { Products, useQueryAllProducts } from "./useQueryAllProducts";
|
||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
||||
import { Layout } from "@saleor/apps-ui";
|
||||
|
||||
const BATCH_SIZE = 100;
|
||||
|
||||
|
@ -63,7 +64,19 @@ export const ImportProductsToAlgolia = () => {
|
|||
}, [searchProvider, currentProductIndex, isAlgoliaImporting, products]);
|
||||
|
||||
return (
|
||||
<Box __cursor={started ? "wait" : "auto"}>
|
||||
<Layout.AppSectionCard
|
||||
footer={
|
||||
searchProvider &&
|
||||
algoliaConfigured && (
|
||||
<Box display={"flex"} justifyContent={"flex-end"}>
|
||||
<Button disabled={started || !searchProvider} onClick={importProducts}>
|
||||
Start importing
|
||||
</Button>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
__cursor={started ? "wait" : "auto"}
|
||||
>
|
||||
{searchProvider && algoliaConfigured ? (
|
||||
<Box>
|
||||
<Text variant={"heading"} as={"p"} marginBottom={1.5}>
|
||||
|
@ -75,11 +88,6 @@ export const ImportProductsToAlgolia = () => {
|
|||
<Text marginBottom={5} variant={"bodyStrong"}>
|
||||
Do not close the app - its running client-side
|
||||
</Text>
|
||||
<Box display={"flex"} justifyContent={"flex-end"}>
|
||||
<Button disabled={started || !searchProvider} onClick={importProducts}>
|
||||
Start importing
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<Box>
|
||||
|
@ -112,7 +120,7 @@ export const ImportProductsToAlgolia = () => {
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
import { Layout, TextLink } from "@saleor/apps-ui";
|
||||
import { useIndicesSetupMutation } from "../lib/useIndicesSetup";
|
||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
||||
|
||||
|
@ -12,7 +12,19 @@ export const IndicesSettings = () => {
|
|||
algoliaConfiguration?.appConfig?.appId && algoliaConfiguration?.appConfig?.secretKey;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Layout.AppSectionCard
|
||||
footer={
|
||||
<Box display={"flex"} justifyContent={"flex-end"}>
|
||||
<Button
|
||||
disabled={!isConfigured}
|
||||
onClick={() => updateWebhooksMutation.mutate()}
|
||||
variant="primary"
|
||||
>
|
||||
Update indices configuration
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box>
|
||||
<Text variant={"heading"} as={"p"} marginBottom={1.5}>
|
||||
Performing this operation will update indices to use recommended settings:
|
||||
|
@ -60,16 +72,7 @@ export const IndicesSettings = () => {
|
|||
Please note - if indices are already configured, this operation will overwrite settings
|
||||
mentioned above.
|
||||
</Text>
|
||||
<Box display={"flex"} justifyContent={"flex-end"}>
|
||||
<Button
|
||||
disabled={!isConfigured}
|
||||
onClick={() => updateWebhooksMutation.mutate()}
|
||||
variant="primary"
|
||||
>
|
||||
Update indices configuration
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,5 +2,6 @@ import debugPkg from "debug";
|
|||
|
||||
/**
|
||||
* todo rewrite to pino logger
|
||||
* @deprecated
|
||||
*/
|
||||
export const createDebug = (namespace: string) => debugPkg.debug(`app-search:${namespace}`);
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
/**
|
||||
* Extracts the app's url from headers from the response.
|
||||
*/
|
||||
export const getBaseUrl = (headers: { [name: string]: string | string[] | undefined }): string => {
|
||||
const { host, "x-forwarded-proto": xForwardedProto = "http" } = headers;
|
||||
|
||||
const xForwardedProtos = Array.isArray(xForwardedProto)
|
||||
? xForwardedProto.join(",")
|
||||
: xForwardedProto;
|
||||
const protocols = xForwardedProtos.split(",");
|
||||
// prefer https over other protocols
|
||||
const protocol = protocols.find((el) => el === "https") || protocols[0];
|
||||
|
||||
return `${protocol}://${host}`;
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||
import { useTheme } from "@saleor/macaw-ui/next";
|
||||
import { useEffect } from "react";
|
||||
|
||||
// todo move to shared
|
||||
export function ThemeSynchronizer() {
|
||||
const { appBridgeState } = useAppBridge();
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (!setTheme || !appBridgeState?.theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (appBridgeState.theme === "light") {
|
||||
setTheme("defaultLight");
|
||||
}
|
||||
|
||||
if (appBridgeState.theme === "dark") {
|
||||
setTheme("defaultDark");
|
||||
}
|
||||
}, [appBridgeState?.theme, setTheme]);
|
||||
|
||||
return null;
|
||||
}
|
|
@ -34,7 +34,9 @@ export const configurationRouter = router({
|
|||
*/
|
||||
const data = await fetchLegacyConfiguration(settingsManager, domain);
|
||||
|
||||
if (data) {
|
||||
config.setAlgoliaSettings(data);
|
||||
}
|
||||
|
||||
return config.getConfig();
|
||||
}
|
||||
|
|
|
@ -7,12 +7,18 @@ import { AppConfigurationFields } from "./configuration";
|
|||
export const fetchLegacyConfiguration = async (
|
||||
settingsManager: SettingsManager,
|
||||
domain: string,
|
||||
) => {
|
||||
const data: AppConfigurationFields = {
|
||||
secretKey: (await settingsManager.get("secretKey", domain)) || "",
|
||||
appId: (await settingsManager.get("appId", domain)) || "",
|
||||
indexNamePrefix: (await settingsManager.get("indexNamePrefix", domain)) || "",
|
||||
};
|
||||
): Promise<AppConfigurationFields | null> => {
|
||||
const secretKey = await settingsManager.get("secretKey", domain);
|
||||
const appId = await settingsManager.get("appId", domain);
|
||||
const indexNamePrefix = await settingsManager.get("indexNamePrefix", domain);
|
||||
|
||||
return data;
|
||||
if (secretKey && appId) {
|
||||
return {
|
||||
appId,
|
||||
secretKey,
|
||||
indexNamePrefix,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -3,12 +3,10 @@ import "@saleor/macaw-ui/next/style";
|
|||
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
||||
import React from "react";
|
||||
import { AppProps } from "next/app";
|
||||
import { GraphQLProvider } from "../providers/GraphQLProvider";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
||||
import { ThemeSynchronizer } from "../lib/theme-synchronizer";
|
||||
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
|
||||
import { NoSSRWrapper } from "@saleor/apps-shared";
|
||||
import { GraphQLProvider, NoSSRWrapper, ThemeSynchronizer } from "@saleor/apps-shared";
|
||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,9 +7,8 @@ import {
|
|||
} from "../../domain/WebhookActivityToggler.service";
|
||||
import { createLogger } from "../../lib/logger";
|
||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||
import { createGraphQLClient } from "@saleor/apps-shared";
|
||||
import { createGraphQLClient, getAppBaseUrl } from "@saleor/apps-shared";
|
||||
import { Client } from "urql";
|
||||
import { getBaseUrl } from "../../lib/getBaseUrl";
|
||||
import { isConfigured } from "../../lib/algolia/is-configured";
|
||||
|
||||
const logger = createLogger({
|
||||
|
@ -53,7 +52,7 @@ export const recreateWebhooksHandlerFactory =
|
|||
|
||||
logger.debug(settings, "fetched settings");
|
||||
|
||||
const baseUrl = getBaseUrl(req.headers);
|
||||
const baseUrl = getAppBaseUrl(req.headers);
|
||||
const enableWebhooks = isConfigured({
|
||||
configuration: {
|
||||
appId: appId,
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { Provider } from "urql";
|
||||
|
||||
import { createGraphQLClient } from "@saleor/apps-shared";
|
||||
|
||||
export function GraphQLProvider(props: PropsWithChildren<{}>) {
|
||||
const { appBridgeState } = useAppBridge();
|
||||
const saleorApiUrl = appBridgeState?.saleorApiUrl!;
|
||||
|
||||
if (!appBridgeState?.saleorApiUrl) {
|
||||
return <div {...props}></div>;
|
||||
}
|
||||
|
||||
const client = createGraphQLClient({
|
||||
saleorApiUrl,
|
||||
token: appBridgeState.token,
|
||||
});
|
||||
|
||||
return <Provider value={client} {...props} />;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||
import { AppSection } from "../../components/AppSection";
|
||||
import { Layout } from "@saleor/apps-ui";
|
||||
import { AlgoliaConfigurationForm } from "../../components/AlgoliaConfigurationForm";
|
||||
import { ImportProductsToAlgolia } from "../../components/ImportProductsToAlgolia";
|
||||
import { WebhooksStatus } from "../../components/WebhooksStatus";
|
||||
|
@ -20,17 +20,20 @@ export const ConfigurationView = () => {
|
|||
</Text>
|
||||
<MainInstructions marginTop={1.5} />
|
||||
</Box>
|
||||
<AppSection
|
||||
<Layout.AppSection
|
||||
includePadding
|
||||
heading="Webhooks status"
|
||||
sideContent={<WebhooksStatusInstructions />}
|
||||
mainContent={<WebhooksStatus />}
|
||||
/>
|
||||
>
|
||||
<Layout.AppSectionCard>
|
||||
<WebhooksStatus />
|
||||
</Layout.AppSectionCard>
|
||||
</Layout.AppSection>
|
||||
|
||||
<AppSection
|
||||
<Layout.AppSection
|
||||
includePadding={false}
|
||||
marginTop={14}
|
||||
heading="Algolia settings"
|
||||
mainContent={<AlgoliaConfigurationForm />}
|
||||
sideContent={
|
||||
<Box>
|
||||
<Text as="p" marginBottom={1.5}>
|
||||
|
@ -44,11 +47,14 @@ export const ConfigurationView = () => {
|
|||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<AppSection
|
||||
>
|
||||
<AlgoliaConfigurationForm />
|
||||
</Layout.AppSection>
|
||||
|
||||
<Layout.AppSection
|
||||
includePadding={false}
|
||||
marginTop={14}
|
||||
heading="Fields filtering"
|
||||
mainContent={<AlgoliaFieldsSelectionForm />}
|
||||
heading="Algolia fields filtering"
|
||||
sideContent={
|
||||
<Box>
|
||||
<Text as="p" marginBottom={1.5}>
|
||||
|
@ -60,30 +66,35 @@ export const ConfigurationView = () => {
|
|||
</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<AppSection
|
||||
>
|
||||
<AlgoliaFieldsSelectionForm />
|
||||
</Layout.AppSection>
|
||||
|
||||
<Layout.AppSection
|
||||
includePadding
|
||||
marginTop={14}
|
||||
heading="Index products"
|
||||
mainContent={<ImportProductsToAlgolia />}
|
||||
sideContent={
|
||||
<Box>
|
||||
<Text>Perform initial index of all products in your Saleor database</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<ImportProductsToAlgolia />
|
||||
</Layout.AppSection>
|
||||
|
||||
<AppSection
|
||||
<Layout.AppSection
|
||||
includePadding
|
||||
marginTop={14}
|
||||
heading="Set indices settings"
|
||||
mainContent={<IndicesSettings />}
|
||||
sideContent={
|
||||
<Box>
|
||||
<Text>Sets up indices with recommended settings.</Text>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<IndicesSettings />
|
||||
</Layout.AppSection>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,7 +17,8 @@ const nextConfig = () => {
|
|||
|
||||
return {
|
||||
reactStrictMode: true,
|
||||
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"],
|
||||
// TODO Infer names dynamically from disk
|
||||
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw", "@saleor/trpc"],
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"@saleor/apps-ui": "workspace:*",
|
||||
"@saleor/macaw-ui": "0.8.0-pre.127",
|
||||
"@saleor/react-hook-form-macaw": "workspace:*",
|
||||
"@saleor/trpc": "workspace:*",
|
||||
"@segment/analytics-node": "^1.1.0",
|
||||
"@sentry/nextjs": "7.55.2",
|
||||
"@tanstack/react-query": "^4.29.19",
|
||||
|
|
|
@ -4,11 +4,9 @@ import { RootConfig } from "../schemas/root-config.schema";
|
|||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
import { ButtonsBox, Layout, SkeletonLayout, TextLink } from "@saleor/apps-ui";
|
||||
import { Text } from "@saleor/macaw-ui/next";
|
||||
import { ButtonsBox } from "@/modules/ui/buttons-box";
|
||||
import { trpcClient } from "@/modules/trpc/trpc-client";
|
||||
import { Skeleton } from "@/modules/ui/skeleton";
|
||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||
|
||||
const Schema = RootConfig.Schema.unwrap();
|
||||
|
@ -22,7 +20,15 @@ const SegmentConfigFormBase = (props: { values: Shape; onSubmit(values: Shape):
|
|||
});
|
||||
|
||||
return (
|
||||
<Box as="form" onSubmit={handleSubmit(props.onSubmit)}>
|
||||
<Layout.AppSectionCard
|
||||
footer={
|
||||
<ButtonsBox>
|
||||
<Button type="submit">Save</Button>
|
||||
</ButtonsBox>
|
||||
}
|
||||
as="form"
|
||||
onSubmit={handleSubmit(props.onSubmit)}
|
||||
>
|
||||
<Input
|
||||
control={control}
|
||||
name="segmentWriteKey"
|
||||
|
@ -41,10 +47,7 @@ const SegmentConfigFormBase = (props: { values: Shape; onSubmit(values: Shape):
|
|||
</Text>
|
||||
}
|
||||
/>
|
||||
<ButtonsBox marginTop={6}>
|
||||
<Button type="submit">Save</Button>
|
||||
</ButtonsBox>
|
||||
</Box>
|
||||
</Layout.AppSectionCard>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -63,7 +66,7 @@ export const SegmentConfigForm = () => {
|
|||
});
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton.Section />;
|
||||
return <SkeletonLayout.Section />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,42 +1,13 @@
|
|||
import { httpBatchLink } from "@trpc/client";
|
||||
import { createTRPCNext } from "@trpc/next";
|
||||
|
||||
import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const";
|
||||
import { createHttpBatchLink } from "@saleor/trpc";
|
||||
import { appBridgeInstance } from "../../pages/_app";
|
||||
import { AppRouter } from "./trpc-app-router";
|
||||
|
||||
function getBaseUrl() {
|
||||
if (typeof window !== "undefined") return "";
|
||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
||||
|
||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||
}
|
||||
|
||||
export const trpcClient = createTRPCNext<AppRouter>({
|
||||
config({ ctx }) {
|
||||
config() {
|
||||
return {
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url: `${getBaseUrl()}/api/trpc`,
|
||||
headers() {
|
||||
const { token, saleorApiUrl } = appBridgeInstance?.getState() || {};
|
||||
|
||||
if (!token || !saleorApiUrl) {
|
||||
console.error(
|
||||
"Can't initialize tRPC client before establishing the App Bridge connection",
|
||||
);
|
||||
throw new Error("Token and Saleor API URL unknown");
|
||||
}
|
||||
return {
|
||||
/**
|
||||
* Attach headers from app to client requests, so tRPC can add them to context
|
||||
*/
|
||||
[SALEOR_AUTHORIZATION_BEARER_HEADER]: appBridgeInstance?.getState().token,
|
||||
[SALEOR_API_URL_HEADER]: appBridgeInstance?.getState().saleorApiUrl,
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
links: [createHttpBatchLink(appBridgeInstance)],
|
||||
queryClientConfig: { defaultOptions: { queries: { refetchOnWindowFocus: false } } },
|
||||
};
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { initTRPC } from "@trpc/server";
|
||||
import { TrpcContext } from "./trpc-context";
|
||||
import { TrpcContext } from "@saleor/trpc";
|
||||
import { Permission } from "@saleor/app-sdk/types";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import { Box, PropsWithBox, Text } from "@saleor/macaw-ui/next";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
// todo move to shared
|
||||
export const AppSection = ({
|
||||
heading,
|
||||
sideContent,
|
||||
mainContent,
|
||||
includePadding = true,
|
||||
...props
|
||||
}: PropsWithBox<{
|
||||
heading: string;
|
||||
sideContent?: ReactNode;
|
||||
mainContent: ReactNode;
|
||||
includePadding?: boolean;
|
||||
}>) => {
|
||||
return (
|
||||
<Box
|
||||
as="section"
|
||||
__gridTemplateColumns={"400px auto"}
|
||||
display={"grid"}
|
||||
gap={10}
|
||||
__maxWidth={"1200px"}
|
||||
{...props}
|
||||
>
|
||||
<Box>
|
||||
<Text as="h2" variant={"heading"} size={"large"} marginBottom={1.5}>
|
||||
{heading}
|
||||
</Text>
|
||||
{sideContent}
|
||||
</Box>
|
||||
<Box
|
||||
borderStyle={"solid"}
|
||||
borderColor={"neutralPlain"}
|
||||
borderWidth={1}
|
||||
padding={includePadding ? 5 : 0}
|
||||
borderRadius={4}
|
||||
>
|
||||
{mainContent}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
import { BoxProps, Box } from "@saleor/macaw-ui/next";
|
||||
|
||||
export const ButtonsBox = (props: BoxProps) => {
|
||||
return <Box display={"flex"} justifyContent="flex-end" gap={4} {...props} />;
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
import { Box, BoxProps } from "@saleor/macaw-ui/next";
|
||||
|
||||
/*
|
||||
* TODO: Make it more generic, move to shared or contribute to macaw
|
||||
* todo use macaw skeleton
|
||||
*/
|
||||
const Section = (props: BoxProps) => {
|
||||
return (
|
||||
<Box display="grid" gap={2} {...props}>
|
||||
<Box
|
||||
__height="10px"
|
||||
backgroundColor="surfaceNeutralHighlight"
|
||||
borderRadius={2}
|
||||
__width="50%"
|
||||
/>
|
||||
<Box
|
||||
__height="10px"
|
||||
backgroundColor="surfaceNeutralHighlight"
|
||||
borderRadius={2}
|
||||
__width="70%"
|
||||
/>
|
||||
<Box
|
||||
__height="10px"
|
||||
backgroundColor="surfaceNeutralHighlight"
|
||||
borderRadius={2}
|
||||
__width="60%"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Skeleton = { Section };
|
|
@ -1,11 +1,10 @@
|
|||
import { GraphQLProvider } from "@/modules/graphql/GraphQLProvider";
|
||||
import { ThemeSynchronizer } from "@/modules/theme/theme-synchronizer";
|
||||
import "@saleor/macaw-ui/next/style";
|
||||
import { trpcClient } from "@/modules/trpc/trpc-client";
|
||||
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
||||
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
||||
import { NoSSRWrapper } from "@saleor/apps-shared";
|
||||
import { GraphQLProvider, NoSSRWrapper, ThemeSynchronizer } from "@saleor/apps-shared";
|
||||
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
|
||||
import "@saleor/macaw-ui/next/style";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { AppProps } from "next/app";
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as trpcNext from "@trpc/server/adapters/next";
|
||||
import { createTrpcContext } from "../../../modules/trpc/trpc-context";
|
||||
|
||||
import { appRouter } from "../../../modules/trpc/trpc-app-router";
|
||||
import { createTrpcContext } from "@saleor/trpc";
|
||||
|
||||
export default trpcNext.createNextApiHandler({
|
||||
router: appRouter,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { SegmentConfigForm } from "@/modules/configuration/segment-config-form/segment-config-form";
|
||||
import { AppHeader } from "@/modules/ui/app-header";
|
||||
import { AppSection } from "@/modules/ui/app-section";
|
||||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||
import { Layout } from "@saleor/apps-ui";
|
||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||
import { NextPage } from "next";
|
||||
|
||||
|
@ -19,12 +19,13 @@ const ConfigurationPage: NextPage = () => {
|
|||
return (
|
||||
<Box>
|
||||
<AppHeader />
|
||||
<AppSection
|
||||
<Layout.AppSection
|
||||
marginBottom={14}
|
||||
heading="Segment.io configuration"
|
||||
sideContent={<Text>Provide Segment credentials to allow sending events.</Text>}
|
||||
mainContent={<SegmentConfigForm />}
|
||||
/>
|
||||
>
|
||||
<SegmentConfigForm />
|
||||
</Layout.AppSection>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export * from "./src/is-in-iframe";
|
||||
export * from "./src/macaw-theme-provider/macaw-theme-provider";
|
||||
export * from "./src/no-ssr-wrapper";
|
||||
export * from "./src/use-dashboard-notification";
|
||||
export * from "./src/logger";
|
||||
|
@ -7,3 +6,6 @@ export * from "./src/saleor-version-compatibility-validator";
|
|||
export * from "./src/create-graphql-client";
|
||||
export * from "./src/metadata-manager";
|
||||
export * from "./src/editor-js/editor-js-plaintext-renderer";
|
||||
export * from "./src/theme-synchronizer";
|
||||
export * from "./src/GraphQLProvider";
|
||||
export * from "./src/get-app-base-url";
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"typescript": "5.1.6",
|
||||
"urql": "^4.0.4",
|
||||
"vite": "4.4.8",
|
||||
"vitest": "0.34.1"
|
||||
"vitest": "0.34.1",
|
||||
"zod": "3.21.4"
|
||||
},
|
||||
"main": "index.ts",
|
||||
"peerDependencies": {
|
||||
|
@ -37,6 +38,7 @@
|
|||
"pino-pretty": "^10.0.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"urql": "^4.0.4"
|
||||
"urql": "^4.0.4",
|
||||
"zod": "3.21.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { Provider } from "urql";
|
||||
|
||||
import { createGraphQLClient } from "@saleor/apps-shared";
|
||||
import { createGraphQLClient } from "./create-graphql-client";
|
||||
|
||||
export function GraphQLProvider(props: PropsWithChildren<{}>) {
|
||||
const { appBridgeState } = useAppBridge();
|
|
@ -1,5 +1,9 @@
|
|||
// todo move to app sdk
|
||||
export const getBaseUrl = (headers: { [name: string]: string | string[] | undefined }): string => {
|
||||
/**
|
||||
* TODO Consume this from App SDK
|
||||
*/
|
||||
export const getAppBaseUrl = (headers: {
|
||||
[name: string]: string | string[] | undefined;
|
||||
}): string => {
|
||||
const { host, "x-forwarded-proto": xForwardedProto = "http" } = headers;
|
||||
|
||||
const xForwardedProtos = Array.isArray(xForwardedProto)
|
|
@ -1,47 +0,0 @@
|
|||
import {
|
||||
dark,
|
||||
light,
|
||||
SaleorThemeColors,
|
||||
ThemeProvider as MacawUIThemeProvider,
|
||||
} from "@saleor/macaw-ui";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { Theme } from "@material-ui/core/styles";
|
||||
|
||||
type PalettesOverride = Record<"light" | "dark", SaleorThemeColors>;
|
||||
|
||||
/**
|
||||
* Temporary override of colors, to match new dashboard palette.
|
||||
* Long term this will be replaced with Macaw UI 2.x with up to date design tokens
|
||||
*/
|
||||
const palettes: PalettesOverride = {
|
||||
light: {
|
||||
...light,
|
||||
background: {
|
||||
default: "#fff",
|
||||
paper: "#fff",
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
...dark,
|
||||
background: {
|
||||
default: "hsla(211, 42%, 14%, 1)",
|
||||
paper: "hsla(211, 42%, 14%, 1)",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* That's a hack required by Macaw-UI incompatibility with React@18
|
||||
*/
|
||||
const ThemeProvider = MacawUIThemeProvider as React.FC<
|
||||
PropsWithChildren<{ overrides?: Partial<Theme>; ssr: boolean; palettes: PalettesOverride }>
|
||||
>;
|
||||
|
||||
type OuterProps = PropsWithChildren<{ themeOverrides?: Partial<Theme> }>;
|
||||
|
||||
/**
|
||||
* This is theme provider for old Macaw. Will be removed with Macaw/next
|
||||
*/
|
||||
export const MacawThemeProvider = (props: OuterProps) => {
|
||||
return <ThemeProvider {...props} ssr palettes={palettes} />;
|
||||
};
|
4
packages/trpc/.eslintrc
Normal file
4
packages/trpc/.eslintrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"root": true,
|
||||
"extends": ["saleor"]
|
||||
}
|
2
packages/trpc/index.ts
Normal file
2
packages/trpc/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./src/context";
|
||||
export * from "./src/http-batch-link";
|
29
packages/trpc/package.json
Normal file
29
packages/trpc/package.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "@saleor/trpc",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"lint:fix": "eslint --fix ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@saleor/app-sdk": "0.41.1",
|
||||
"eslint": "8.46.0",
|
||||
"eslint-config-saleor": "workspace:*",
|
||||
"next": "13.4.8",
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"main": "index.ts",
|
||||
"peerDependencies": {
|
||||
"@saleor/app-sdk": "0.41.1",
|
||||
"@saleor/apps-shared": "workspace:*",
|
||||
"@tanstack/react-query": "^4.29.19",
|
||||
"@trpc/client": "10.34.0",
|
||||
"@trpc/next": "10.34.0",
|
||||
"@trpc/react-query": "10.34.0",
|
||||
"@trpc/server": "10.34.0",
|
||||
"next": "13.3.0",
|
||||
"pino": "^8.14.1",
|
||||
"pino-pretty": "^10.0.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
}
|
|
@ -1,22 +1,10 @@
|
|||
import * as trpcNext from "@trpc/server/adapters/next";
|
||||
import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_API_URL_HEADER } from "@saleor/app-sdk/const";
|
||||
import { inferAsyncReturnType } from "@trpc/server";
|
||||
|
||||
const getBaseUrl = (headers: { [name: string]: string | string[] | undefined }): string => {
|
||||
const { host, "x-forwarded-proto": xForwardedProto = "http" } = headers;
|
||||
|
||||
const xForwardedProtos = Array.isArray(xForwardedProto)
|
||||
? xForwardedProto.join(",")
|
||||
: xForwardedProto;
|
||||
const protocols = xForwardedProtos.split(",");
|
||||
// prefer https over other protocols
|
||||
const protocol = protocols.find((el) => el === "https") || protocols[0];
|
||||
|
||||
return `${protocol}://${host}`;
|
||||
};
|
||||
import { getAppBaseUrl } from "@saleor/apps-shared";
|
||||
|
||||
export const createTrpcContext = async ({ res, req }: trpcNext.CreateNextContextOptions) => {
|
||||
const baseUrl = getBaseUrl(req.headers);
|
||||
const baseUrl = getAppBaseUrl(req.headers);
|
||||
|
||||
return {
|
||||
token: req.headers[SALEOR_AUTHORIZATION_BEARER_HEADER] as string | undefined,
|
33
packages/trpc/src/http-batch-link.ts
Normal file
33
packages/trpc/src/http-batch-link.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { AppBridge } from "@saleor/app-sdk/app-bridge";
|
||||
import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const";
|
||||
import { httpBatchLink } from "@trpc/client";
|
||||
|
||||
function getBaseUrl() {
|
||||
if (typeof window !== "undefined") return "";
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
||||
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||
}
|
||||
|
||||
export const createHttpBatchLink = (appBridgeInstance?: AppBridge) => {
|
||||
return httpBatchLink({
|
||||
url: `${getBaseUrl()}/api/trpc`,
|
||||
headers() {
|
||||
const { token, saleorApiUrl } = appBridgeInstance?.getState() || {};
|
||||
|
||||
if (!token || !saleorApiUrl) {
|
||||
console.error("Can't initialize tRPC client before establishing the App Bridge connection");
|
||||
throw new Error("Token and Saleor API URL unknown");
|
||||
}
|
||||
return {
|
||||
/**
|
||||
* Attach headers from app to client requests, so tRPC can add them to context
|
||||
*/
|
||||
[SALEOR_AUTHORIZATION_BEARER_HEADER]: appBridgeInstance?.getState().token,
|
||||
[SALEOR_API_URL_HEADER]: appBridgeInstance?.getState().saleorApiUrl,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
9
packages/trpc/turbo.json
Normal file
9
packages/trpc/turbo.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": ["//"],
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"env": ["VERCEL_URL", "PORT"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
export * from "./src/text-link";
|
||||
export * from "./src/semantic-chip";
|
||||
export * from "./src/breadcrumbs";
|
||||
export * from "./src/layout";
|
||||
export * from "./src/buttons-box";
|
||||
export * from "./src/skeleton-layout";
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import { Box } from "@saleor/macaw-ui/next";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
type ExampleProps = PropsWithChildren<{}>;
|
||||
|
||||
export const Example = ({ children }: ExampleProps) => {
|
||||
return <Box>{children}</Box>;
|
||||
};
|
78
packages/ui/src/layout.tsx
Normal file
78
packages/ui/src/layout.tsx
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { Box, PropsWithBox, Text } from "@saleor/macaw-ui/next";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type AppSectionProps = PropsWithBox<{
|
||||
heading: ReactNode;
|
||||
sideContent?: ReactNode;
|
||||
includePadding?: boolean;
|
||||
leftColumnWidthPx?: number;
|
||||
maxWidthPx?: number;
|
||||
}>;
|
||||
|
||||
const AppSectionCard = ({
|
||||
children,
|
||||
footer,
|
||||
includePadding = true,
|
||||
...props
|
||||
}: PropsWithBox<{
|
||||
footer?: ReactNode;
|
||||
includePadding?: boolean;
|
||||
}>) => {
|
||||
return (
|
||||
<Box
|
||||
borderStyle={"solid"}
|
||||
borderColor={"neutralPlain"}
|
||||
borderWidth={1}
|
||||
borderRadius={4}
|
||||
{...props}
|
||||
>
|
||||
<Box padding={includePadding ? 5 : 0}>{children}</Box>
|
||||
{footer && (
|
||||
<Box
|
||||
borderTopStyle="solid"
|
||||
borderTopWidth={1}
|
||||
borderColor="neutralHighlight"
|
||||
padding={includePadding ? 5 : 0}
|
||||
marginTop={5}
|
||||
>
|
||||
{footer}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const AppSection = ({
|
||||
heading,
|
||||
sideContent,
|
||||
includePadding = true,
|
||||
leftColumnWidthPx = 400,
|
||||
maxWidthPx = 1200,
|
||||
children,
|
||||
...props
|
||||
}: AppSectionProps) => {
|
||||
return (
|
||||
<Box
|
||||
as="section"
|
||||
__gridTemplateColumns={`${leftColumnWidthPx}px auto`}
|
||||
display={"grid"}
|
||||
gap={10}
|
||||
__maxWidth={`${maxWidthPx}px`}
|
||||
alignItems="start"
|
||||
{...props}
|
||||
>
|
||||
<Box>
|
||||
<Text as="h2" variant={"heading"} size={"large"} marginBottom={1.5}>
|
||||
{heading}
|
||||
</Text>
|
||||
{sideContent}
|
||||
</Box>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Layout = {
|
||||
AppSection,
|
||||
AppSectionCard,
|
||||
};
|
17
packages/ui/src/skeleton-layout.tsx
Normal file
17
packages/ui/src/skeleton-layout.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Box, BoxProps, Skeleton as MacawSkeleton, SkeletonProps } from "@saleor/macaw-ui/next";
|
||||
|
||||
const Section = (props: BoxProps) => {
|
||||
return (
|
||||
<Box display="grid" gap={2} {...props}>
|
||||
<MacawSkeleton __height="16px" __width="50%" />
|
||||
<MacawSkeleton __height="16px" __width="70%" />
|
||||
<MacawSkeleton __height="16px" __width="60%" />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Line = (props: SkeletonProps) => {
|
||||
return <MacawSkeleton __height="16px" __width="60%" {...props} />;
|
||||
};
|
||||
|
||||
export const SkeletonLayout = { Section, Line };
|
|
@ -1188,6 +1188,9 @@ importers:
|
|||
'@saleor/react-hook-form-macaw':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/react-hook-form-macaw
|
||||
'@saleor/trpc':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/trpc
|
||||
'@segment/analytics-node':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
|
@ -1760,6 +1763,58 @@ importers:
|
|||
vitest:
|
||||
specifier: 0.34.1
|
||||
version: 0.34.1(jsdom@20.0.3)
|
||||
zod:
|
||||
specifier: 3.21.4
|
||||
version: 3.21.4
|
||||
|
||||
packages/trpc:
|
||||
dependencies:
|
||||
'@saleor/apps-shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
'@tanstack/react-query':
|
||||
specifier: ^4.29.19
|
||||
version: 4.29.19(react-dom@18.2.0)(react@18.2.0)
|
||||
'@trpc/client':
|
||||
specifier: 10.34.0
|
||||
version: 10.34.0(@trpc/server@10.34.0)
|
||||
'@trpc/next':
|
||||
specifier: 10.34.0
|
||||
version: 10.34.0(@tanstack/react-query@4.29.19)(@trpc/client@10.34.0)(@trpc/react-query@10.34.0)(@trpc/server@10.34.0)(next@13.4.8)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@trpc/react-query':
|
||||
specifier: 10.34.0
|
||||
version: 10.34.0(@tanstack/react-query@4.29.19)(@trpc/client@10.34.0)(@trpc/server@10.34.0)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@trpc/server':
|
||||
specifier: 10.34.0
|
||||
version: 10.34.0
|
||||
pino:
|
||||
specifier: ^8.14.1
|
||||
version: 8.14.1
|
||||
pino-pretty:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
react:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0
|
||||
react-dom:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
devDependencies:
|
||||
'@saleor/app-sdk':
|
||||
specifier: 0.41.1
|
||||
version: 0.41.1(next@13.4.8)(react-dom@18.2.0)(react@18.2.0)
|
||||
eslint:
|
||||
specifier: 8.46.0
|
||||
version: 8.46.0
|
||||
eslint-config-saleor:
|
||||
specifier: workspace:*
|
||||
version: link:../eslint-config-saleor
|
||||
next:
|
||||
specifier: 13.4.8
|
||||
version: 13.4.8(@babel/core@7.22.11)(react-dom@18.2.0)(react@18.2.0)
|
||||
typescript:
|
||||
specifier: 5.1.6
|
||||
version: 5.1.6
|
||||
|
||||
packages/ui:
|
||||
dependencies:
|
||||
|
@ -2939,7 +2994,7 @@ packages:
|
|||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.2.1
|
||||
'@babel/code-frame': 7.22.5
|
||||
'@babel/code-frame': 7.22.10
|
||||
'@babel/generator': 7.22.9
|
||||
'@babel/helper-compilation-targets': 7.22.9(@babel/core@7.21.8)
|
||||
'@babel/helper-module-transforms': 7.22.9(@babel/core@7.21.8)
|
||||
|
@ -5704,7 +5759,7 @@ packages:
|
|||
resolution: {integrity: sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.22.5
|
||||
'@babel/code-frame': 7.22.10
|
||||
'@babel/generator': 7.22.9
|
||||
'@babel/helper-environment-visitor': 7.22.5
|
||||
'@babel/helper-function-name': 7.22.5
|
||||
|
@ -5757,7 +5812,7 @@ packages:
|
|||
resolution: {integrity: sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.22.5
|
||||
'@babel/code-frame': 7.22.10
|
||||
'@babel/generator': 7.22.9
|
||||
'@babel/helper-environment-visitor': 7.22.5
|
||||
'@babel/helper-function-name': 7.22.5
|
||||
|
@ -10358,7 +10413,7 @@ packages:
|
|||
/@swc/helpers@0.5.1:
|
||||
resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==}
|
||||
dependencies:
|
||||
tslib: 2.6.1
|
||||
tslib: 2.6.2
|
||||
|
||||
/@tanstack/query-core@4.29.19:
|
||||
resolution: {integrity: sha512-uPe1DukeIpIHpQi6UzIgBcXsjjsDaLnc7hF+zLBKnaUlh7jFE/A+P8t4cU4VzKPMFB/C970n/9SxtpO5hmIRgw==}
|
||||
|
@ -12260,7 +12315,7 @@ packages:
|
|||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001503
|
||||
caniuse-lite: 1.0.30001519
|
||||
electron-to-chromium: 1.4.482
|
||||
node-releases: 2.0.13
|
||||
update-browserslist-db: 1.0.11(browserslist@4.21.9)
|
||||
|
|
Loading…
Reference in a new issue