refactor: ♻️ replace material-ui with macaw-ui; remove all views
This commit is contained in:
parent
ca4306162f
commit
0b54a01cbf
30 changed files with 55 additions and 2153 deletions
|
@ -17,13 +17,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^2.9.10",
|
||||
"@material-ui/core": "^4.12.4",
|
||||
"@material-ui/icons": "^4.11.3",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"@saleor/app-sdk": "0.39.1",
|
||||
"@saleor/apps-shared": "workspace:*",
|
||||
"@saleor/apps-ui": "workspace:*",
|
||||
"@saleor/macaw-ui": "^0.7.2",
|
||||
"@saleor/macaw-ui": "^0.8.0-pre.72",
|
||||
"@sentry/nextjs": "^7.45.0",
|
||||
"@tanstack/react-query": "^4.19.1",
|
||||
"@trpc/client": "^10.9.0",
|
||||
|
@ -64,9 +60,9 @@
|
|||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/node": "^18.8.1",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react": "~18.0.38",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-config-next": "12.3.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import React, { PropsWithChildren } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
const Wrapper = (props: PropsWithChildren<{}>) => <React.Fragment>{props.children}</React.Fragment>;
|
||||
|
||||
export const NoSSRWrapper = dynamic(() => Promise.resolve(Wrapper), {
|
||||
ssr: false,
|
||||
});
|
|
@ -1,28 +1,25 @@
|
|||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||
import { useTheme } from "@saleor/macaw-ui";
|
||||
import { memo, useEffect } from "react";
|
||||
import { useTheme } from "@saleor/macaw-ui/next";
|
||||
import { useEffect } from "react";
|
||||
|
||||
/**
|
||||
* Macaw-ui stores its theme mode in memory and local storage. To synchronize App with Dashboard,
|
||||
* Macaw must be informed about this change from AppBridge.
|
||||
*
|
||||
* If you are not using Macaw, you can remove this.
|
||||
*/
|
||||
function _ThemeSynchronizer() {
|
||||
// todo move to shared
|
||||
export function ThemeSynchronizer() {
|
||||
const { appBridgeState } = useAppBridge();
|
||||
const { setTheme, themeType } = useTheme();
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (!setTheme || !appBridgeState?.theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (themeType !== appBridgeState?.theme) {
|
||||
setTheme(appBridgeState.theme);
|
||||
if (appBridgeState.theme === "light") {
|
||||
setTheme("defaultLight");
|
||||
}
|
||||
}, [appBridgeState?.theme, setTheme, themeType]);
|
||||
|
||||
if (appBridgeState.theme === "dark") {
|
||||
setTheme("defaultDark");
|
||||
}
|
||||
}, [appBridgeState?.theme, setTheme]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export const ThemeSynchronizer = memo(_ThemeSynchronizer);
|
||||
|
|
|
@ -1,318 +0,0 @@
|
|||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
FormHelperText,
|
||||
Grid,
|
||||
InputLabel,
|
||||
Switch,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
} from "@material-ui/core";
|
||||
import { Delete, Save } from "@material-ui/icons";
|
||||
import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||
import React from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { useInstanceId } from "../../taxes/tax-context";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { AppLink } from "../../ui/app-link";
|
||||
import { avataxConfigSchema } from "../avatax-config";
|
||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
reverseRow: {
|
||||
display: "flex",
|
||||
flexDirection: "row-reverse",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
const schema = avataxConfigSchema;
|
||||
|
||||
type FormValues = z.infer<typeof schema>;
|
||||
|
||||
const defaultValues: FormValues = {
|
||||
companyCode: "",
|
||||
isAutocommit: false,
|
||||
isSandbox: false,
|
||||
password: "",
|
||||
username: "",
|
||||
name: "",
|
||||
shippingTaxCode: "",
|
||||
};
|
||||
|
||||
export const AvataxConfigurationForm = () => {
|
||||
const { notifySuccess, notifyError } = useDashboardNotification();
|
||||
const [isWarningDialogOpen, setIsWarningDialogOpen] = React.useState(false);
|
||||
const styles = useStyles();
|
||||
const { handleSubmit, reset, control, formState } = useForm<FormValues>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues,
|
||||
});
|
||||
const { instanceId, setInstanceId } = useInstanceId();
|
||||
const { refetch: refetchChannelConfigurationData } =
|
||||
trpcClient.channelsConfiguration.fetch.useQuery(undefined, {
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
});
|
||||
const { refetch: refetchProvidersConfigurationData } =
|
||||
trpcClient.providersConfiguration.getAll.useQuery();
|
||||
const { data: instance } = trpcClient.avataxConfiguration.get.useQuery(
|
||||
{ id: instanceId ?? "" },
|
||||
{
|
||||
enabled: !!instanceId,
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const resetInstanceId = () => {
|
||||
setInstanceId(null);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (instance) {
|
||||
const { config } = instance;
|
||||
|
||||
reset(config);
|
||||
} else {
|
||||
reset(defaultValues);
|
||||
}
|
||||
}, [instance, reset]);
|
||||
|
||||
const { mutate: createMutation, isLoading: isCreateLoading } =
|
||||
trpcClient.avataxConfiguration.post.useMutation({
|
||||
onSuccess({ id }) {
|
||||
setInstanceId(id);
|
||||
refetchProvidersConfigurationData();
|
||||
notifySuccess("Success", "Saved app configuration");
|
||||
},
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: updateMutation, isLoading: isUpdateLoading } =
|
||||
trpcClient.avataxConfiguration.patch.useMutation({
|
||||
onSuccess() {
|
||||
refetchProvidersConfigurationData();
|
||||
notifySuccess("Success", "Updated Avalara configuration");
|
||||
},
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: deleteMutation } = trpcClient.avataxConfiguration.delete.useMutation({
|
||||
onSuccess() {
|
||||
resetInstanceId();
|
||||
refetchProvidersConfigurationData();
|
||||
refetchChannelConfigurationData();
|
||||
notifySuccess("Success", "Removed Avatax instance");
|
||||
},
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
});
|
||||
|
||||
const textFieldProps: TextFieldProps = {
|
||||
fullWidth: true,
|
||||
};
|
||||
|
||||
const onSubmit = (value: FormValues) => {
|
||||
if (instanceId) {
|
||||
updateMutation({
|
||||
id: instanceId,
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
createMutation({
|
||||
value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const closeWarningDialog = () => {
|
||||
setIsWarningDialogOpen(false);
|
||||
};
|
||||
|
||||
const openWarningDialog = () => {
|
||||
setIsWarningDialogOpen(true);
|
||||
};
|
||||
|
||||
const deleteProvider = () => {
|
||||
closeWarningDialog();
|
||||
if (instanceId) {
|
||||
deleteMutation({ id: instanceId });
|
||||
}
|
||||
};
|
||||
|
||||
const isLoading = isCreateLoading || isUpdateLoading;
|
||||
|
||||
return (
|
||||
<>
|
||||
<form autoComplete="off" onSubmit={handleSubmit(onSubmit)}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
defaultValue={defaultValues.name}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
required
|
||||
type="text"
|
||||
{...field}
|
||||
label="Instance name"
|
||||
{...textFieldProps}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{formState.errors.name && (
|
||||
<FormHelperText error>{formState.errors.name.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputLabel>
|
||||
Sandbox
|
||||
<Controller
|
||||
name={"isSandbox"}
|
||||
control={control}
|
||||
defaultValue={defaultValues.isSandbox}
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onChange={(e) => field.onChange(e.target.checked)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</InputLabel>
|
||||
<FormHelperText>
|
||||
Toggling between{" "}
|
||||
<AppLink
|
||||
href={
|
||||
"https://developer.avalara.com/erp-integration-guide/sales-tax-badge/authentication-in-avatax/sandbox-vs-production/"
|
||||
}
|
||||
>
|
||||
<q>Production</q> and <q>Sandbox</q>
|
||||
</AppLink>{" "}
|
||||
environments.{" "}
|
||||
</FormHelperText>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputLabel>
|
||||
Autocommit
|
||||
<Controller
|
||||
name={"isAutocommit"}
|
||||
control={control}
|
||||
defaultValue={defaultValues.isAutocommit}
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onChange={(e) => field.onChange(e.target.checked)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</InputLabel>
|
||||
<FormHelperText>
|
||||
If enabled, the order will be automatically{" "}
|
||||
<AppLink
|
||||
href={
|
||||
"https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit/"
|
||||
}
|
||||
>
|
||||
committed to Avalara.
|
||||
</AppLink>{" "}
|
||||
</FormHelperText>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="username"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => (
|
||||
<TextField required type="text" {...field} label="Username" {...textFieldProps} />
|
||||
)}
|
||||
/>
|
||||
{formState.errors.username && (
|
||||
<FormHelperText error>{formState.errors.username.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="password"
|
||||
control={control}
|
||||
defaultValue={defaultValues.password}
|
||||
render={({ field }) => (
|
||||
<TextField required label="Password" {...field} {...textFieldProps} />
|
||||
)}
|
||||
/>
|
||||
|
||||
{formState.errors.password && (
|
||||
<FormHelperText error>{formState.errors.password.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="companyCode"
|
||||
control={control}
|
||||
defaultValue={defaultValues.companyCode}
|
||||
render={({ field }) => (
|
||||
<TextField type="text" {...field} label="Company code" {...textFieldProps} />
|
||||
)}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{"When not provided, the default company will be used. "}
|
||||
<AppLink href="https://developer.avalara.com/erp-integration-guide/sales-tax-badge/transactions/simple-transactions/company-codes/">
|
||||
Read more
|
||||
</AppLink>{" "}
|
||||
about company codes.
|
||||
</FormHelperText>
|
||||
{formState.errors.companyCode && (
|
||||
<FormHelperText error>{formState.errors.companyCode.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="shippingTaxCode"
|
||||
control={control}
|
||||
defaultValue={defaultValues.shippingTaxCode}
|
||||
render={({ field }) => (
|
||||
<TextField type="text" {...field} label="Shipping tax code" {...textFieldProps} />
|
||||
)}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{"Tax code that for the shipping line sent to Avatax. "}
|
||||
<AppLink href="https://taxcode.avatax.avalara.com">
|
||||
Must match Avatax tax codes format.
|
||||
</AppLink>
|
||||
</FormHelperText>
|
||||
{formState.errors.shippingTaxCode && (
|
||||
<FormHelperText error>{formState.errors.shippingTaxCode.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<br />
|
||||
<div className={styles.reverseRow}>
|
||||
<Button startIcon={<Save />} type="submit" variant="primary">
|
||||
{isLoading ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
{instanceId && (
|
||||
<Button onClick={deleteProvider} startIcon={<Delete />}>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
{/* <DeleteProviderDialog
|
||||
isOpen={isWarningDialogOpen}
|
||||
onClose={closeWarningDialog}
|
||||
onCancel={closeWarningDialog}
|
||||
onConfirm={deleteProvider}
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
import { AvataxConfigurationForm } from "./avatax-configuration-form";
|
||||
|
||||
export const AvataxConfiguration = () => {
|
||||
return (
|
||||
<section>
|
||||
<h2>Avatax configuration</h2>
|
||||
<AvataxConfigurationForm />
|
||||
</section>
|
||||
);
|
||||
};
|
|
@ -1,218 +0,0 @@
|
|||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
FormGroup,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { Save } from "@material-ui/icons";
|
||||
import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||
|
||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||
import React from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
ChannelConfig,
|
||||
channelSchema,
|
||||
defaultChannelConfig,
|
||||
} from "../../channels-configuration/channels-config";
|
||||
import { ProvidersConfig } from "../../providers-configuration/providers-config";
|
||||
import { ProviderIcon } from "../../providers-configuration/ui/provider-icon";
|
||||
import { useChannelSlug } from "../../taxes/tax-context";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { CountrySelect } from "../../ui/country-select/country-select";
|
||||
|
||||
type ChannelTaxProviderFormValues = ChannelConfig;
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
reverseRow: {
|
||||
display: "flex",
|
||||
flexDirection: "row-reverse",
|
||||
},
|
||||
menuItem: {
|
||||
display: "flex",
|
||||
gap: theme.spacing(1),
|
||||
alignItems: "center",
|
||||
},
|
||||
helperText: {
|
||||
marginTop: 0,
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
const getDefaultFormValues = (
|
||||
channel: ChannelConfig | undefined,
|
||||
providers: ProvidersConfig
|
||||
): ChannelTaxProviderFormValues => {
|
||||
if (channel && channel.providerInstanceId !== "") {
|
||||
return {
|
||||
...defaultChannelConfig,
|
||||
...channel,
|
||||
};
|
||||
}
|
||||
|
||||
const isOnlyOneInstance = providers.length === 1;
|
||||
|
||||
if (isOnlyOneInstance) {
|
||||
return {
|
||||
...defaultChannelConfig,
|
||||
providerInstanceId: providers[0].id,
|
||||
};
|
||||
}
|
||||
|
||||
return defaultChannelConfig;
|
||||
};
|
||||
|
||||
export const ChannelTaxProviderForm = () => {
|
||||
const styles = useStyles();
|
||||
const { control, reset, handleSubmit } = useForm<ChannelTaxProviderFormValues>({
|
||||
resolver: zodResolver(channelSchema),
|
||||
});
|
||||
const { notifyError, notifySuccess } = useDashboardNotification();
|
||||
|
||||
const { channelSlug } = useChannelSlug();
|
||||
|
||||
const { data: channelConfigurationData, refetch: refetchChannelConfigurationData } =
|
||||
trpcClient.channelsConfiguration.fetch.useQuery(undefined, {
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
});
|
||||
|
||||
const { data: providerInstances = [] } = trpcClient.providersConfiguration.getAll.useQuery(
|
||||
undefined,
|
||||
{
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
}
|
||||
);
|
||||
const channelConfig = channelConfigurationData?.[channelSlug];
|
||||
|
||||
const { mutate, isLoading } = trpcClient.channelsConfiguration.upsert.useMutation({
|
||||
onSuccess() {
|
||||
refetchChannelConfigurationData();
|
||||
notifySuccess("Success", `Saved configuration of channel: ${channelSlug}`);
|
||||
},
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const defaultValues = getDefaultFormValues(channelConfig, providerInstances);
|
||||
|
||||
reset(defaultValues);
|
||||
}, [channelConfig, providerInstances, reset]);
|
||||
|
||||
const textFieldProps: TextFieldProps = {
|
||||
fullWidth: true,
|
||||
};
|
||||
|
||||
const onSubmit = (values: ChannelTaxProviderFormValues) => {
|
||||
mutate({
|
||||
channelSlug,
|
||||
config: {
|
||||
...values,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12}>
|
||||
<InputLabel>
|
||||
Channel tax provider
|
||||
<Controller
|
||||
name={"providerInstanceId"}
|
||||
control={control}
|
||||
defaultValue={""}
|
||||
render={({ field }) => (
|
||||
<Select fullWidth {...field}>
|
||||
{providerInstances.map(({ config, id, provider }) => (
|
||||
<MenuItem value={id} key={id}>
|
||||
<div className={styles.menuItem}>
|
||||
<Typography variant="body1">{config.name}</Typography>
|
||||
<ProviderIcon size={"medium"} provider={provider} />
|
||||
</div>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</InputLabel>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormGroup>
|
||||
<Typography variant="h4">Ship from address</Typography>
|
||||
<FormHelperText className={styles.helperText}>
|
||||
The taxes will be calculated based on the address.
|
||||
</FormHelperText>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={8}>
|
||||
<Controller
|
||||
name="address.country"
|
||||
control={control}
|
||||
render={({ field }) => <CountrySelect {...field} />}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Controller
|
||||
name="address.zip"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => <TextField {...field} label="Zip" {...textFieldProps} />}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Controller
|
||||
name="address.state"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => (
|
||||
<TextField {...field} label="State" {...textFieldProps} />
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Controller
|
||||
name="address.city"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => (
|
||||
<TextField {...field} label="City" {...textFieldProps} />
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="address.street"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field }) => (
|
||||
<TextField {...field} label="Street" {...textFieldProps} />
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</FormGroup>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<br />
|
||||
<div className={styles.reverseRow}>
|
||||
<Button variant="primary" startIcon={<Save />} type="submit">
|
||||
{isLoading ? "Saving..." : "Save"}
|
||||
</Button>{" "}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,160 +0,0 @@
|
|||
import { Grid, Typography } from "@material-ui/core";
|
||||
import { Warning } from "@material-ui/icons";
|
||||
import { Skeleton } from "@material-ui/lab";
|
||||
import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { useAppRedirect } from "../../../lib/app/redirect";
|
||||
import { ProviderIcon } from "../../providers-configuration/ui/provider-icon";
|
||||
import { providerConfig, TaxProviderName } from "../../taxes/provider-config";
|
||||
import { useActiveTab, useChannelSlug, useInstanceId } from "../../taxes/tax-context";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { AppLink } from "../../ui/app-link";
|
||||
import { AppPaper } from "../../ui/app-paper";
|
||||
import { ChannelTaxProviderForm } from "./channel-tax-provider-form";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
centerWithGap: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
const NoDataPlaceholder = ({
|
||||
title,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
title: string;
|
||||
}>) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<AppPaper>
|
||||
<div>
|
||||
<span className={styles.centerWithGap}>
|
||||
<Typography component={"h3"} variant="h3">
|
||||
{title}
|
||||
</Typography>
|
||||
<Warning />
|
||||
</span>
|
||||
<br />
|
||||
{children}
|
||||
</div>
|
||||
</AppPaper>
|
||||
);
|
||||
};
|
||||
|
||||
const NoChannelPlaceholder = () => {
|
||||
const { redirect } = useAppRedirect();
|
||||
|
||||
return (
|
||||
<NoDataPlaceholder title={"Channels not found"}>
|
||||
<Typography variant="body1">
|
||||
For a channel to appear on this list, you need to configure it on the{" "}
|
||||
<AppLink href="/taxes/channels">Tax Configuration</AppLink> page.
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography variant="body1">
|
||||
By default, each channel will use <q>flat rates</q> as the tax calculation method. If you
|
||||
want a channel to calculate taxes using the Tax App, you need to change the tax calculation
|
||||
method to <b>Use tax app</b>.
|
||||
</Typography>
|
||||
<br />
|
||||
<Button onClick={() => redirect("/taxes/channels")}>Go to Tax Configuration</Button>
|
||||
</NoDataPlaceholder>
|
||||
);
|
||||
};
|
||||
|
||||
const NoProviderPlaceholder = () => {
|
||||
const styles = useStyles();
|
||||
const { setActiveTab } = useActiveTab();
|
||||
const { setInstanceId } = useInstanceId();
|
||||
|
||||
return (
|
||||
<NoDataPlaceholder title={"Tax providers not found"}>
|
||||
<Typography variant="body1">
|
||||
You need to set up at least one tax provider before you can configure a channel.
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography>
|
||||
We currently support the following tax providers:
|
||||
<ul>
|
||||
{Object.entries(providerConfig).map(([provider, { label }]) => (
|
||||
<Typography variant="body1" component={"li"} key={label}>
|
||||
<span className={styles.centerWithGap}>
|
||||
{label}
|
||||
<ProviderIcon size={"medium"} provider={provider as TaxProviderName} />
|
||||
</span>
|
||||
</Typography>
|
||||
))}
|
||||
</ul>
|
||||
</Typography>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setActiveTab("providers");
|
||||
setInstanceId(null);
|
||||
}}
|
||||
>
|
||||
Configure a tax provider
|
||||
</Button>
|
||||
</NoDataPlaceholder>
|
||||
);
|
||||
};
|
||||
|
||||
const ChannelTaxProviderSkeleton = () => {
|
||||
return (
|
||||
<AppPaper>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rect" width={"35%"} height={10} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rect" width={"100%"} height={30} />
|
||||
</Grid>
|
||||
<br />
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rect" width={"35%"} height={10} />
|
||||
</Grid>
|
||||
<Grid item xs={8}>
|
||||
<Skeleton variant="rect" width={"100%"} height={50} />
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Skeleton variant="rect" width={"100%"} height={50} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rect" width={"100%"} height={50} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Skeleton variant="rect" width={"100%"} height={50} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rect" width={"100%"} height={50} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AppPaper>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChannelTaxProvider = () => {
|
||||
const { channelSlug } = useChannelSlug();
|
||||
const channels = trpcClient.channels.fetch.useQuery(undefined, {});
|
||||
const providers = trpcClient.providersConfiguration.getAll.useQuery();
|
||||
|
||||
if (channels?.isFetching || providers?.isFetching) {
|
||||
return <ChannelTaxProviderSkeleton />;
|
||||
}
|
||||
|
||||
if (!channelSlug) {
|
||||
return <NoChannelPlaceholder />;
|
||||
}
|
||||
|
||||
if (!providers?.data?.length) {
|
||||
return <NoProviderPlaceholder />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppPaper>
|
||||
<ChannelTaxProviderForm />
|
||||
</AppPaper>
|
||||
);
|
||||
};
|
|
@ -1,70 +0,0 @@
|
|||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import {
|
||||
OffsettedList,
|
||||
OffsettedListBody,
|
||||
OffsettedListHeader,
|
||||
OffsettedListItem,
|
||||
OffsettedListItemCell,
|
||||
} from "@saleor/macaw-ui";
|
||||
import clsx from "clsx";
|
||||
import { ChannelFragment } from "../../../../generated/graphql";
|
||||
|
||||
const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
headerItem: {
|
||||
height: "auto !important",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr",
|
||||
},
|
||||
listItem: {
|
||||
cursor: "pointer",
|
||||
height: "auto !important",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr",
|
||||
},
|
||||
listItemActive: {
|
||||
border: `2px solid ${theme.palette.primary.main}`,
|
||||
},
|
||||
cellSlug: {
|
||||
fontFamily: "monospace",
|
||||
opacity: 0.8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type Props = {
|
||||
channels: ChannelFragment[];
|
||||
activeChannelSlug: string;
|
||||
onChannelClick(channelSlug: string): void;
|
||||
};
|
||||
|
||||
export const ChannelsList = ({ channels, activeChannelSlug, onChannelClick }: Props) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<OffsettedList gridTemplate={["1fr", "1fr"]}>
|
||||
<OffsettedListHeader>
|
||||
<OffsettedListItem className={styles.headerItem}>
|
||||
<OffsettedListItemCell>Channel name</OffsettedListItemCell>
|
||||
</OffsettedListItem>
|
||||
</OffsettedListHeader>
|
||||
<OffsettedListBody>
|
||||
{channels.map((c) => {
|
||||
return (
|
||||
<OffsettedListItem
|
||||
className={clsx(styles.listItem, {
|
||||
[styles.listItemActive]: c.slug === activeChannelSlug,
|
||||
})}
|
||||
key={c.slug}
|
||||
onClick={() => {
|
||||
onChannelClick(c.slug);
|
||||
}}
|
||||
>
|
||||
<OffsettedListItemCell>{c.name}</OffsettedListItemCell>
|
||||
</OffsettedListItem>
|
||||
);
|
||||
})}
|
||||
</OffsettedListBody>
|
||||
</OffsettedList>
|
||||
);
|
||||
};
|
|
@ -1,56 +0,0 @@
|
|||
import { Grid } from "@material-ui/core";
|
||||
import { Skeleton } from "@material-ui/lab";
|
||||
import { useChannelSlug } from "../../taxes/tax-context";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { AppPaper } from "../../ui/app-paper";
|
||||
import { ChannelsList } from "./channels-list";
|
||||
|
||||
const ChannelsSkeleton = () => {
|
||||
return (
|
||||
<AppPaper>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rect" width={"45%"} height={10} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rect" width={"100%"} height={30} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AppPaper>
|
||||
);
|
||||
};
|
||||
|
||||
export const Channels = () => {
|
||||
const { channelSlug, setChannelSlug } = useChannelSlug();
|
||||
|
||||
const channels = trpcClient.channels.fetch.useQuery(undefined, {
|
||||
onSuccess: (result) => {
|
||||
if (result?.[0]) {
|
||||
setChannelSlug(result?.[0].slug);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (channels?.isFetching) {
|
||||
return <ChannelsSkeleton />;
|
||||
}
|
||||
|
||||
if (channels.error) {
|
||||
return <div>Error. No channel available</div>;
|
||||
}
|
||||
|
||||
if (channels.data?.length === 0) {
|
||||
// empty space for grid
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppPaper>
|
||||
<ChannelsList
|
||||
channels={channels.data ?? []}
|
||||
activeChannelSlug={channelSlug}
|
||||
onChannelClick={(nextSlug) => setChannelSlug(nextSlug)}
|
||||
/>
|
||||
</AppPaper>
|
||||
);
|
||||
};
|
|
@ -1,105 +0,0 @@
|
|||
import { FormControlLabel, Grid, Radio, RadioGroup, Typography } from "@material-ui/core";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import React from "react";
|
||||
import { AvataxConfiguration } from "../../avatax/ui/avatax-configuration";
|
||||
import { providerConfig, TaxProviderName } from "../../taxes/provider-config";
|
||||
import { TaxJarConfiguration } from "../../taxjar/ui/taxjar-configuration";
|
||||
import { useInstanceId } from "../../taxes/tax-context";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { AppPaper } from "../../ui/app-paper";
|
||||
import { ProviderIcon } from "./provider-icon";
|
||||
|
||||
const providersConfigurationComponent: Record<TaxProviderName, React.ComponentType> = {
|
||||
taxjar: TaxJarConfiguration,
|
||||
avatax: AvataxConfiguration,
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
radioLabel: {
|
||||
width: "100%",
|
||||
padding: theme.spacing(1),
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
theme.palette.type === "dark" ? theme.palette.primary.dark : theme.palette.grey[50],
|
||||
},
|
||||
},
|
||||
gridItem: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
},
|
||||
radioLabelActive: {
|
||||
backgroundColor:
|
||||
theme.palette.type === "dark" ? theme.palette.primary.dark : theme.palette.grey[50],
|
||||
},
|
||||
iconWithLabel: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
export const Configuration = () => {
|
||||
const [provider, setProvider] = React.useState<TaxProviderName>("taxjar");
|
||||
const { instanceId } = useInstanceId();
|
||||
const { data: providersConfigurationData } = trpcClient.providersConfiguration.getAll.useQuery();
|
||||
const styles = useStyles();
|
||||
|
||||
React.useEffect(() => {
|
||||
const instance = providersConfigurationData?.find((instance) => instance.id === instanceId);
|
||||
|
||||
setProvider(instance?.provider ?? "taxjar");
|
||||
}, [instanceId, providersConfigurationData]);
|
||||
|
||||
const SelectedConfigurationForm = React.useMemo(
|
||||
() => (provider ? providersConfigurationComponent[provider] : () => null),
|
||||
[provider]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppPaper>
|
||||
{!instanceId && (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<div className={styles.gridItem}>
|
||||
<Typography component="h3" variant="h3">
|
||||
Please select one of the providers:
|
||||
</Typography>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroup
|
||||
value={provider ?? ""}
|
||||
onChange={(e) => setProvider(e.target.value as TaxProviderName)}
|
||||
>
|
||||
<Grid container justifyContent="center">
|
||||
{Object.entries(providerConfig).map(([name, config]) => (
|
||||
<Grid className={styles.gridItem} item xs={6} key={name}>
|
||||
<FormControlLabel
|
||||
className={
|
||||
provider === name
|
||||
? `${styles.radioLabelActive} ${styles.radioLabel}`
|
||||
: styles.radioLabel
|
||||
}
|
||||
control={<Radio style={{ display: "none" }} name="provider" value={name} />}
|
||||
label={
|
||||
<div className={styles.iconWithLabel}>
|
||||
<ProviderIcon size={"xlarge"} provider={name as TaxProviderName} />
|
||||
<Typography variant="body1">{config.label}</Typography>
|
||||
</div>
|
||||
}
|
||||
labelPlacement="top"
|
||||
aria-label={config.label}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</RadioGroup>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
<SelectedConfigurationForm />
|
||||
</AppPaper>
|
||||
);
|
||||
};
|
|
@ -1,45 +0,0 @@
|
|||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogActions,
|
||||
} from "@material-ui/core";
|
||||
import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||
|
||||
type DeleteProviderDialogProps = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
actions: {
|
||||
display: "flex",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
export const DeleteProviderDialog = (p: DeleteProviderDialogProps) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<Dialog open={p.isOpen} onClose={p.onClose}>
|
||||
<DialogTitle>Delete provider instance?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete this provider instance? This action cannot be undone.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<div className={styles.actions}>
|
||||
<Button onClick={p.onCancel}>Cancel</Button>
|
||||
<Button variant="primary" onClick={p.onConfirm}>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
import Image, { ImageProps } from "next/image";
|
||||
import { providerConfig, TaxProviderName } from "../../taxes/provider-config";
|
||||
|
||||
type Size = "small" | "medium" | "large" | "xlarge";
|
||||
|
||||
const sizes: Record<Size, number> = {
|
||||
small: 16,
|
||||
medium: 24,
|
||||
large: 32,
|
||||
xlarge: 48,
|
||||
};
|
||||
|
||||
type ProviderIconProps = {
|
||||
provider: TaxProviderName;
|
||||
size?: Size;
|
||||
} & Omit<ImageProps, "src" | "height" | "width" | "alt">;
|
||||
|
||||
export const ProviderIcon = ({ provider, size = "medium", ...props }: ProviderIconProps) => {
|
||||
const { icon, label } = providerConfig[provider];
|
||||
const matchedSize = sizes[size];
|
||||
|
||||
return (
|
||||
<Image src={icon} alt={`${label} icon`} width={matchedSize} height={matchedSize} {...props} />
|
||||
);
|
||||
};
|
|
@ -1,70 +0,0 @@
|
|||
import {
|
||||
makeStyles,
|
||||
OffsettedList,
|
||||
OffsettedListBody,
|
||||
OffsettedListHeader,
|
||||
OffsettedListItem,
|
||||
OffsettedListItemCell,
|
||||
} from "@saleor/macaw-ui";
|
||||
import clsx from "clsx";
|
||||
import { useInstanceId } from "../../taxes/tax-context";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { AppPaper } from "../../ui/app-paper";
|
||||
import { ProviderIcon } from "./provider-icon";
|
||||
|
||||
const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
headerItem: {
|
||||
height: "auto !important",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr",
|
||||
},
|
||||
listItem: {
|
||||
cursor: "pointer",
|
||||
height: "auto !important",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr",
|
||||
},
|
||||
listItemActive: {
|
||||
border: `2px solid ${theme.palette.primary.main}`,
|
||||
},
|
||||
cell: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const TaxProvidersInstancesList = () => {
|
||||
const styles = useStyles();
|
||||
const { instanceId, setInstanceId } = useInstanceId();
|
||||
const { data: providersConfigurationData } = trpcClient.providersConfiguration.getAll.useQuery();
|
||||
const instances = providersConfigurationData ?? [];
|
||||
|
||||
return (
|
||||
<AppPaper>
|
||||
<OffsettedList gridTemplate={["1fr", "1fr"]}>
|
||||
<OffsettedListHeader>
|
||||
<OffsettedListItem className={styles.headerItem}>
|
||||
<OffsettedListItemCell>Tax provider list</OffsettedListItemCell>
|
||||
</OffsettedListItem>
|
||||
</OffsettedListHeader>
|
||||
<OffsettedListBody>
|
||||
{instances.map((instance) => (
|
||||
<OffsettedListItem
|
||||
onClick={() => setInstanceId(instance.id)}
|
||||
className={clsx(styles.listItem, instance.id === instanceId && styles.listItemActive)}
|
||||
key={instance.id}
|
||||
>
|
||||
<OffsettedListItemCell className={styles.cell}>
|
||||
{instance.config.name}
|
||||
<ProviderIcon size="medium" provider={instance.provider} />
|
||||
</OffsettedListItemCell>
|
||||
</OffsettedListItem>
|
||||
))}
|
||||
</OffsettedListBody>
|
||||
</OffsettedList>
|
||||
</AppPaper>
|
||||
);
|
||||
};
|
|
@ -1,74 +0,0 @@
|
|||
import { Grid } from "@material-ui/core";
|
||||
import { Add } from "@material-ui/icons";
|
||||
import { Skeleton } from "@material-ui/lab";
|
||||
import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||
import { useInstanceId } from "../../taxes/tax-context";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { AppPaper } from "../../ui/app-paper";
|
||||
import { TaxProvidersInstancesList } from "./providers-instances-list";
|
||||
|
||||
const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
button: {
|
||||
padding: theme.spacing(1, 2),
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const ProvidersSkeleton = () => {
|
||||
return (
|
||||
<AppPaper>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rect" width={"45%"} height={10} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rect" width={"100%"} height={30} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Skeleton variant="rect" width={"100%"} height={30} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AppPaper>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProvidersInstances = () => {
|
||||
const styles = useStyles();
|
||||
const providers = trpcClient.providersConfiguration.getAll.useQuery();
|
||||
const { setInstanceId } = useInstanceId();
|
||||
|
||||
if (providers?.isFetching) {
|
||||
return <ProvidersSkeleton />;
|
||||
}
|
||||
|
||||
if (providers.error) {
|
||||
return <div>Error. No provider available</div>;
|
||||
}
|
||||
|
||||
const isAnyProvider = providers.data?.length !== 0;
|
||||
|
||||
if (!isAnyProvider) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TaxProvidersInstancesList />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
startIcon={<Add />}
|
||||
className={styles.button}
|
||||
fullWidth
|
||||
onClick={() => setInstanceId(null)}
|
||||
>
|
||||
Add provider
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
|
@ -1,5 +1,4 @@
|
|||
import { atom, useAtom } from "jotai";
|
||||
import { AppTab } from "../../pages/configuration";
|
||||
|
||||
const channelSlugAtom = atom("");
|
||||
|
||||
|
@ -16,11 +15,3 @@ export const useInstanceId = () => {
|
|||
|
||||
return { instanceId, setInstanceId };
|
||||
};
|
||||
|
||||
const activeTabAtom = atom<AppTab>("channels");
|
||||
|
||||
export const useActiveTab = () => {
|
||||
const [activeTab, setActiveTab] = useAtom(activeTabAtom);
|
||||
|
||||
return { activeTab, setActiveTab };
|
||||
};
|
||||
|
|
|
@ -1,230 +0,0 @@
|
|||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
FormHelperText,
|
||||
Grid,
|
||||
InputLabel,
|
||||
Switch,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
} from "@material-ui/core";
|
||||
import { Delete, Save } from "@material-ui/icons";
|
||||
import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||
import React from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { useInstanceId } from "../../taxes/tax-context";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { taxJarConfigSchema } from "../taxjar-config";
|
||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
reverseRow: {
|
||||
display: "flex",
|
||||
flexDirection: "row-reverse",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
const schema = taxJarConfigSchema;
|
||||
|
||||
type FormValues = z.infer<typeof schema>;
|
||||
|
||||
const defaultValues: FormValues = {
|
||||
name: "",
|
||||
apiKey: "",
|
||||
isSandbox: false,
|
||||
};
|
||||
|
||||
export const TaxJarConfigurationForm = () => {
|
||||
const [isWarningDialogOpen, setIsWarningDialogOpen] = React.useState(false);
|
||||
const styles = useStyles();
|
||||
const { instanceId, setInstanceId } = useInstanceId();
|
||||
const { handleSubmit, reset, control, formState } = useForm<FormValues>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues,
|
||||
});
|
||||
const { notifySuccess, notifyError } = useDashboardNotification();
|
||||
|
||||
const resetInstanceId = () => {
|
||||
setInstanceId(null);
|
||||
};
|
||||
|
||||
const { refetch: refetchChannelConfigurationData } =
|
||||
trpcClient.channelsConfiguration.fetch.useQuery(undefined, {
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
});
|
||||
|
||||
const { refetch: refetchProvidersConfigurationData } =
|
||||
trpcClient.providersConfiguration.getAll.useQuery();
|
||||
const { data: instance } = trpcClient.taxJarConfiguration.get.useQuery(
|
||||
{ id: instanceId ?? "" },
|
||||
{
|
||||
enabled: !!instanceId,
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { mutate: createMutation, isLoading: isCreateLoading } =
|
||||
trpcClient.taxJarConfiguration.post.useMutation({
|
||||
onSuccess({ id }) {
|
||||
setInstanceId(id);
|
||||
refetchProvidersConfigurationData();
|
||||
refetchChannelConfigurationData();
|
||||
|
||||
notifySuccess("Success", "Saved TaxJar configuration");
|
||||
},
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: updateMutation, isLoading: isUpdateLoading } =
|
||||
trpcClient.taxJarConfiguration.patch.useMutation({
|
||||
onSuccess() {
|
||||
refetchProvidersConfigurationData();
|
||||
refetchChannelConfigurationData();
|
||||
notifySuccess("Success", "Updated TaxJar configuration");
|
||||
},
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: deleteMutation, isLoading: isDeleteLoading } =
|
||||
trpcClient.taxJarConfiguration.delete.useMutation({
|
||||
onSuccess() {
|
||||
resetInstanceId();
|
||||
refetchProvidersConfigurationData();
|
||||
refetchChannelConfigurationData();
|
||||
|
||||
notifySuccess("Success", "Removed TaxJar instance");
|
||||
},
|
||||
onError(error) {
|
||||
notifyError("Error", error.message);
|
||||
},
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (instance) {
|
||||
const { config } = instance;
|
||||
|
||||
reset(config);
|
||||
} else {
|
||||
reset({ ...defaultValues });
|
||||
}
|
||||
}, [instance, instanceId, reset]);
|
||||
|
||||
const textFieldProps: TextFieldProps = {
|
||||
fullWidth: true,
|
||||
};
|
||||
|
||||
const onSubmit = (value: FormValues) => {
|
||||
if (instanceId) {
|
||||
updateMutation({
|
||||
id: instanceId,
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
createMutation({
|
||||
value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const closeWarningDialog = () => {
|
||||
setIsWarningDialogOpen(false);
|
||||
};
|
||||
|
||||
const openWarningDialog = () => {
|
||||
setIsWarningDialogOpen(true);
|
||||
};
|
||||
|
||||
const deleteProvider = () => {
|
||||
closeWarningDialog();
|
||||
if (instanceId) {
|
||||
deleteMutation({ id: instanceId });
|
||||
}
|
||||
};
|
||||
|
||||
const isLoading = isCreateLoading || isUpdateLoading;
|
||||
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
defaultValue={defaultValues.name}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
required
|
||||
type="text"
|
||||
{...field}
|
||||
label="Instance name"
|
||||
{...textFieldProps}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{formState.errors.name && (
|
||||
<FormHelperText error>{formState.errors.name.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
name="apiKey"
|
||||
control={control}
|
||||
defaultValue={defaultValues.apiKey}
|
||||
render={({ field }) => (
|
||||
<TextField required label="API Key" {...field} {...textFieldProps} />
|
||||
)}
|
||||
/>
|
||||
{formState.errors?.apiKey && (
|
||||
<FormHelperText error>{formState.errors?.apiKey.message}</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<InputLabel>
|
||||
Sandbox
|
||||
<Controller
|
||||
name={"isSandbox"}
|
||||
control={control}
|
||||
defaultValue={defaultValues.isSandbox}
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onChange={(e) => field.onChange(e.target.checked)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</InputLabel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<br />
|
||||
<div className={styles.reverseRow}>
|
||||
<Button startIcon={<Save />} type="submit" variant="primary">
|
||||
{isLoading ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
{instanceId && (
|
||||
<Button onClick={deleteProvider} startIcon={<Delete />}>
|
||||
{isDeleteLoading ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
{/* // todo: bring back to life once Dashboard allows to summon dialog */}
|
||||
{/* <DeleteProviderDialog
|
||||
isOpen={isWarningDialogOpen}
|
||||
onClose={closeWarningDialog}
|
||||
onCancel={closeWarningDialog}
|
||||
onConfirm={deleteProvider}
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
import { TaxJarConfigurationForm } from "./taxjar-configuration-form";
|
||||
|
||||
export const TaxJarConfiguration = () => {
|
||||
return (
|
||||
<section>
|
||||
<h2>TaxJar configuration</h2>
|
||||
<TaxJarConfigurationForm />
|
||||
</section>
|
||||
);
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
|
||||
export const useStyles = makeStyles({
|
||||
root: {
|
||||
maxWidth: 1180,
|
||||
margin: "0 auto",
|
||||
},
|
||||
});
|
||||
|
||||
export const AppContainer = ({ children }: { children: React.ReactNode }) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return <div className={styles.root}>{children}</div>;
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
export const useStyles = makeStyles({
|
||||
root: {
|
||||
display: "grid",
|
||||
gridTemplateColumns: "280px auto 280px",
|
||||
alignItems: "start",
|
||||
gap: 32,
|
||||
},
|
||||
});
|
||||
|
||||
export type Props = PropsWithChildren<{}>;
|
||||
|
||||
export const AppGrid = ({ children }: Props) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return <div className={styles.root}>{children}</div>;
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
import React from "react";
|
||||
import { AppContainer } from "./app-container";
|
||||
import { AppGrid } from "./app-grid";
|
||||
|
||||
export const AppLayout = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<AppContainer>
|
||||
<AppGrid>{children}</AppGrid>
|
||||
</AppContainer>
|
||||
);
|
||||
};
|
|
@ -1,13 +0,0 @@
|
|||
import { Link } from "@material-ui/core";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { useAppRedirect } from "../../lib/app/redirect";
|
||||
|
||||
export const AppLink = ({ children, href }: PropsWithChildren<{ href: string }>) => {
|
||||
const { redirect } = useAppRedirect();
|
||||
|
||||
return (
|
||||
<Link target={"_blank"} href={href} rel="noreferrer" onClick={() => redirect(href)}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
import { Paper } from "@material-ui/core";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import React from "react";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
padding: "16px",
|
||||
},
|
||||
});
|
||||
|
||||
export const AppPaper = ({ children }: { children: React.ReactNode }) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<Paper elevation={0} className={styles.root}>
|
||||
{children}
|
||||
</Paper>
|
||||
);
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
import { Link, makeStyles } from "@material-ui/core";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { AppTab } from "../../pages/configuration";
|
||||
import { useActiveTab } from "../taxes/tax-context";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
button: {
|
||||
fontSize: "inherit",
|
||||
fontFamily: "inherit",
|
||||
verticalAlign: "unset",
|
||||
},
|
||||
}));
|
||||
|
||||
export const AppTabNavButton = ({ children, to }: PropsWithChildren<{ to: AppTab }>) => {
|
||||
const styles = useStyles();
|
||||
const { setActiveTab } = useActiveTab();
|
||||
|
||||
return (
|
||||
<Link className={styles.button} component="button" onClick={() => setActiveTab(to)}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
|
@ -1,382 +0,0 @@
|
|||
type CountryType = {
|
||||
code: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
// From https://bitbucket.org/atlassian/atlaskit-mk-2/raw/4ad0e56649c3e6c973e226b7efaeb28cb240ccb0/packages/core/select/src/data/countries.js
|
||||
export const countries: CountryType[] = [
|
||||
{ code: "AD", label: "Andorra" },
|
||||
{
|
||||
code: "AE",
|
||||
label: "United Arab Emirates",
|
||||
},
|
||||
{ code: "AF", label: "Afghanistan" },
|
||||
{
|
||||
code: "AG",
|
||||
label: "Antigua and Barbuda",
|
||||
},
|
||||
{ code: "AI", label: "Anguilla" },
|
||||
{ code: "AL", label: "Albania" },
|
||||
{ code: "AM", label: "Armenia" },
|
||||
{ code: "AO", label: "Angola" },
|
||||
{ code: "AQ", label: "Antarctica" },
|
||||
{ code: "AR", label: "Argentina" },
|
||||
{ code: "AS", label: "American Samoa" },
|
||||
{ code: "AT", label: "Austria" },
|
||||
{
|
||||
code: "AU",
|
||||
label: "Australia",
|
||||
},
|
||||
{ code: "AW", label: "Aruba" },
|
||||
{ code: "AX", label: "Alland Islands" },
|
||||
{ code: "AZ", label: "Azerbaijan" },
|
||||
{
|
||||
code: "BA",
|
||||
label: "Bosnia and Herzegovina",
|
||||
},
|
||||
{ code: "BB", label: "Barbados" },
|
||||
{ code: "BD", label: "Bangladesh" },
|
||||
{ code: "BE", label: "Belgium" },
|
||||
{ code: "BF", label: "Burkina Faso" },
|
||||
{ code: "BG", label: "Bulgaria" },
|
||||
{ code: "BH", label: "Bahrain" },
|
||||
{ code: "BI", label: "Burundi" },
|
||||
{ code: "BJ", label: "Benin" },
|
||||
{ code: "BL", label: "Saint Barthelemy" },
|
||||
{ code: "BM", label: "Bermuda" },
|
||||
{ code: "BN", label: "Brunei Darussalam" },
|
||||
{ code: "BO", label: "Bolivia" },
|
||||
{ code: "BR", label: "Brazil" },
|
||||
{ code: "BS", label: "Bahamas" },
|
||||
{ code: "BT", label: "Bhutan" },
|
||||
{ code: "BV", label: "Bouvet Island" },
|
||||
{ code: "BW", label: "Botswana" },
|
||||
{ code: "BY", label: "Belarus" },
|
||||
{ code: "BZ", label: "Belize" },
|
||||
{
|
||||
code: "CA",
|
||||
label: "Canada",
|
||||
},
|
||||
{
|
||||
code: "CC",
|
||||
label: "Cocos (Keeling) Islands",
|
||||
},
|
||||
{
|
||||
code: "CD",
|
||||
label: "Congo, Democratic Republic of the",
|
||||
},
|
||||
{
|
||||
code: "CF",
|
||||
label: "Central African Republic",
|
||||
},
|
||||
{
|
||||
code: "CG",
|
||||
label: "Congo, Republic of the",
|
||||
},
|
||||
{ code: "CH", label: "Switzerland" },
|
||||
{ code: "CI", label: "Cote d'Ivoire" },
|
||||
{ code: "CK", label: "Cook Islands" },
|
||||
{ code: "CL", label: "Chile" },
|
||||
{ code: "CM", label: "Cameroon" },
|
||||
{ code: "CN", label: "China" },
|
||||
{ code: "CO", label: "Colombia" },
|
||||
{ code: "CR", label: "Costa Rica" },
|
||||
{ code: "CU", label: "Cuba" },
|
||||
{ code: "CV", label: "Cape Verde" },
|
||||
{ code: "CW", label: "Curacao" },
|
||||
{ code: "CX", label: "Christmas Island" },
|
||||
{ code: "CY", label: "Cyprus" },
|
||||
{ code: "CZ", label: "Czech Republic" },
|
||||
{
|
||||
code: "DE",
|
||||
label: "Germany",
|
||||
},
|
||||
{ code: "DJ", label: "Djibouti" },
|
||||
{ code: "DK", label: "Denmark" },
|
||||
{ code: "DM", label: "Dominica" },
|
||||
{
|
||||
code: "DO",
|
||||
label: "Dominican Republic",
|
||||
},
|
||||
{ code: "DZ", label: "Algeria" },
|
||||
{ code: "EC", label: "Ecuador" },
|
||||
{ code: "EE", label: "Estonia" },
|
||||
{ code: "EG", label: "Egypt" },
|
||||
{ code: "EH", label: "Western Sahara" },
|
||||
{ code: "ER", label: "Eritrea" },
|
||||
{ code: "ES", label: "Spain" },
|
||||
{ code: "ET", label: "Ethiopia" },
|
||||
{ code: "FI", label: "Finland" },
|
||||
{ code: "FJ", label: "Fiji" },
|
||||
{
|
||||
code: "FK",
|
||||
label: "Falkland Islands (Malvinas)",
|
||||
},
|
||||
{
|
||||
code: "FM",
|
||||
label: "Micronesia, Federated States of",
|
||||
},
|
||||
{ code: "FO", label: "Faroe Islands" },
|
||||
{
|
||||
code: "FR",
|
||||
label: "France",
|
||||
},
|
||||
{ code: "GA", label: "Gabon" },
|
||||
{ code: "GB", label: "United Kingdom" },
|
||||
{ code: "GD", label: "Grenada" },
|
||||
{ code: "GE", label: "Georgia" },
|
||||
{ code: "GF", label: "French Guiana" },
|
||||
{ code: "GG", label: "Guernsey" },
|
||||
{ code: "GH", label: "Ghana" },
|
||||
{ code: "GI", label: "Gibraltar" },
|
||||
{ code: "GL", label: "Greenland" },
|
||||
{ code: "GM", label: "Gambia" },
|
||||
{ code: "GN", label: "Guinea" },
|
||||
{ code: "GP", label: "Guadeloupe" },
|
||||
{ code: "GQ", label: "Equatorial Guinea" },
|
||||
{ code: "GR", label: "Greece" },
|
||||
{
|
||||
code: "GS",
|
||||
label: "South Georgia and the South Sandwich Islands",
|
||||
},
|
||||
{ code: "GT", label: "Guatemala" },
|
||||
{ code: "GU", label: "Guam" },
|
||||
{ code: "GW", label: "Guinea-Bissau" },
|
||||
{ code: "GY", label: "Guyana" },
|
||||
{ code: "HK", label: "Hong Kong" },
|
||||
{
|
||||
code: "HM",
|
||||
label: "Heard Island and McDonald Islands",
|
||||
},
|
||||
{ code: "HN", label: "Honduras" },
|
||||
{ code: "HR", label: "Croatia" },
|
||||
{ code: "HT", label: "Haiti" },
|
||||
{ code: "HU", label: "Hungary" },
|
||||
{ code: "ID", label: "Indonesia" },
|
||||
{ code: "IE", label: "Ireland" },
|
||||
{ code: "IL", label: "Israel" },
|
||||
{ code: "IM", label: "Isle of Man" },
|
||||
{ code: "IN", label: "India" },
|
||||
{
|
||||
code: "IO",
|
||||
label: "British Indian Ocean Territory",
|
||||
},
|
||||
{ code: "IQ", label: "Iraq" },
|
||||
{
|
||||
code: "IR",
|
||||
label: "Iran, Islamic Republic of",
|
||||
},
|
||||
{ code: "IS", label: "Iceland" },
|
||||
{ code: "IT", label: "Italy" },
|
||||
{ code: "JE", label: "Jersey" },
|
||||
{ code: "JM", label: "Jamaica" },
|
||||
{ code: "JO", label: "Jordan" },
|
||||
{
|
||||
code: "JP",
|
||||
label: "Japan",
|
||||
},
|
||||
{ code: "KE", label: "Kenya" },
|
||||
{ code: "KG", label: "Kyrgyzstan" },
|
||||
{ code: "KH", label: "Cambodia" },
|
||||
{ code: "KI", label: "Kiribati" },
|
||||
{ code: "KM", label: "Comoros" },
|
||||
{
|
||||
code: "KN",
|
||||
label: "Saint Kitts and Nevis",
|
||||
},
|
||||
{
|
||||
code: "KP",
|
||||
label: "Korea, Democratic People's Republic of",
|
||||
},
|
||||
{ code: "KR", label: "Korea, Republic of" },
|
||||
{ code: "KW", label: "Kuwait" },
|
||||
{ code: "KY", label: "Cayman Islands" },
|
||||
{ code: "KZ", label: "Kazakhstan" },
|
||||
{
|
||||
code: "LA",
|
||||
label: "Lao People's Democratic Republic",
|
||||
},
|
||||
{ code: "LB", label: "Lebanon" },
|
||||
{ code: "LC", label: "Saint Lucia" },
|
||||
{ code: "LI", label: "Liechtenstein" },
|
||||
{ code: "LK", label: "Sri Lanka" },
|
||||
{ code: "LR", label: "Liberia" },
|
||||
{ code: "LS", label: "Lesotho" },
|
||||
{ code: "LT", label: "Lithuania" },
|
||||
{ code: "LU", label: "Luxembourg" },
|
||||
{ code: "LV", label: "Latvia" },
|
||||
{ code: "LY", label: "Libya" },
|
||||
{ code: "MA", label: "Morocco" },
|
||||
{ code: "MC", label: "Monaco" },
|
||||
{
|
||||
code: "MD",
|
||||
label: "Moldova, Republic of",
|
||||
},
|
||||
{ code: "ME", label: "Montenegro" },
|
||||
{
|
||||
code: "MF",
|
||||
label: "Saint Martin (French part)",
|
||||
},
|
||||
{ code: "MG", label: "Madagascar" },
|
||||
{ code: "MH", label: "Marshall Islands" },
|
||||
{
|
||||
code: "MK",
|
||||
label: "Macedonia, the Former Yugoslav Republic of",
|
||||
},
|
||||
{ code: "ML", label: "Mali" },
|
||||
{ code: "MM", label: "Myanmar" },
|
||||
{ code: "MN", label: "Mongolia" },
|
||||
{ code: "MO", label: "Macao" },
|
||||
{
|
||||
code: "MP",
|
||||
label: "Northern Mariana Islands",
|
||||
},
|
||||
{ code: "MQ", label: "Martinique" },
|
||||
{ code: "MR", label: "Mauritania" },
|
||||
{ code: "MS", label: "Montserrat" },
|
||||
{ code: "MT", label: "Malta" },
|
||||
{ code: "MU", label: "Mauritius" },
|
||||
{ code: "MV", label: "Maldives" },
|
||||
{ code: "MW", label: "Malawi" },
|
||||
{ code: "MX", label: "Mexico" },
|
||||
{ code: "MY", label: "Malaysia" },
|
||||
{ code: "MZ", label: "Mozambique" },
|
||||
{ code: "NA", label: "Namibia" },
|
||||
{ code: "NC", label: "New Caledonia" },
|
||||
{ code: "NE", label: "Niger" },
|
||||
{ code: "NF", label: "Norfolk Island" },
|
||||
{ code: "NG", label: "Nigeria" },
|
||||
{ code: "NI", label: "Nicaragua" },
|
||||
{ code: "NL", label: "Netherlands" },
|
||||
{ code: "NO", label: "Norway" },
|
||||
{ code: "NP", label: "Nepal" },
|
||||
{ code: "NR", label: "Nauru" },
|
||||
{ code: "NU", label: "Niue" },
|
||||
{ code: "NZ", label: "New Zealand" },
|
||||
{ code: "OM", label: "Oman" },
|
||||
{ code: "PA", label: "Panama" },
|
||||
{ code: "PE", label: "Peru" },
|
||||
{ code: "PF", label: "French Polynesia" },
|
||||
{ code: "PG", label: "Papua New Guinea" },
|
||||
{ code: "PH", label: "Philippines" },
|
||||
{ code: "PK", label: "Pakistan" },
|
||||
{ code: "PL", label: "Poland" },
|
||||
{
|
||||
code: "PM",
|
||||
label: "Saint Pierre and Miquelon",
|
||||
},
|
||||
{ code: "PN", label: "Pitcairn" },
|
||||
{ code: "PR", label: "Puerto Rico" },
|
||||
{
|
||||
code: "PS",
|
||||
label: "Palestine, State of",
|
||||
},
|
||||
{ code: "PT", label: "Portugal" },
|
||||
{ code: "PW", label: "Palau" },
|
||||
{ code: "PY", label: "Paraguay" },
|
||||
{ code: "QA", label: "Qatar" },
|
||||
{ code: "RE", label: "Reunion" },
|
||||
{ code: "RO", label: "Romania" },
|
||||
{ code: "RS", label: "Serbia" },
|
||||
{ code: "RU", label: "Russian Federation" },
|
||||
{ code: "RW", label: "Rwanda" },
|
||||
{ code: "SA", label: "Saudi Arabia" },
|
||||
{ code: "SB", label: "Solomon Islands" },
|
||||
{ code: "SC", label: "Seychelles" },
|
||||
{ code: "SD", label: "Sudan" },
|
||||
{ code: "SE", label: "Sweden" },
|
||||
{ code: "SG", label: "Singapore" },
|
||||
{ code: "SH", label: "Saint Helena" },
|
||||
{ code: "SI", label: "Slovenia" },
|
||||
{
|
||||
code: "SJ",
|
||||
label: "Svalbard and Jan Mayen",
|
||||
},
|
||||
{ code: "SK", label: "Slovakia" },
|
||||
{ code: "SL", label: "Sierra Leone" },
|
||||
{ code: "SM", label: "San Marino" },
|
||||
{ code: "SN", label: "Senegal" },
|
||||
{ code: "SO", label: "Somalia" },
|
||||
{ code: "SR", label: "Suriname" },
|
||||
{ code: "SS", label: "South Sudan" },
|
||||
{
|
||||
code: "ST",
|
||||
label: "Sao Tome and Principe",
|
||||
},
|
||||
{ code: "SV", label: "El Salvador" },
|
||||
{
|
||||
code: "SX",
|
||||
label: "Sint Maarten (Dutch part)",
|
||||
},
|
||||
{
|
||||
code: "SY",
|
||||
label: "Syrian Arab Republic",
|
||||
},
|
||||
{ code: "SZ", label: "Swaziland" },
|
||||
{
|
||||
code: "TC",
|
||||
label: "Turks and Caicos Islands",
|
||||
},
|
||||
{ code: "TD", label: "Chad" },
|
||||
{
|
||||
code: "TF",
|
||||
label: "French Southern Territories",
|
||||
},
|
||||
{ code: "TG", label: "Togo" },
|
||||
{ code: "TH", label: "Thailand" },
|
||||
{ code: "TJ", label: "Tajikistan" },
|
||||
{ code: "TK", label: "Tokelau" },
|
||||
{ code: "TL", label: "Timor-Leste" },
|
||||
{ code: "TM", label: "Turkmenistan" },
|
||||
{ code: "TN", label: "Tunisia" },
|
||||
{ code: "TO", label: "Tonga" },
|
||||
{ code: "TR", label: "Turkey" },
|
||||
{
|
||||
code: "TT",
|
||||
label: "Trinidad and Tobago",
|
||||
},
|
||||
{ code: "TV", label: "Tuvalu" },
|
||||
{
|
||||
code: "TW",
|
||||
label: "Taiwan, Province of China",
|
||||
},
|
||||
{
|
||||
code: "TZ",
|
||||
label: "United Republic of Tanzania",
|
||||
},
|
||||
{ code: "UA", label: "Ukraine" },
|
||||
{ code: "UG", label: "Uganda" },
|
||||
{
|
||||
code: "US",
|
||||
label: "United States",
|
||||
},
|
||||
{ code: "UY", label: "Uruguay" },
|
||||
{ code: "UZ", label: "Uzbekistan" },
|
||||
{
|
||||
code: "VA",
|
||||
label: "Holy See (Vatican City State)",
|
||||
},
|
||||
{
|
||||
code: "VC",
|
||||
label: "Saint Vincent and the Grenadines",
|
||||
},
|
||||
{ code: "VE", label: "Venezuela" },
|
||||
{
|
||||
code: "VG",
|
||||
label: "British Virgin Islands",
|
||||
},
|
||||
{
|
||||
code: "VI",
|
||||
label: "US Virgin Islands",
|
||||
},
|
||||
{ code: "VN", label: "Vietnam" },
|
||||
{ code: "VU", label: "Vanuatu" },
|
||||
{ code: "WF", label: "Wallis and Futuna" },
|
||||
{ code: "WS", label: "Samoa" },
|
||||
{ code: "XK", label: "Kosovo" },
|
||||
{ code: "YE", label: "Yemen" },
|
||||
{ code: "YT", label: "Mayotte" },
|
||||
{ code: "ZA", label: "South Africa" },
|
||||
{ code: "ZM", label: "Zambia" },
|
||||
{ code: "ZW", label: "Zimbabwe" },
|
||||
];
|
|
@ -1,53 +0,0 @@
|
|||
import { TextField } from "@material-ui/core";
|
||||
import { Autocomplete } from "@material-ui/lab";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { ControllerRenderProps } from "react-hook-form";
|
||||
import { ChannelConfig } from "../../channels-configuration/channels-config";
|
||||
import { countries } from "./countries";
|
||||
import React from "react";
|
||||
|
||||
type CountrySelectProps = ControllerRenderProps<ChannelConfig, "address.country">;
|
||||
|
||||
// TODO: replace with macaw-ui component
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
padding: "7px 9px !important",
|
||||
},
|
||||
clearIndicator: {
|
||||
marginRight: 2,
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const CountrySelect = React.forwardRef((p: CountrySelectProps, ref) => {
|
||||
const styles = useStyles();
|
||||
const { onChange, value } = p;
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
classes={{
|
||||
inputRoot: styles.root,
|
||||
clearIndicator: styles.clearIndicator,
|
||||
}}
|
||||
options={countries}
|
||||
onChange={(_, data) => onChange(data ? data.code : null)}
|
||||
value={
|
||||
countries.find((country) => {
|
||||
return value === country.code;
|
||||
}) ?? null
|
||||
}
|
||||
getOptionLabel={(option) => option.label}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
inputRef={ref}
|
||||
placeholder={"Country"}
|
||||
inputProps={{
|
||||
...params.inputProps,
|
||||
autoComplete: "new-password", // disable autocomplete and autofill
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
});
|
|
@ -1,52 +0,0 @@
|
|||
import { Typography } from "@material-ui/core";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import { AppLink } from "./app-link";
|
||||
import { AppPaper } from "./app-paper";
|
||||
import { AppTabNavButton } from "./app-tab-nav-button";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "16px",
|
||||
},
|
||||
list: {
|
||||
paddingLeft: "16px",
|
||||
margin: 0,
|
||||
color: "inherit",
|
||||
},
|
||||
}));
|
||||
|
||||
export const Instructions = () => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<AppPaper>
|
||||
<div className={styles.root}>
|
||||
<Typography variant="h4">Use external service for tax calculation</Typography>
|
||||
<Typography variant="body1">
|
||||
<ol className={styles.list}>
|
||||
<li>
|
||||
Go to <AppLink href="/taxes/channels">Tax Configuration</AppLink>. Make sure you chose{" "}
|
||||
<q>Use tax app</q> as the method of tax calculation for your channel.
|
||||
</li>
|
||||
<li>
|
||||
In the Tax App, go to the <AppTabNavButton to="providers">Providers</AppTabNavButton>{" "}
|
||||
tab to add an instance of your provider. Click <q>Add provider</q>, and select the tax
|
||||
provider you want to use. Fill in the configuration form and hit <q>Save</q>.
|
||||
</li>
|
||||
<li>
|
||||
Go to the <AppTabNavButton to="channels">Channels</AppTabNavButton> tab. Select a
|
||||
channel. In the <q>Channel tax provider</q> field, select the created instance. Fill
|
||||
in the rest of the form, and hit <q>Save</q>.
|
||||
</li>
|
||||
<li>
|
||||
Saleor will now use the channel's configured tax provider for order & checkout tax
|
||||
calculations.
|
||||
</li>
|
||||
</ol>
|
||||
</Typography>
|
||||
</div>
|
||||
</AppPaper>
|
||||
);
|
||||
};
|
|
@ -1,27 +1,16 @@
|
|||
import "../styles/globals.css";
|
||||
|
||||
import { Theme } from "@material-ui/core/styles";
|
||||
import "@saleor/macaw-ui/next/style";
|
||||
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
||||
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
||||
import {
|
||||
dark,
|
||||
light,
|
||||
SaleorThemeColors,
|
||||
ThemeProvider as MacawUIThemeProvider,
|
||||
} from "@saleor/macaw-ui";
|
||||
import React, { PropsWithChildren, useEffect } from "react";
|
||||
import { AppProps } from "next/app";
|
||||
import { ThemeProvider } from "@saleor/macaw-ui/next";
|
||||
|
||||
import { AppProps } from "next/app";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { GraphQLProvider } from "../providers/GraphQLProvider";
|
||||
import { ThemeSynchronizer } from "../lib/theme-synchronizer";
|
||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
||||
import { NoSSRWrapper } from "../lib/no-ssr-wrapper";
|
||||
|
||||
const themeOverrides: Partial<Theme> = {
|
||||
/**
|
||||
* You can override MacawUI theme here
|
||||
*/
|
||||
};
|
||||
import { GraphQLProvider } from "../providers/GraphQLProvider";
|
||||
import { NoSSRWrapper } from "@saleor/apps-shared";
|
||||
|
||||
/**
|
||||
* Ensure instance is a singleton.
|
||||
|
@ -29,36 +18,6 @@ const themeOverrides: Partial<Theme> = {
|
|||
*/
|
||||
export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined;
|
||||
|
||||
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 }>
|
||||
>;
|
||||
|
||||
function NextApp({ Component, pageProps }: AppProps) {
|
||||
/**
|
||||
* Configure JSS (used by MacawUI) for SSR. If Macaw is not used, can be removed.
|
||||
|
@ -75,7 +34,7 @@ function NextApp({ Component, pageProps }: AppProps) {
|
|||
<NoSSRWrapper>
|
||||
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
||||
<GraphQLProvider>
|
||||
<ThemeProvider palettes={palettes} overrides={themeOverrides} ssr>
|
||||
<ThemeProvider>
|
||||
<ThemeSynchronizer />
|
||||
<RoutePropagator />
|
||||
<Component {...pageProps} />
|
||||
|
|
|
@ -1,71 +1,5 @@
|
|||
import { makeStyles, PageTab, PageTabs } from "@saleor/macaw-ui";
|
||||
import { ChannelTaxProvider } from "../modules/channels/ui/channel-tax-provider";
|
||||
import { Channels } from "../modules/channels/ui/channels";
|
||||
import { Configuration } from "../modules/providers-configuration/ui/configuration";
|
||||
import { ProvidersInstances } from "../modules/providers-configuration/ui/providers-instances";
|
||||
import { useActiveTab } from "../modules/taxes/tax-context";
|
||||
import { AppContainer } from "../modules/ui/app-container";
|
||||
import { AppLayout } from "../modules/ui/app-layout";
|
||||
import { Instructions } from "../modules/ui/instructions";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
tabs: {
|
||||
margin: "16px 0",
|
||||
},
|
||||
});
|
||||
|
||||
const ChannelTab = () => {
|
||||
return (
|
||||
<>
|
||||
<Channels />
|
||||
<ChannelTaxProvider />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ProvidersTab = () => {
|
||||
return (
|
||||
<>
|
||||
<ProvidersInstances />
|
||||
<Configuration />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const tabs = {
|
||||
channels: {
|
||||
component: <ChannelTab />,
|
||||
label: "Channels",
|
||||
},
|
||||
providers: {
|
||||
component: <ProvidersTab />,
|
||||
label: "Providers",
|
||||
},
|
||||
};
|
||||
|
||||
export type AppTab = keyof typeof tabs;
|
||||
|
||||
const ConfigurationPage = () => {
|
||||
const styles = useStyles();
|
||||
const { activeTab, setActiveTab } = useActiveTab();
|
||||
|
||||
return (
|
||||
<main>
|
||||
<AppContainer>
|
||||
<div className={styles.tabs}>
|
||||
<PageTabs value={activeTab} onChange={(value) => setActiveTab(value as AppTab)}>
|
||||
{Object.entries(tabs).map(([key, config]) => (
|
||||
<PageTab key={key} value={key} label={config.label} />
|
||||
))}
|
||||
</PageTabs>
|
||||
</div>
|
||||
</AppContainer>
|
||||
<AppLayout>
|
||||
{tabs[activeTab].component}
|
||||
<Instructions />
|
||||
</AppLayout>
|
||||
</main>
|
||||
);
|
||||
return <main>Configuration</main>;
|
||||
};
|
||||
|
||||
export default ConfigurationPage;
|
||||
|
|
|
@ -3,7 +3,6 @@ import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|||
import { useEffect } from "react";
|
||||
import { useIsMounted } from "usehooks-ts";
|
||||
import { useRouter } from "next/router";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
import { isInIframe } from "@saleor/apps-shared";
|
||||
|
||||
const IndexPage: NextPage = () => {
|
||||
|
@ -18,7 +17,7 @@ const IndexPage: NextPage = () => {
|
|||
}, [isMounted, appBridgeState?.ready, replace]);
|
||||
|
||||
if (isInIframe()) {
|
||||
return <LinearProgress />;
|
||||
return <span>Loading...</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1508,27 +1508,15 @@ importers:
|
|||
'@hookform/resolvers':
|
||||
specifier: ^2.9.10
|
||||
version: 2.9.11(react-hook-form@7.43.1)
|
||||
'@material-ui/core':
|
||||
specifier: ^4.12.4
|
||||
version: 4.12.4(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@material-ui/icons':
|
||||
specifier: ^4.11.3
|
||||
version: 4.11.3(@material-ui/core@4.12.4)(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@material-ui/lab':
|
||||
specifier: 4.0.0-alpha.61
|
||||
version: 4.0.0-alpha.61(@material-ui/core@4.12.4)(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@saleor/app-sdk':
|
||||
specifier: 0.39.1
|
||||
version: 0.39.1(next@13.3.0)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@saleor/apps-shared':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/shared
|
||||
'@saleor/apps-ui':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/ui
|
||||
'@saleor/macaw-ui':
|
||||
specifier: ^0.7.2
|
||||
version: 0.7.2(@material-ui/core@4.12.4)(@material-ui/icons@4.11.3)(@material-ui/lab@4.0.0-alpha.61)(@types/react@18.0.27)(react-dom@18.2.0)(react-helmet@6.1.0)(react@18.2.0)
|
||||
specifier: ^0.8.0-pre.72
|
||||
version: 0.8.0-pre.84(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@sentry/nextjs':
|
||||
specifier: ^7.45.0
|
||||
version: 7.45.0(next@13.3.0)(react@18.2.0)
|
||||
|
@ -1640,19 +1628,19 @@ importers:
|
|||
version: 13.4.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@testing-library/react-hooks':
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0)
|
||||
version: 8.0.1(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@types/node':
|
||||
specifier: ^18.8.1
|
||||
version: 18.13.0
|
||||
'@types/react':
|
||||
specifier: ^18.0.21
|
||||
version: 18.0.27
|
||||
specifier: ~18.0.38
|
||||
version: 18.0.38
|
||||
'@types/react-dom':
|
||||
specifier: ^18.0.6
|
||||
version: 18.0.10
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0(vite@4.3.6)
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0(vite@4.3.6)
|
||||
eslint:
|
||||
specifier: 8.25.0
|
||||
version: 8.25.0
|
||||
|
@ -9677,6 +9665,29 @@ packages:
|
|||
react-error-boundary: 3.1.4(react@18.2.0)
|
||||
dev: true
|
||||
|
||||
/@testing-library/react-hooks@8.0.1(@types/react@18.0.38)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.9.0 || ^17.0.0
|
||||
react: ^16.9.0 || ^17.0.0
|
||||
react-dom: ^16.9.0 || ^17.0.0
|
||||
react-test-renderer: ^16.9.0 || ^17.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
react-test-renderer:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@types/react': 18.0.38
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-error-boundary: 3.1.4(react@18.2.0)
|
||||
dev: true
|
||||
|
||||
/@testing-library/react@13.4.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
Loading…
Reference in a new issue