Skip to content

Commit d350145

Browse files
authored
Merge pull request #946 from evershopcommerce/shipment-refactoring-929
Shipment refactoring 929
2 parents d027756 + 6e69388 commit d350145

257 files changed

Lines changed: 15468 additions & 3603 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ cypress
3030
.prettierrc
3131
mysqlToPostgres.js
3232
/docs
33-
extensions
33+
extensions
34+
/wiki
35+
/CLAUDE.md

packages/evershop/src/bin/build/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { lockHooks } from '../../lib/util/hookable.js';
99
import { lockRegistry } from '../../lib/util/registry.js';
1010
import { validateConfiguration } from '../../lib/util/validateConfiguration.js';
1111
import { isBuildRequired } from '../../lib/webpack/isBuildRequired.js';
12+
import { lockCarrierRegistry } from '../../modules/oms/services/carrier/registry.js';
1213
import { getEnabledExtensions } from '../extension/index.js';
1314
import { loadBootstrapScript } from '../lib/bootstrap/bootstrap.js';
1415
import { buildEntry } from '../lib/buildEntry.js';
@@ -50,6 +51,7 @@ export default async function build() {
5051
}
5152
lockHooks();
5253
lockRegistry();
54+
lockCarrierRegistry();
5355
// Get the configuration (nodeconfig)
5456
validateConfiguration(config);
5557
} catch (e) {

packages/evershop/src/bin/install/templates/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"currency": "USD",
44
"language": "en",
55
"weightUnit": "kg",
6+
"dimensionUnit": "cm",
67
"timezone": "UTC"
78
},
89
"system": {

packages/evershop/src/bin/lib/bootstrap/migrate.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@ async function migrateModule(module, connection = null) {
3434
const migrations = readdirSync(path.resolve(module.path, 'migration'), {
3535
withFileTypes: true
3636
})
37+
// Match `Version-X.Y.Z.js` where each segment is one or more digits. The
38+
// previous regex (`Version-+([1-9].[0-9].[0-9])+.js`) had three traps:
39+
// single-digit-only segments (so `1.0.10` silently dropped on the floor),
40+
// unescaped dots that matched any character, and a useless `+` after the
41+
// capture group.
3742
.filter(
3843
(dirent) =>
3944
dirent.isFile() &&
40-
dirent.name.match(/^Version-+([1-9].[0-9].[0-9])+.js$/)
45+
dirent.name.match(/^Version-(\d+\.\d+\.\d+)\.js$/)
4146
)
4247
.map((dirent) => dirent.name.replace('Version-', '').replace('.js', ''))
4348
.sort((first, second) => semver.lt(first, second));

packages/evershop/src/bin/lib/startUp.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { lockHooks } from '../../lib/util/hookable.js';
99
import isDevelopmentMode from '../../lib/util/isDevelopmentMode.js';
1010
import { lockRegistry } from '../../lib/util/registry.js';
1111
import { validateConfiguration } from '../../lib/util/validateConfiguration.js';
12+
import { lockCarrierRegistry } from '../../modules/oms/services/carrier/registry.js';
1213
import { getEnabledExtensions } from '../extension/index.js';
1314
import { createApp } from './app.js';
1415
import { loadBootstrapScript } from './bootstrap/bootstrap.js';
@@ -36,6 +37,7 @@ export const start = async function start(context, cb) {
3637
}
3738
lockHooks();
3839
lockRegistry();
40+
lockCarrierRegistry();
3941
// Get the configuration (nodeconfig)
4042
validateConfiguration(config);
4143
} catch (e) {
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { CheckboxField } from '@components/common/form/CheckboxField.js';
2+
import { Form } from '@components/common/form/Form.js';
3+
import { InputField } from '@components/common/form/InputField.js';
4+
import { NumberField } from '@components/common/form/NumberField.js';
5+
import { Button } from '@components/common/ui/Button.js';
6+
import axios from 'axios';
7+
import React from 'react';
8+
import { useForm } from 'react-hook-form';
9+
import { toast } from 'react-toastify';
10+
11+
export interface PackageData {
12+
uuid: string;
13+
name: string;
14+
length: number;
15+
width: number;
16+
height: number;
17+
weight: { value: number; unit: string };
18+
isDefault: boolean;
19+
updateApi: string;
20+
deleteApi: string;
21+
}
22+
23+
export interface PackageFormProps {
24+
/** POST to create, PATCH to update. */
25+
formMethod: 'POST' | 'PATCH';
26+
saveApi: string;
27+
/** Receives the raw created/updated `package` row from the API response. */
28+
onSuccess: (row?: Record<string, unknown>) => void;
29+
reload?: () => void;
30+
pkg?: PackageData;
31+
dimensionUnit?: string;
32+
weightUnit?: string;
33+
}
34+
35+
/**
36+
* Create/edit form for a package (parcel size). Height may be 0 — a flat
37+
* envelope. `weight` is the TARE (the empty package's own weight) and is
38+
* optional; it is added to the shipping weight for quotes and labels.
39+
* Submits via axios so business errors from the API (duplicate name,
40+
* default-swap rules) surface as toasts.
41+
*/
42+
export function PackageForm({
43+
formMethod,
44+
saveApi,
45+
onSuccess,
46+
reload,
47+
pkg,
48+
dimensionUnit,
49+
weightUnit
50+
}: PackageFormProps) {
51+
const form = useForm();
52+
const [saving, setSaving] = React.useState(false);
53+
54+
const onSubmit = form.handleSubmit(async (values) => {
55+
setSaving(true);
56+
try {
57+
const response = await axios({
58+
method: formMethod,
59+
url: saveApi,
60+
data: values,
61+
validateStatus: () => true
62+
});
63+
if (response.data?.error) {
64+
toast.error(response.data.error.message);
65+
} else {
66+
toast.success(
67+
formMethod === 'POST' ? 'Package created' : 'Package saved'
68+
);
69+
onSuccess(response.data?.data);
70+
if (reload) reload();
71+
}
72+
} catch (e) {
73+
toast.error((e as Error).message);
74+
} finally {
75+
setSaving(false);
76+
}
77+
});
78+
79+
return (
80+
<Form
81+
id="packageForm"
82+
method={formMethod}
83+
action={saveApi}
84+
submitBtn={false}
85+
onSuccess={() => {
86+
/* we submit via axios — Form's submit path isn't used */
87+
}}
88+
form={form}
89+
>
90+
<div className="space-y-3">
91+
<h3 className="text-lg font-semibold">
92+
{formMethod === 'POST' ? 'Create a new package' : 'Edit package'}
93+
</h3>
94+
<InputField
95+
name="name"
96+
label="Name"
97+
placeholder="e.g. Small Box"
98+
required
99+
validation={{ required: 'Name is required' }}
100+
defaultValue={pkg?.name}
101+
/>
102+
<div className="grid grid-cols-3 gap-x-3">
103+
<NumberField
104+
name="length"
105+
label="Length"
106+
unit={dimensionUnit}
107+
required
108+
validation={{
109+
required: 'Length is required',
110+
min: { value: 0.01, message: 'Length must be greater than 0' }
111+
}}
112+
defaultValue={pkg?.length}
113+
/>
114+
<NumberField
115+
name="width"
116+
label="Width"
117+
unit={dimensionUnit}
118+
required
119+
validation={{
120+
required: 'Width is required',
121+
min: { value: 0.01, message: 'Width must be greater than 0' }
122+
}}
123+
defaultValue={pkg?.width}
124+
/>
125+
<NumberField
126+
name="height"
127+
label="Height"
128+
unit={dimensionUnit}
129+
required
130+
validation={{
131+
required: 'Height is required (0 for envelopes)',
132+
min: { value: 0, message: 'Height must be 0 or greater' }
133+
}}
134+
helperText="0 for flat envelopes"
135+
defaultValue={pkg?.height}
136+
/>
137+
</div>
138+
<NumberField
139+
name="weight"
140+
label="Empty package weight"
141+
unit={weightUnit}
142+
validation={{
143+
min: { value: 0, message: 'Weight must be 0 or greater' }
144+
}}
145+
helperText="Optional. Added to the shipping weight for quotes and labels."
146+
defaultValue={pkg?.weight?.value ?? 0}
147+
/>
148+
<CheckboxField
149+
name="is_default"
150+
label="Default package"
151+
defaultValue={pkg?.isDefault === true}
152+
helperText="Preselected for new products. Exactly one package is the default."
153+
/>
154+
<div className="flex justify-end gap-2 pt-2">
155+
<Button
156+
title="Save"
157+
variant="default"
158+
type="button"
159+
disabled={saving}
160+
onClick={onSubmit}
161+
>
162+
{saving ? 'Saving…' : 'Save'}
163+
</Button>
164+
</div>
165+
</div>
166+
</Form>
167+
);
168+
}

packages/evershop/src/components/common/form/Editor.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -126,38 +126,38 @@ const SortableRow: React.FC<{
126126
>
127127
<path
128128
stroke="currentColor"
129-
stroke-linecap="round"
130-
stroke-width="2.6"
129+
strokeLinecap="round"
130+
strokeWidth="2.6"
131131
d="M9.40999 7.29999H9.4"
132132
></path>
133133
<path
134134
stroke="currentColor"
135-
stroke-linecap="round"
136-
stroke-width="2.6"
135+
strokeLinecap="round"
136+
strokeWidth="2.6"
137137
d="M14.6 7.29999H14.59"
138138
></path>
139139
<path
140140
stroke="currentColor"
141-
stroke-linecap="round"
142-
stroke-width="2.6"
141+
strokeLinecap="round"
142+
strokeWidth="2.6"
143143
d="M9.30999 12H9.3"
144144
></path>
145145
<path
146146
stroke="currentColor"
147-
stroke-linecap="round"
148-
stroke-width="2.6"
147+
strokeLinecap="round"
148+
strokeWidth="2.6"
149149
d="M14.6 12H14.59"
150150
></path>
151151
<path
152152
stroke="currentColor"
153-
stroke-linecap="round"
154-
stroke-width="2.6"
153+
strokeLinecap="round"
154+
strokeWidth="2.6"
155155
d="M9.40999 16.7H9.4"
156156
></path>
157157
<path
158158
stroke="currentColor"
159-
stroke-linecap="round"
160-
stroke-width="2.6"
159+
strokeLinecap="round"
160+
strokeWidth="2.6"
161161
d="M14.6 16.7H14.59"
162162
></path>
163163
</svg>

packages/evershop/src/components/common/form/Form.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,20 @@ export function Form<T extends FieldValues = FieldValues>({
130130
return (
131131
<FormProvider {...theForm}>
132132
<form
133-
onSubmit={handleSubmit(handleFormSubmit, onValidationError)}
133+
onSubmit={(event) => {
134+
// Nested forms must not submit this form. Dialogs render through
135+
// React portals, and portals bubble events through the REACT tree
136+
// (not the DOM tree) — so a popup form mounted inside this form
137+
// (e.g. the core method editor inside the provider-settings page
138+
// form) fires this handler too. Only handle submissions that
139+
// originate from THIS form element, and stop our own submission
140+
// from reaching ancestor forms.
141+
if (event.target !== event.currentTarget) {
142+
return;
143+
}
144+
event.stopPropagation();
145+
handleSubmit(handleFormSubmit, onValidationError)(event);
146+
}}
134147
className={className}
135148
noValidate={noValidate}
136149
{...props}

packages/evershop/src/components/common/ui/AlertDialog.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ function AlertDialogOverlay({
2424
...props
2525
}: AlertDialogPrimitive.Backdrop.Props) {
2626
return (
27+
// z sits ABOVE Dialog (content z-1001): an alert dialog is a terminal
28+
// confirmation, often triggered from inside a Dialog, so it must stack on
29+
// top of it — not behind. Overlay 1002 < content 1003.
2730
<AlertDialogPrimitive.Backdrop
2831
data-slot="alert-dialog-overlay"
2932
className={cn(
30-
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50',
33+
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-1002',
3134
className
3235
)}
3336
{...props}
@@ -49,7 +52,7 @@ function AlertDialogContent({
4952
data-slot="alert-dialog-content"
5053
data-size={size}
5154
className={cn(
52-
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none',
55+
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg group/alert-dialog-content fixed top-1/2 left-1/2 z-1003 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none',
5356
className
5457
)}
5558
{...props}

0 commit comments

Comments
 (0)