@vendure/dashboard 3.4.3-master-202509230228 → 3.4.3-master-202509250229
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.html +11 -12
- package/package.json +4 -4
- package/src/app/common/use-page-title.test.ts +263 -0
- package/src/app/common/use-page-title.ts +86 -0
- package/src/app/routes/__root.tsx +4 -4
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-container.tsx +2 -2
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-types.ts +5 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-utils.tsx +124 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history.tsx +91 -59
- package/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx +176 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +4 -2
- package/src/app/routes/_authenticated/_customers/customers.graphql.ts +2 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx +98 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-container.tsx +9 -7
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-types.ts +5 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +173 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history.tsx +64 -408
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +4 -0
- package/src/app/routes/_authenticated/_orders/utils/order-detail-loaders.tsx +9 -4
- package/src/app/routes/_authenticated/_shipping-methods/components/metadata-badges.tsx +15 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/price-display.tsx +21 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/shipping-method-test-result-wrapper.tsx +87 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/test-address-form.tsx +255 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/test-order-builder.tsx +243 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods-result.tsx +97 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods-sheet.tsx +41 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods.tsx +74 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/test-single-method-result.tsx +90 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/test-single-shipping-method-sheet.tsx +56 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/test-single-shipping-method.tsx +82 -0
- package/src/app/routes/_authenticated/_shipping-methods/components/use-shipping-method-test-state.ts +67 -0
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +27 -0
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +2 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +24 -4
- package/src/lib/components/shared/history-timeline/history-note-entry.tsx +65 -0
- package/src/lib/components/shared/history-timeline/history-timeline-with-grouping.tsx +141 -0
- package/src/lib/components/shared/history-timeline/use-history-note-editor.ts +26 -0
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +5 -0
- package/src/lib/framework/extension-api/extension-api-types.ts +7 -0
- package/src/lib/framework/extension-api/logic/history-entries.ts +24 -0
- package/src/lib/framework/extension-api/logic/index.ts +1 -0
- package/src/lib/framework/extension-api/types/history-entries.ts +120 -0
- package/src/lib/framework/extension-api/types/index.ts +1 -0
- package/src/lib/framework/history-entry/history-entry-extensions.ts +11 -0
- package/src/lib/framework/history-entry/history-entry.tsx +129 -0
- package/src/lib/framework/registry/registry-types.ts +2 -0
- package/src/lib/index.ts +5 -1
- package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-method-dialog.tsx +0 -32
- package/src/lib/components/shared/history-timeline/history-entry.tsx +0 -188
package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods-sheet.tsx
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
2
|
+
import {
|
|
3
|
+
Sheet,
|
|
4
|
+
SheetContent,
|
|
5
|
+
SheetDescription,
|
|
6
|
+
SheetHeader,
|
|
7
|
+
SheetTitle,
|
|
8
|
+
SheetTrigger,
|
|
9
|
+
} from '@/vdb/components/ui/sheet.js';
|
|
10
|
+
import { Trans } from '@/vdb/lib/trans.js';
|
|
11
|
+
import { FlaskConical } from 'lucide-react';
|
|
12
|
+
import { useState } from 'react';
|
|
13
|
+
import { TestShippingMethods } from './test-shipping-methods.js';
|
|
14
|
+
|
|
15
|
+
export function TestShippingMethodsSheet() {
|
|
16
|
+
const [open, setOpen] = useState(false);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Sheet open={open} onOpenChange={setOpen}>
|
|
20
|
+
<SheetTrigger asChild>
|
|
21
|
+
<Button variant="secondary">
|
|
22
|
+
<FlaskConical />
|
|
23
|
+
<Trans>Test</Trans>
|
|
24
|
+
</Button>
|
|
25
|
+
</SheetTrigger>
|
|
26
|
+
<SheetContent className="w-[800px] sm:max-w-[800px]">
|
|
27
|
+
<SheetHeader>
|
|
28
|
+
<SheetTitle>
|
|
29
|
+
<Trans>Test shipping methods</Trans>
|
|
30
|
+
</SheetTitle>
|
|
31
|
+
<SheetDescription>
|
|
32
|
+
<Trans>Test your shipping methods by simulating a new order.</Trans>
|
|
33
|
+
</SheetDescription>
|
|
34
|
+
</SheetHeader>
|
|
35
|
+
<div className="mt-6">
|
|
36
|
+
<TestShippingMethods />
|
|
37
|
+
</div>
|
|
38
|
+
</SheetContent>
|
|
39
|
+
</Sheet>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Accordion } from '@/vdb/components/ui/accordion.js';
|
|
2
|
+
import { api } from '@/vdb/graphql/api.js';
|
|
3
|
+
import { useQuery } from '@tanstack/react-query';
|
|
4
|
+
import { testEligibleShippingMethodsDocument } from '../shipping-methods.graphql.js';
|
|
5
|
+
import { TestAddressForm } from './test-address-form.js';
|
|
6
|
+
import { TestOrderBuilder } from './test-order-builder.js';
|
|
7
|
+
import { TestShippingMethodsResult } from './test-shipping-methods-result.js';
|
|
8
|
+
import { useShippingMethodTestState } from './use-shipping-method-test-state.js';
|
|
9
|
+
|
|
10
|
+
export function TestShippingMethods() {
|
|
11
|
+
const {
|
|
12
|
+
testAddress,
|
|
13
|
+
testOrderLines,
|
|
14
|
+
testDataUpdated,
|
|
15
|
+
hasTestedOnce,
|
|
16
|
+
expandedAccordions,
|
|
17
|
+
setExpandedAccordions,
|
|
18
|
+
allTestDataPresent,
|
|
19
|
+
handleAddressChange,
|
|
20
|
+
handleOrderLinesChange,
|
|
21
|
+
markTestRun,
|
|
22
|
+
} = useShippingMethodTestState();
|
|
23
|
+
|
|
24
|
+
const { data, isLoading, refetch } = useQuery({
|
|
25
|
+
queryKey: ['testEligibleShippingMethods', testAddress, testOrderLines],
|
|
26
|
+
queryFn: async () => {
|
|
27
|
+
if (!testAddress || !testOrderLines.length) {
|
|
28
|
+
return { testEligibleShippingMethods: [] };
|
|
29
|
+
}
|
|
30
|
+
return api.query(testEligibleShippingMethodsDocument, {
|
|
31
|
+
input: {
|
|
32
|
+
shippingAddress: testAddress,
|
|
33
|
+
lines: testOrderLines.map(l => ({
|
|
34
|
+
productVariantId: l.id,
|
|
35
|
+
quantity: l.quantity,
|
|
36
|
+
})),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
enabled: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const testResult = data?.testEligibleShippingMethods || [];
|
|
44
|
+
|
|
45
|
+
const runTest = () => {
|
|
46
|
+
if (allTestDataPresent) {
|
|
47
|
+
markTestRun();
|
|
48
|
+
refetch();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="space-y-6 overflow-y-auto max-h-[calc(100vh-200px)] px-4">
|
|
54
|
+
<Accordion
|
|
55
|
+
type="multiple"
|
|
56
|
+
value={expandedAccordions}
|
|
57
|
+
onValueChange={setExpandedAccordions}
|
|
58
|
+
className="w-full"
|
|
59
|
+
>
|
|
60
|
+
<TestOrderBuilder onOrderLinesChange={handleOrderLinesChange} />
|
|
61
|
+
<TestAddressForm onAddressChange={handleAddressChange} />
|
|
62
|
+
</Accordion>
|
|
63
|
+
|
|
64
|
+
<TestShippingMethodsResult
|
|
65
|
+
testResult={testResult}
|
|
66
|
+
okToRun={allTestDataPresent}
|
|
67
|
+
testDataUpdated={testDataUpdated}
|
|
68
|
+
hasTestedOnce={hasTestedOnce}
|
|
69
|
+
onRunTest={runTest}
|
|
70
|
+
loading={isLoading}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
package/src/app/routes/_authenticated/_shipping-methods/components/test-single-method-result.tsx
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useChannel } from '@/vdb/hooks/use-channel.js';
|
|
2
|
+
import { Trans } from '@/vdb/lib/trans.js';
|
|
3
|
+
import { ResultOf } from 'gql.tada';
|
|
4
|
+
import { Check } from 'lucide-react';
|
|
5
|
+
import { testShippingMethodDocument } from '../shipping-methods.graphql.js';
|
|
6
|
+
import { MetadataBadges } from './metadata-badges.js';
|
|
7
|
+
import { PriceDisplay } from './price-display.js';
|
|
8
|
+
import { ShippingMethodTestResultWrapper } from './shipping-method-test-result-wrapper.js';
|
|
9
|
+
|
|
10
|
+
export type TestShippingMethodResult = ResultOf<typeof testShippingMethodDocument>['testShippingMethod'];
|
|
11
|
+
|
|
12
|
+
interface TestSingleMethodResultProps {
|
|
13
|
+
testResult?: TestShippingMethodResult;
|
|
14
|
+
okToRun: boolean;
|
|
15
|
+
testDataUpdated: boolean;
|
|
16
|
+
hasTestedOnce: boolean;
|
|
17
|
+
onRunTest: () => void;
|
|
18
|
+
loading?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function TestSingleMethodResult({
|
|
22
|
+
testResult,
|
|
23
|
+
okToRun,
|
|
24
|
+
testDataUpdated,
|
|
25
|
+
hasTestedOnce,
|
|
26
|
+
onRunTest,
|
|
27
|
+
loading = false,
|
|
28
|
+
}: Readonly<TestSingleMethodResultProps>) {
|
|
29
|
+
const { activeChannel } = useChannel();
|
|
30
|
+
const currencyCode = activeChannel?.defaultCurrencyCode ?? 'USD';
|
|
31
|
+
const showEmptyState = testResult === undefined && hasTestedOnce && !testDataUpdated && !loading;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<ShippingMethodTestResultWrapper
|
|
35
|
+
okToRun={okToRun}
|
|
36
|
+
testDataUpdated={testDataUpdated}
|
|
37
|
+
hasTestedOnce={hasTestedOnce}
|
|
38
|
+
onRunTest={onRunTest}
|
|
39
|
+
loading={loading}
|
|
40
|
+
showEmptyState={showEmptyState}
|
|
41
|
+
emptyState={
|
|
42
|
+
<div className="text-center py-8 text-muted-foreground">
|
|
43
|
+
<Trans>Click "Run Test" to test this shipping method.</Trans>
|
|
44
|
+
</div>
|
|
45
|
+
}
|
|
46
|
+
loadingLabel={<Trans>Testing shipping method...</Trans>}
|
|
47
|
+
>
|
|
48
|
+
{testResult && (
|
|
49
|
+
<div className="space-y-4">
|
|
50
|
+
{testResult.eligible ? (
|
|
51
|
+
<div className="space-y-3">
|
|
52
|
+
{testResult.quote && (
|
|
53
|
+
<div className="p-3 border rounded-lg bg-muted/50">
|
|
54
|
+
<div className="flex justify-between items-center">
|
|
55
|
+
<div className="flex items-center gap-2">
|
|
56
|
+
<Check className="h-5 w-5 text-success" />
|
|
57
|
+
<span className="text-sm">
|
|
58
|
+
<Trans>Shipping method is eligible for this order</Trans>
|
|
59
|
+
</span>
|
|
60
|
+
</div>
|
|
61
|
+
<PriceDisplay
|
|
62
|
+
price={testResult.quote.price}
|
|
63
|
+
priceWithTax={testResult.quote.priceWithTax}
|
|
64
|
+
currencyCode={currencyCode}
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
<div className="flex-1">
|
|
68
|
+
<MetadataBadges metadata={testResult.quote.metadata} />
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
) : (
|
|
74
|
+
<div className="text-center py-8">
|
|
75
|
+
<p className="text-destructive">
|
|
76
|
+
<Trans>Shipping method is not eligible for this order</Trans>
|
|
77
|
+
</p>
|
|
78
|
+
<p className="text-sm text-muted-foreground mt-2">
|
|
79
|
+
<Trans>
|
|
80
|
+
This shipping method's eligibility checker conditions are not met for the
|
|
81
|
+
current order and shipping address.
|
|
82
|
+
</Trans>
|
|
83
|
+
</p>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</ShippingMethodTestResultWrapper>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
2
|
+
import {
|
|
3
|
+
Sheet,
|
|
4
|
+
SheetContent,
|
|
5
|
+
SheetDescription,
|
|
6
|
+
SheetHeader,
|
|
7
|
+
SheetTitle,
|
|
8
|
+
SheetTrigger,
|
|
9
|
+
} from '@/vdb/components/ui/sheet.js';
|
|
10
|
+
import { Trans } from '@/vdb/lib/trans.js';
|
|
11
|
+
import { VariablesOf } from 'gql.tada';
|
|
12
|
+
import { FlaskConical } from 'lucide-react';
|
|
13
|
+
import { useState } from 'react';
|
|
14
|
+
import { testShippingMethodDocument } from '../shipping-methods.graphql.js';
|
|
15
|
+
import { TestSingleShippingMethod } from './test-single-shipping-method.js';
|
|
16
|
+
|
|
17
|
+
interface TestSingleShippingMethodDialogProps {
|
|
18
|
+
checker?: VariablesOf<typeof testShippingMethodDocument>['input']['checker'];
|
|
19
|
+
calculator?: VariablesOf<typeof testShippingMethodDocument>['input']['calculator'];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function TestSingleShippingMethodSheet({
|
|
23
|
+
checker,
|
|
24
|
+
calculator,
|
|
25
|
+
}: Readonly<TestSingleShippingMethodDialogProps>) {
|
|
26
|
+
const [open, setOpen] = useState(false);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Sheet open={open} onOpenChange={setOpen}>
|
|
30
|
+
<SheetTrigger asChild>
|
|
31
|
+
<Button variant="secondary">
|
|
32
|
+
<FlaskConical />
|
|
33
|
+
<Trans>Test</Trans>
|
|
34
|
+
</Button>
|
|
35
|
+
</SheetTrigger>
|
|
36
|
+
<SheetContent className="w-[800px] sm:max-w-[800px]">
|
|
37
|
+
<SheetHeader>
|
|
38
|
+
<SheetTitle>
|
|
39
|
+
<Trans>Test Shipping Method</Trans>
|
|
40
|
+
</SheetTitle>
|
|
41
|
+
<SheetDescription>
|
|
42
|
+
<Trans>
|
|
43
|
+
Test this shipping method by simulating an order to see if it's eligible and what
|
|
44
|
+
the shipping cost would be.
|
|
45
|
+
</Trans>
|
|
46
|
+
</SheetDescription>
|
|
47
|
+
</SheetHeader>
|
|
48
|
+
<div className="mt-6">
|
|
49
|
+
{checker && calculator ? (
|
|
50
|
+
<TestSingleShippingMethod checker={checker} calculator={calculator} />
|
|
51
|
+
) : null}
|
|
52
|
+
</div>
|
|
53
|
+
</SheetContent>
|
|
54
|
+
</Sheet>
|
|
55
|
+
);
|
|
56
|
+
}
|
package/src/app/routes/_authenticated/_shipping-methods/components/test-single-shipping-method.tsx
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Accordion } from '@/vdb/components/ui/accordion.js';
|
|
2
|
+
import { api } from '@/vdb/graphql/api.js';
|
|
3
|
+
import { useQuery } from '@tanstack/react-query';
|
|
4
|
+
import { VariablesOf } from 'gql.tada';
|
|
5
|
+
import { testShippingMethodDocument } from '../shipping-methods.graphql.js';
|
|
6
|
+
import { TestAddressForm } from './test-address-form.js';
|
|
7
|
+
import { TestOrderBuilder } from './test-order-builder.js';
|
|
8
|
+
import { TestSingleMethodResult } from './test-single-method-result.js';
|
|
9
|
+
import { useShippingMethodTestState } from './use-shipping-method-test-state.js';
|
|
10
|
+
|
|
11
|
+
interface TestSingleShippingMethodProps {
|
|
12
|
+
checker: VariablesOf<typeof testShippingMethodDocument>['input']['checker'];
|
|
13
|
+
calculator: VariablesOf<typeof testShippingMethodDocument>['input']['calculator'];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function TestSingleShippingMethod({ checker, calculator }: Readonly<TestSingleShippingMethodProps>) {
|
|
17
|
+
const {
|
|
18
|
+
testAddress,
|
|
19
|
+
testOrderLines,
|
|
20
|
+
testDataUpdated,
|
|
21
|
+
hasTestedOnce,
|
|
22
|
+
expandedAccordions,
|
|
23
|
+
setExpandedAccordions,
|
|
24
|
+
allTestDataPresent,
|
|
25
|
+
handleAddressChange,
|
|
26
|
+
handleOrderLinesChange,
|
|
27
|
+
markTestRun,
|
|
28
|
+
} = useShippingMethodTestState();
|
|
29
|
+
|
|
30
|
+
const { data, isLoading, refetch } = useQuery({
|
|
31
|
+
queryKey: ['testShippingMethod', testAddress, testOrderLines, checker, calculator],
|
|
32
|
+
queryFn: async () => {
|
|
33
|
+
if (!testAddress || !testOrderLines.length) {
|
|
34
|
+
return { testShippingMethod: undefined };
|
|
35
|
+
}
|
|
36
|
+
return api.query(testShippingMethodDocument, {
|
|
37
|
+
input: {
|
|
38
|
+
shippingAddress: testAddress,
|
|
39
|
+
lines: testOrderLines.map(l => ({
|
|
40
|
+
productVariantId: l.id,
|
|
41
|
+
quantity: l.quantity,
|
|
42
|
+
})),
|
|
43
|
+
checker,
|
|
44
|
+
calculator,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
enabled: false,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const testResult = data?.testShippingMethod;
|
|
52
|
+
|
|
53
|
+
const runTest = () => {
|
|
54
|
+
if (allTestDataPresent) {
|
|
55
|
+
markTestRun();
|
|
56
|
+
refetch();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="space-y-6 overflow-y-auto max-h-[calc(100vh-200px)] px-4">
|
|
62
|
+
<Accordion
|
|
63
|
+
type="multiple"
|
|
64
|
+
value={expandedAccordions}
|
|
65
|
+
onValueChange={setExpandedAccordions}
|
|
66
|
+
className="w-full"
|
|
67
|
+
>
|
|
68
|
+
<TestOrderBuilder onOrderLinesChange={handleOrderLinesChange} />
|
|
69
|
+
<TestAddressForm onAddressChange={handleAddressChange} />
|
|
70
|
+
</Accordion>
|
|
71
|
+
|
|
72
|
+
<TestSingleMethodResult
|
|
73
|
+
testResult={testResult}
|
|
74
|
+
okToRun={allTestDataPresent}
|
|
75
|
+
testDataUpdated={testDataUpdated}
|
|
76
|
+
hasTestedOnce={hasTestedOnce}
|
|
77
|
+
onRunTest={runTest}
|
|
78
|
+
loading={isLoading}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
package/src/app/routes/_authenticated/_shipping-methods/components/use-shipping-method-test-state.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { TestAddress } from './test-address-form.js';
|
|
4
|
+
import { TestOrderLine } from './test-order-builder.js';
|
|
5
|
+
|
|
6
|
+
export function useShippingMethodTestState() {
|
|
7
|
+
const [testAddress, setTestAddress] = useState<TestAddress | null>(null);
|
|
8
|
+
const [testOrderLines, setTestOrderLines] = useState<TestOrderLine[]>([]);
|
|
9
|
+
const [testDataUpdated, setTestDataUpdated] = useState(true);
|
|
10
|
+
const [hasTestedOnce, setHasTestedOnce] = useState(false);
|
|
11
|
+
const [expandedAccordions, setExpandedAccordions] = useState<string[]>([
|
|
12
|
+
'test-order',
|
|
13
|
+
'shipping-address',
|
|
14
|
+
]);
|
|
15
|
+
const [lastTestedAddress, setLastTestedAddress] = useState<TestAddress | null>(null);
|
|
16
|
+
const [lastTestedOrderLines, setLastTestedOrderLines] = useState<TestOrderLine[]>([]);
|
|
17
|
+
|
|
18
|
+
const allTestDataPresent = !!(testAddress && testOrderLines && testOrderLines.length > 0);
|
|
19
|
+
|
|
20
|
+
const handleAddressChange = useCallback(
|
|
21
|
+
(address: TestAddress) => {
|
|
22
|
+
setTestAddress(address);
|
|
23
|
+
if (hasTestedOnce && JSON.stringify(address) !== JSON.stringify(lastTestedAddress)) {
|
|
24
|
+
setTestDataUpdated(true);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
[hasTestedOnce, lastTestedAddress],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const handleOrderLinesChange = useCallback(
|
|
31
|
+
(lines: TestOrderLine[]) => {
|
|
32
|
+
setTestOrderLines(lines);
|
|
33
|
+
if (hasTestedOnce && JSON.stringify(lines) !== JSON.stringify(lastTestedOrderLines)) {
|
|
34
|
+
setTestDataUpdated(true);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
[hasTestedOnce, lastTestedOrderLines],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// runTest now only updates state; actual query logic is handled in the component
|
|
41
|
+
const markTestRun = () => {
|
|
42
|
+
setTestDataUpdated(false);
|
|
43
|
+
setHasTestedOnce(true);
|
|
44
|
+
setLastTestedAddress(testAddress);
|
|
45
|
+
setLastTestedOrderLines(testOrderLines);
|
|
46
|
+
setExpandedAccordions([]); // Collapse all accordions
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
testAddress,
|
|
51
|
+
setTestAddress,
|
|
52
|
+
testOrderLines,
|
|
53
|
+
setTestOrderLines,
|
|
54
|
+
testDataUpdated,
|
|
55
|
+
setTestDataUpdated,
|
|
56
|
+
hasTestedOnce,
|
|
57
|
+
setHasTestedOnce,
|
|
58
|
+
expandedAccordions,
|
|
59
|
+
setExpandedAccordions,
|
|
60
|
+
lastTestedAddress,
|
|
61
|
+
lastTestedOrderLines,
|
|
62
|
+
allTestDataPresent,
|
|
63
|
+
handleAddressChange,
|
|
64
|
+
handleOrderLinesChange,
|
|
65
|
+
markTestRun,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -108,3 +108,30 @@ export const removeShippingMethodsFromChannelDocument = graphql(`
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
`);
|
|
111
|
+
|
|
112
|
+
export const testEligibleShippingMethodsDocument = graphql(`
|
|
113
|
+
query TestEligibleShippingMethods($input: TestEligibleShippingMethodsInput!) {
|
|
114
|
+
testEligibleShippingMethods(input: $input) {
|
|
115
|
+
id
|
|
116
|
+
name
|
|
117
|
+
code
|
|
118
|
+
description
|
|
119
|
+
price
|
|
120
|
+
priceWithTax
|
|
121
|
+
metadata
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
`);
|
|
125
|
+
|
|
126
|
+
export const testShippingMethodDocument = graphql(`
|
|
127
|
+
query TestShippingMethod($input: TestShippingMethodInput!) {
|
|
128
|
+
testShippingMethod(input: $input) {
|
|
129
|
+
eligible
|
|
130
|
+
quote {
|
|
131
|
+
price
|
|
132
|
+
priceWithTax
|
|
133
|
+
metadata
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
`);
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
DeleteShippingMethodsBulkAction,
|
|
12
12
|
RemoveShippingMethodsFromChannelBulkAction,
|
|
13
13
|
} from './components/shipping-method-bulk-actions.js';
|
|
14
|
-
import {
|
|
14
|
+
import { TestShippingMethodsSheet } from './components/test-shipping-methods-sheet.js';
|
|
15
15
|
import { shippingMethodListQuery } from './shipping-methods.graphql.js';
|
|
16
16
|
|
|
17
17
|
export const Route = createFileRoute('/_authenticated/_shipping-methods/shipping-methods')({
|
|
@@ -58,6 +58,7 @@ function ShippingMethodListPage() {
|
|
|
58
58
|
]}
|
|
59
59
|
>
|
|
60
60
|
<PageActionBarRight>
|
|
61
|
+
<TestShippingMethodsSheet />
|
|
61
62
|
<PermissionGuard requires={['CreateShippingMethod']}>
|
|
62
63
|
<Button asChild>
|
|
63
64
|
<Link to="./new">
|
|
@@ -66,7 +67,6 @@ function ShippingMethodListPage() {
|
|
|
66
67
|
</Link>
|
|
67
68
|
</Button>
|
|
68
69
|
</PermissionGuard>
|
|
69
|
-
<TestShippingMethodDialog />
|
|
70
70
|
</PageActionBarRight>
|
|
71
71
|
</ListPage>
|
|
72
72
|
);
|
|
@@ -24,6 +24,7 @@ import { toast } from 'sonner';
|
|
|
24
24
|
import { FulfillmentHandlerSelector } from './components/fulfillment-handler-selector.js';
|
|
25
25
|
import { ShippingCalculatorSelector } from './components/shipping-calculator-selector.js';
|
|
26
26
|
import { ShippingEligibilityCheckerSelector } from './components/shipping-eligibility-checker-selector.js';
|
|
27
|
+
import { TestSingleShippingMethodSheet } from './components/test-single-shipping-method-sheet.js';
|
|
27
28
|
import {
|
|
28
29
|
createShippingMethodDocument,
|
|
29
30
|
shippingMethodDetailDocument,
|
|
@@ -84,19 +85,35 @@ function ShippingMethodDetailPage() {
|
|
|
84
85
|
},
|
|
85
86
|
params: { id: params.id },
|
|
86
87
|
onSuccess: async data => {
|
|
87
|
-
toast.success(
|
|
88
|
+
toast.success(
|
|
89
|
+
i18n.t(
|
|
90
|
+
creatingNewEntity
|
|
91
|
+
? 'Successfully created shipping method'
|
|
92
|
+
: 'Successfully updated shipping method',
|
|
93
|
+
),
|
|
94
|
+
);
|
|
88
95
|
resetForm();
|
|
89
96
|
if (creatingNewEntity) {
|
|
90
97
|
await navigate({ to: `../$id`, params: { id: data.id } });
|
|
91
98
|
}
|
|
92
99
|
},
|
|
93
100
|
onError: err => {
|
|
94
|
-
toast.error(
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
toast.error(
|
|
102
|
+
i18n.t(
|
|
103
|
+
creatingNewEntity
|
|
104
|
+
? 'Failed to create shipping method'
|
|
105
|
+
: 'Failed to update shipping method',
|
|
106
|
+
),
|
|
107
|
+
{
|
|
108
|
+
description: err instanceof Error ? err.message : 'Unknown error',
|
|
109
|
+
},
|
|
110
|
+
);
|
|
97
111
|
},
|
|
98
112
|
});
|
|
99
113
|
|
|
114
|
+
const checker = form.watch('checker');
|
|
115
|
+
const calculator = form.watch('calculator');
|
|
116
|
+
|
|
100
117
|
return (
|
|
101
118
|
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
102
119
|
<PageTitle>
|
|
@@ -104,6 +121,9 @@ function ShippingMethodDetailPage() {
|
|
|
104
121
|
</PageTitle>
|
|
105
122
|
<PageActionBar>
|
|
106
123
|
<PageActionBarRight>
|
|
124
|
+
{!creatingNewEntity && entity && (
|
|
125
|
+
<TestSingleShippingMethodSheet checker={checker} calculator={calculator} />
|
|
126
|
+
)}
|
|
107
127
|
<PermissionGuard requires={['UpdateShippingMethod']}>
|
|
108
128
|
<Button
|
|
109
129
|
type="submit"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Badge } from '@/vdb/components/ui/badge.js';
|
|
2
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuTrigger,
|
|
8
|
+
} from '@/vdb/components/ui/dropdown-menu.js';
|
|
9
|
+
import { Separator } from '@/vdb/components/ui/separator.js';
|
|
10
|
+
import { HistoryEntry, HistoryEntryProps } from '@/vdb/framework/history-entry/history-entry.js';
|
|
11
|
+
import { Trans } from '@/vdb/lib/trans.js';
|
|
12
|
+
import { MoreVerticalIcon, PencilIcon, TrashIcon } from 'lucide-react';
|
|
13
|
+
|
|
14
|
+
interface HistoryNoteEntryProps extends Readonly<HistoryEntryProps> {
|
|
15
|
+
onEditNote?: (noteId: string, note: string, isPrivate: boolean) => void;
|
|
16
|
+
onDeleteNote?: (noteId: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function HistoryNoteEntry(props: HistoryNoteEntryProps) {
|
|
20
|
+
const { entry, isPrimary, onEditNote, onDeleteNote } = props;
|
|
21
|
+
return (
|
|
22
|
+
<HistoryEntry {...props}>
|
|
23
|
+
<div className={isPrimary ? 'space-y-2' : 'space-y-1'}>
|
|
24
|
+
<div className="space-y-1">
|
|
25
|
+
<p className={`${isPrimary ? 'text-sm' : 'text-xs'} text-foreground`}>
|
|
26
|
+
{entry.data.note}
|
|
27
|
+
</p>
|
|
28
|
+
</div>
|
|
29
|
+
{onEditNote && onDeleteNote && (
|
|
30
|
+
<div className="flex items-center gap-2">
|
|
31
|
+
<Badge variant={entry.isPublic ? 'outline' : 'secondary'} className="text-xs">
|
|
32
|
+
{entry.isPublic ? 'Public' : 'Private'}
|
|
33
|
+
</Badge>
|
|
34
|
+
<DropdownMenu>
|
|
35
|
+
<DropdownMenuTrigger asChild>
|
|
36
|
+
<Button variant="ghost" size="sm" className="h-6 w-6 p-0">
|
|
37
|
+
<MoreVerticalIcon className="h-3 w-3" />
|
|
38
|
+
</Button>
|
|
39
|
+
</DropdownMenuTrigger>
|
|
40
|
+
<DropdownMenuContent align="end">
|
|
41
|
+
<DropdownMenuItem
|
|
42
|
+
onClick={() => {
|
|
43
|
+
onEditNote(entry.id, entry.data.note, !entry.isPublic);
|
|
44
|
+
}}
|
|
45
|
+
className="cursor-pointer"
|
|
46
|
+
>
|
|
47
|
+
<PencilIcon className="mr-2 h-4 w-4" />
|
|
48
|
+
<Trans>Edit</Trans>
|
|
49
|
+
</DropdownMenuItem>
|
|
50
|
+
<Separator className="my-1" />
|
|
51
|
+
<DropdownMenuItem
|
|
52
|
+
onClick={() => onDeleteNote(entry.id)}
|
|
53
|
+
className="cursor-pointer text-red-600 focus:text-red-600"
|
|
54
|
+
>
|
|
55
|
+
<TrashIcon className="mr-2 h-4 w-4" />
|
|
56
|
+
<span>Delete</span>
|
|
57
|
+
</DropdownMenuItem>
|
|
58
|
+
</DropdownMenuContent>
|
|
59
|
+
</DropdownMenu>
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
</HistoryEntry>
|
|
64
|
+
);
|
|
65
|
+
}
|