includio-cms 0.15.2 → 0.15.3
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/CHANGELOG.md +25 -0
- package/DOCS.md +137 -2
- package/ROADMAP.md +7 -2
- package/dist/admin/client/shop/shipping-method-form.svelte +66 -1
- package/dist/admin/client/shop/shipping-method-form.svelte.d.ts +8 -0
- package/dist/admin/client/shop/shop-order-detail-page.svelte +101 -0
- package/dist/admin/remote/shop.remote.d.ts +44 -0
- package/dist/admin/remote/shop.remote.js +35 -0
- package/dist/cli/index.js +49 -4
- package/dist/cli/scaffold/admin.d.ts +9 -2
- package/dist/cli/scaffold/admin.js +32 -3
- package/dist/db-postgres/schema/shop/order.d.ts +68 -0
- package/dist/db-postgres/schema/shop/order.js +4 -0
- package/dist/db-postgres/schema/shop/shippingMethod.d.ts +25 -0
- package/dist/db-postgres/schema/shop/shippingMethod.js +1 -0
- package/dist/shop/adapters/inpost/geowidget.d.ts +27 -0
- package/dist/shop/adapters/inpost/geowidget.js +31 -0
- package/dist/shop/adapters/inpost/index.d.ts +89 -0
- package/dist/shop/adapters/inpost/index.js +156 -0
- package/dist/shop/adapters/inpost/payload.d.ts +18 -0
- package/dist/shop/adapters/inpost/payload.js +85 -0
- package/dist/shop/adapters/inpost/points-api.d.ts +17 -0
- package/dist/shop/adapters/inpost/points-api.js +55 -0
- package/dist/shop/adapters/inpost/shipx-client.d.ts +56 -0
- package/dist/shop/adapters/inpost/shipx-client.js +95 -0
- package/dist/shop/adapters/inpost/status-map.d.ts +9 -0
- package/dist/shop/adapters/inpost/status-map.js +46 -0
- package/dist/shop/adapters/inpost/webhook.d.ts +16 -0
- package/dist/shop/adapters/inpost/webhook.js +55 -0
- package/dist/shop/client/index.d.ts +5 -0
- package/dist/shop/http/carrier-handler.d.ts +12 -0
- package/dist/shop/http/carrier-handler.js +45 -0
- package/dist/shop/http/carrier-webhook-handler.d.ts +13 -0
- package/dist/shop/http/carrier-webhook-handler.js +66 -0
- package/dist/shop/http/checkout-handler.js +23 -1
- package/dist/shop/http/index.d.ts +3 -0
- package/dist/shop/http/index.js +3 -0
- package/dist/shop/http/order-handler.js +14 -0
- package/dist/shop/http/shipment-label-handler.d.ts +10 -0
- package/dist/shop/http/shipment-label-handler.js +53 -0
- package/dist/shop/http/shipping-handler.js +3 -0
- package/dist/shop/index.d.ts +3 -1
- package/dist/shop/index.js +1 -0
- package/dist/shop/server/email.js +37 -0
- package/dist/shop/server/orders.d.ts +9 -0
- package/dist/shop/server/orders.js +48 -0
- package/dist/shop/server/shipments.d.ts +33 -0
- package/dist/shop/server/shipments.js +145 -0
- package/dist/shop/server/shipping.d.ts +2 -1
- package/dist/shop/server/shipping.js +9 -0
- package/dist/shop/svelte/InpostPicker.svelte +270 -0
- package/dist/shop/svelte/InpostPicker.svelte.d.ts +51 -0
- package/dist/shop/svelte/OrderStatus.svelte +53 -1
- package/dist/shop/svelte/index.d.ts +1 -0
- package/dist/shop/svelte/index.js +1 -0
- package/dist/shop/svelte/labels.d.ts +5 -0
- package/dist/shop/svelte/labels.js +6 -1
- package/dist/shop/types.d.ts +49 -1
- package/dist/updates/0.15.3/index.d.ts +2 -0
- package/dist/updates/0.15.3/index.js +19 -0
- package/dist/updates/index.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface CarrierWidgetConfig {
|
|
5
|
+
token: string;
|
|
6
|
+
language?: string;
|
|
7
|
+
config?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface CarrierDescriptor {
|
|
11
|
+
id: string;
|
|
12
|
+
widget: {
|
|
13
|
+
scriptUrl: string | null;
|
|
14
|
+
stylesheetUrl: string | null;
|
|
15
|
+
config: CarrierWidgetConfig;
|
|
16
|
+
} | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface InpostPoint {
|
|
20
|
+
name: string;
|
|
21
|
+
address?: { line1?: string; line2?: string };
|
|
22
|
+
address_details?: Record<string, string>;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Props {
|
|
27
|
+
/** Currently selected paczkomat code (e.g. `KRA01M`). Two-way bindable. */
|
|
28
|
+
value?: string;
|
|
29
|
+
/** Optional pre-fetched config (skips network call). */
|
|
30
|
+
descriptor?: CarrierDescriptor | null;
|
|
31
|
+
/** Carrier id to fetch when no `descriptor` provided. Default: `inpost`. */
|
|
32
|
+
carrierId?: string;
|
|
33
|
+
/** Base URL prefix (when shop API lives on a different origin). */
|
|
34
|
+
apiBase?: string;
|
|
35
|
+
/** Override geowidget language. */
|
|
36
|
+
language?: string;
|
|
37
|
+
/** Override geowidget config preset (e.g. `parcelCollect247`). */
|
|
38
|
+
config?: string;
|
|
39
|
+
/** Custom fetch (e.g. SvelteKit `fetch` from a load function). */
|
|
40
|
+
fetchFn?: typeof fetch;
|
|
41
|
+
/**
|
|
42
|
+
* Hide picker when `serviceType` is a courier service (no parcel locker
|
|
43
|
+
* needed). Pass `shippingMethod.carrierConfig?.serviceType` from the
|
|
44
|
+
* public shipping methods API.
|
|
45
|
+
*/
|
|
46
|
+
serviceType?: string | null;
|
|
47
|
+
/** Called when user picks a paczkomat with the full point payload. */
|
|
48
|
+
onSelect?: (point: InpostPoint) => void;
|
|
49
|
+
class?: string;
|
|
50
|
+
style?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let {
|
|
54
|
+
value = $bindable(''),
|
|
55
|
+
descriptor = null,
|
|
56
|
+
carrierId = 'inpost',
|
|
57
|
+
apiBase = '',
|
|
58
|
+
language,
|
|
59
|
+
config,
|
|
60
|
+
fetchFn,
|
|
61
|
+
serviceType = null,
|
|
62
|
+
onSelect,
|
|
63
|
+
class: className = '',
|
|
64
|
+
style = ''
|
|
65
|
+
}: Props = $props();
|
|
66
|
+
|
|
67
|
+
let mountEl: HTMLDivElement | undefined = $state();
|
|
68
|
+
let loading = $state(false);
|
|
69
|
+
let error = $state<string | null>(null);
|
|
70
|
+
let resolved = $state<CarrierDescriptor | null>(descriptor);
|
|
71
|
+
let pointHandler: ((e: Event) => void) | null = null;
|
|
72
|
+
let pointEventName: string | null = null;
|
|
73
|
+
let geowidgetEl: HTMLElement | null = null;
|
|
74
|
+
|
|
75
|
+
const isCourier = $derived(
|
|
76
|
+
typeof serviceType === 'string' && serviceType.startsWith('inpost_courier_')
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
async function loadDescriptor(): Promise<CarrierDescriptor | null> {
|
|
80
|
+
if (resolved) return resolved;
|
|
81
|
+
const f = fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
82
|
+
const res = await f(`${apiBase}/api/shop/carriers/${encodeURIComponent(carrierId)}`);
|
|
83
|
+
if (!res.ok) throw new Error(`Carrier config fetch failed: ${res.status}`);
|
|
84
|
+
return (await res.json()) as CarrierDescriptor;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadStylesheet(href: string): void {
|
|
88
|
+
const exists = document.querySelector(`link[data-inpost-css="${href}"]`);
|
|
89
|
+
if (exists) return;
|
|
90
|
+
const link = document.createElement('link');
|
|
91
|
+
link.rel = 'stylesheet';
|
|
92
|
+
link.href = href;
|
|
93
|
+
link.dataset.inpostCss = href;
|
|
94
|
+
document.head.appendChild(link);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function loadScript(src: string): Promise<void> {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const existing = document.querySelector<HTMLScriptElement>(
|
|
100
|
+
`script[data-inpost-script="${src}"]`
|
|
101
|
+
);
|
|
102
|
+
if (existing) {
|
|
103
|
+
if (existing.dataset.loaded === '1') return resolve();
|
|
104
|
+
existing.addEventListener('load', () => resolve(), { once: true });
|
|
105
|
+
existing.addEventListener(
|
|
106
|
+
'error',
|
|
107
|
+
() => reject(new Error(`Geowidget script failed: ${src}`)),
|
|
108
|
+
{ once: true }
|
|
109
|
+
);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const s = document.createElement('script');
|
|
113
|
+
s.src = src;
|
|
114
|
+
s.async = true;
|
|
115
|
+
s.defer = true;
|
|
116
|
+
s.dataset.inpostScript = src;
|
|
117
|
+
s.addEventListener(
|
|
118
|
+
'load',
|
|
119
|
+
() => {
|
|
120
|
+
s.dataset.loaded = '1';
|
|
121
|
+
resolve();
|
|
122
|
+
},
|
|
123
|
+
{ once: true }
|
|
124
|
+
);
|
|
125
|
+
s.addEventListener(
|
|
126
|
+
'error',
|
|
127
|
+
() => reject(new Error(`Geowidget script failed: ${src}`)),
|
|
128
|
+
{ once: true }
|
|
129
|
+
);
|
|
130
|
+
document.head.appendChild(s);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function mountWidget(d: CarrierDescriptor): void {
|
|
135
|
+
if (!mountEl || !d.widget) return;
|
|
136
|
+
mountEl.innerHTML = '';
|
|
137
|
+
|
|
138
|
+
// `onpoint` attribute is the *name of a custom event* that Geowidget
|
|
139
|
+
// dispatches on `document` after a user picks a point. Use a unique name
|
|
140
|
+
// per instance so multiple pickers don't fight over the same listener.
|
|
141
|
+
pointEventName =
|
|
142
|
+
'aria-inpost-pick-' + Math.random().toString(36).slice(2, 10);
|
|
143
|
+
|
|
144
|
+
const el = document.createElement('inpost-geowidget');
|
|
145
|
+
el.setAttribute('token', d.widget.config.token);
|
|
146
|
+
el.setAttribute('language', language ?? d.widget.config.language ?? 'pl');
|
|
147
|
+
el.setAttribute('config', config ?? d.widget.config.config ?? 'parcelcollect');
|
|
148
|
+
el.setAttribute('onpoint', pointEventName);
|
|
149
|
+
if (value) el.setAttribute('selected_point', value);
|
|
150
|
+
|
|
151
|
+
pointHandler = (e: Event) => {
|
|
152
|
+
const ce = e as CustomEvent<InpostPoint> & { details?: InpostPoint };
|
|
153
|
+
const detail = ce.detail ?? ce.details;
|
|
154
|
+
if (!detail || typeof detail.name !== 'string') return;
|
|
155
|
+
value = detail.name;
|
|
156
|
+
onSelect?.(detail);
|
|
157
|
+
};
|
|
158
|
+
document.addEventListener(pointEventName, pointHandler);
|
|
159
|
+
mountEl.appendChild(el);
|
|
160
|
+
geowidgetEl = el;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
onMount(async () => {
|
|
164
|
+
if (isCourier) return;
|
|
165
|
+
loading = true;
|
|
166
|
+
error = null;
|
|
167
|
+
try {
|
|
168
|
+
const d = await loadDescriptor();
|
|
169
|
+
resolved = d;
|
|
170
|
+
if (!d?.widget?.scriptUrl) {
|
|
171
|
+
throw new Error('Geowidget script URL missing in carrier config.');
|
|
172
|
+
}
|
|
173
|
+
if (d.widget.stylesheetUrl) loadStylesheet(d.widget.stylesheetUrl);
|
|
174
|
+
await loadScript(d.widget.scriptUrl);
|
|
175
|
+
mountWidget(d);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
error = err instanceof Error ? err.message : 'Failed to load InPost widget.';
|
|
178
|
+
} finally {
|
|
179
|
+
loading = false;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
onDestroy(() => {
|
|
184
|
+
if (pointEventName && pointHandler) {
|
|
185
|
+
document.removeEventListener(pointEventName, pointHandler);
|
|
186
|
+
}
|
|
187
|
+
geowidgetEl = null;
|
|
188
|
+
pointHandler = null;
|
|
189
|
+
pointEventName = null;
|
|
190
|
+
});
|
|
191
|
+
</script>
|
|
192
|
+
|
|
193
|
+
{#if isCourier}
|
|
194
|
+
<div class="aria-inpost-courier-note {className}" {style} part="courier-note">
|
|
195
|
+
Kurier dostarczy paczkę pod podany adres — wybór paczkomatu nie jest potrzebny.
|
|
196
|
+
</div>
|
|
197
|
+
{:else}
|
|
198
|
+
<div class="aria-inpost-picker {className}" {style} part="root">
|
|
199
|
+
{#if loading && !resolved}
|
|
200
|
+
<div class="state-loading" part="loading" role="status" aria-live="polite">
|
|
201
|
+
Ładowanie wyboru paczkomatu…
|
|
202
|
+
</div>
|
|
203
|
+
{/if}
|
|
204
|
+
{#if error}
|
|
205
|
+
<div class="state-error" part="error" role="alert">{error}</div>
|
|
206
|
+
{/if}
|
|
207
|
+
|
|
208
|
+
<div class="mount" bind:this={mountEl} part="mount"></div>
|
|
209
|
+
|
|
210
|
+
{#if value}
|
|
211
|
+
<div class="selected" part="selected" aria-live="polite">
|
|
212
|
+
Wybrany paczkomat: <strong>{value}</strong>
|
|
213
|
+
</div>
|
|
214
|
+
{/if}
|
|
215
|
+
</div>
|
|
216
|
+
{/if}
|
|
217
|
+
|
|
218
|
+
<style>
|
|
219
|
+
.aria-inpost-picker {
|
|
220
|
+
--inpost-bg: #fafafe;
|
|
221
|
+
--inpost-fg: #1a1a2e;
|
|
222
|
+
--inpost-muted: #555566;
|
|
223
|
+
--inpost-accent: #5b4a9e;
|
|
224
|
+
--inpost-border: #e2dff0;
|
|
225
|
+
--inpost-radius: 12px;
|
|
226
|
+
--inpost-min-height: 520px;
|
|
227
|
+
|
|
228
|
+
display: block;
|
|
229
|
+
font-family: inherit;
|
|
230
|
+
color: var(--inpost-fg);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.aria-inpost-courier-note {
|
|
234
|
+
color: var(--inpost-muted, #555566);
|
|
235
|
+
font-size: 0.9rem;
|
|
236
|
+
padding: 0.75rem 1rem;
|
|
237
|
+
border: 1px dashed var(--inpost-border, #e2dff0);
|
|
238
|
+
border-radius: var(--inpost-radius, 12px);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.mount {
|
|
242
|
+
min-height: var(--inpost-min-height);
|
|
243
|
+
border: 1px solid var(--inpost-border);
|
|
244
|
+
border-radius: var(--inpost-radius);
|
|
245
|
+
overflow: hidden;
|
|
246
|
+
background: var(--inpost-bg);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.mount :global(inpost-geowidget) {
|
|
250
|
+
display: block;
|
|
251
|
+
width: 100%;
|
|
252
|
+
height: var(--inpost-min-height);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.state-loading,
|
|
256
|
+
.state-error {
|
|
257
|
+
padding: 0.75rem 1rem;
|
|
258
|
+
font-size: 0.9rem;
|
|
259
|
+
color: var(--inpost-muted);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.state-error {
|
|
263
|
+
color: #c44b4b;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.selected {
|
|
267
|
+
margin-top: 0.5rem;
|
|
268
|
+
font-size: 0.95rem;
|
|
269
|
+
}
|
|
270
|
+
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
interface CarrierWidgetConfig {
|
|
2
|
+
token: string;
|
|
3
|
+
language?: string;
|
|
4
|
+
config?: string;
|
|
5
|
+
}
|
|
6
|
+
interface CarrierDescriptor {
|
|
7
|
+
id: string;
|
|
8
|
+
widget: {
|
|
9
|
+
scriptUrl: string | null;
|
|
10
|
+
stylesheetUrl: string | null;
|
|
11
|
+
config: CarrierWidgetConfig;
|
|
12
|
+
} | null;
|
|
13
|
+
}
|
|
14
|
+
interface InpostPoint {
|
|
15
|
+
name: string;
|
|
16
|
+
address?: {
|
|
17
|
+
line1?: string;
|
|
18
|
+
line2?: string;
|
|
19
|
+
};
|
|
20
|
+
address_details?: Record<string, string>;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
interface Props {
|
|
24
|
+
/** Currently selected paczkomat code (e.g. `KRA01M`). Two-way bindable. */
|
|
25
|
+
value?: string;
|
|
26
|
+
/** Optional pre-fetched config (skips network call). */
|
|
27
|
+
descriptor?: CarrierDescriptor | null;
|
|
28
|
+
/** Carrier id to fetch when no `descriptor` provided. Default: `inpost`. */
|
|
29
|
+
carrierId?: string;
|
|
30
|
+
/** Base URL prefix (when shop API lives on a different origin). */
|
|
31
|
+
apiBase?: string;
|
|
32
|
+
/** Override geowidget language. */
|
|
33
|
+
language?: string;
|
|
34
|
+
/** Override geowidget config preset (e.g. `parcelCollect247`). */
|
|
35
|
+
config?: string;
|
|
36
|
+
/** Custom fetch (e.g. SvelteKit `fetch` from a load function). */
|
|
37
|
+
fetchFn?: typeof fetch;
|
|
38
|
+
/**
|
|
39
|
+
* Hide picker when `serviceType` is a courier service (no parcel locker
|
|
40
|
+
* needed). Pass `shippingMethod.carrierConfig?.serviceType` from the
|
|
41
|
+
* public shipping methods API.
|
|
42
|
+
*/
|
|
43
|
+
serviceType?: string | null;
|
|
44
|
+
/** Called when user picks a paczkomat with the full point payload. */
|
|
45
|
+
onSelect?: (point: InpostPoint) => void;
|
|
46
|
+
class?: string;
|
|
47
|
+
style?: string;
|
|
48
|
+
}
|
|
49
|
+
declare const InpostPicker: import("svelte").Component<Props, {}, "value">;
|
|
50
|
+
type InpostPicker = ReturnType<typeof InpostPicker>;
|
|
51
|
+
export default InpostPicker;
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
intro: { ...DEFAULT_LABELS_PL.intro, ...labelOverrides?.intro },
|
|
35
35
|
totals: { ...DEFAULT_LABELS_PL.totals, ...labelOverrides?.totals },
|
|
36
36
|
actions: { ...DEFAULT_LABELS_PL.actions, ...labelOverrides?.actions },
|
|
37
|
-
errors: { ...DEFAULT_LABELS_PL.errors, ...labelOverrides?.errors }
|
|
37
|
+
errors: { ...DEFAULT_LABELS_PL.errors, ...labelOverrides?.errors },
|
|
38
|
+
tracking: { ...DEFAULT_LABELS_PL.tracking, ...labelOverrides?.tracking }
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
const order: OrderState = createOrderState({ number, token, initialData });
|
|
@@ -187,6 +188,27 @@
|
|
|
187
188
|
</section>
|
|
188
189
|
{/if}
|
|
189
190
|
|
|
191
|
+
{#if o.trackingNumber}
|
|
192
|
+
<section class="tracking" part="tracking">
|
|
193
|
+
<h2 class="section-title">{labels.tracking.title}</h2>
|
|
194
|
+
<div class="tracking-row">
|
|
195
|
+
<span class="tracking-label">{labels.tracking.number}</span>
|
|
196
|
+
<span class="tracking-value">{o.trackingNumber}</span>
|
|
197
|
+
</div>
|
|
198
|
+
{#if o.trackingUrl}
|
|
199
|
+
<a
|
|
200
|
+
class="tracking-link"
|
|
201
|
+
part="tracking-link"
|
|
202
|
+
href={o.trackingUrl}
|
|
203
|
+
target="_blank"
|
|
204
|
+
rel="noopener noreferrer"
|
|
205
|
+
>
|
|
206
|
+
{labels.tracking.link}
|
|
207
|
+
</a>
|
|
208
|
+
{/if}
|
|
209
|
+
</section>
|
|
210
|
+
{/if}
|
|
211
|
+
|
|
190
212
|
{#if order.data.statusHistory.length > 0}
|
|
191
213
|
<section class="history" part="history">
|
|
192
214
|
<h2 class="section-title">{labels.statusHistory}</h2>
|
|
@@ -274,12 +296,42 @@
|
|
|
274
296
|
.summary,
|
|
275
297
|
.items,
|
|
276
298
|
.actions,
|
|
299
|
+
.tracking,
|
|
277
300
|
.history {
|
|
278
301
|
margin-top: 1.5rem;
|
|
279
302
|
padding-top: 1.5rem;
|
|
280
303
|
border-top: 1px solid var(--shop-border);
|
|
281
304
|
}
|
|
282
305
|
|
|
306
|
+
.tracking-row {
|
|
307
|
+
display: flex;
|
|
308
|
+
justify-content: space-between;
|
|
309
|
+
gap: 1rem;
|
|
310
|
+
padding: 0.4rem 0;
|
|
311
|
+
font-size: 0.95rem;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.tracking-label {
|
|
315
|
+
color: var(--shop-muted);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.tracking-value {
|
|
319
|
+
font-family: ui-monospace, monospace;
|
|
320
|
+
font-weight: 600;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.tracking-link {
|
|
324
|
+
display: inline-block;
|
|
325
|
+
margin-top: 0.5rem;
|
|
326
|
+
color: var(--shop-accent);
|
|
327
|
+
font-weight: 600;
|
|
328
|
+
text-decoration: none;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.tracking-link:hover {
|
|
332
|
+
text-decoration: underline;
|
|
333
|
+
}
|
|
334
|
+
|
|
283
335
|
.section-title {
|
|
284
336
|
font-size: 0.875rem;
|
|
285
337
|
font-weight: 600;
|
|
@@ -37,5 +37,10 @@ export const DEFAULT_LABELS_PL = {
|
|
|
37
37
|
loadFailed: 'Nie udało się pobrać zamówienia.',
|
|
38
38
|
forbidden: 'Link wygasł lub jest nieprawidłowy.'
|
|
39
39
|
},
|
|
40
|
-
statusHistory: 'Historia statusów'
|
|
40
|
+
statusHistory: 'Historia statusów',
|
|
41
|
+
tracking: {
|
|
42
|
+
title: 'Śledzenie przesyłki',
|
|
43
|
+
number: 'Numer',
|
|
44
|
+
link: 'Sprawdź status przesyłki ↗'
|
|
45
|
+
}
|
|
41
46
|
};
|
package/dist/shop/types.d.ts
CHANGED
|
@@ -40,11 +40,59 @@ export interface PaymentAdapter {
|
|
|
40
40
|
}
|
|
41
41
|
export interface CarrierAdapter {
|
|
42
42
|
id: string;
|
|
43
|
+
label?: I18nText;
|
|
43
44
|
widget?: {
|
|
44
45
|
scriptUrl?: string;
|
|
46
|
+
stylesheetUrl?: string;
|
|
45
47
|
config: Record<string, unknown>;
|
|
46
48
|
};
|
|
47
|
-
validateSelection?(ref: string
|
|
49
|
+
validateSelection?(ref: string, ctx?: {
|
|
50
|
+
serviceType?: string;
|
|
51
|
+
}): Promise<boolean> | boolean;
|
|
52
|
+
createShipment?(input: ShipmentCreateInput): Promise<ShipmentCreateResult>;
|
|
53
|
+
getShipmentLabel?(shipmentId: string, opts?: {
|
|
54
|
+
format?: 'pdf';
|
|
55
|
+
size?: 'A4' | 'A6';
|
|
56
|
+
}): Promise<ShipmentLabel>;
|
|
57
|
+
cancelShipment?(shipmentId: string): Promise<void>;
|
|
58
|
+
handleWebhook?(req: Request): Promise<CarrierEvent>;
|
|
59
|
+
trackingUrl?(trackingNumber: string): string;
|
|
60
|
+
}
|
|
61
|
+
export interface ShipmentReceiver {
|
|
62
|
+
id: string;
|
|
63
|
+
number: string;
|
|
64
|
+
customerEmail: string;
|
|
65
|
+
customerName?: string | null;
|
|
66
|
+
customerPhone?: string | null;
|
|
67
|
+
}
|
|
68
|
+
export interface ShipmentCreateInput {
|
|
69
|
+
order: ShipmentReceiver;
|
|
70
|
+
shippingAddress: Record<string, string> | null;
|
|
71
|
+
carrierRef?: string | null;
|
|
72
|
+
serviceType: string;
|
|
73
|
+
parcelSize?: string;
|
|
74
|
+
cartTotalGross: number;
|
|
75
|
+
insuranceAmount?: number;
|
|
76
|
+
cod?: number;
|
|
77
|
+
language?: string | null;
|
|
78
|
+
}
|
|
79
|
+
export interface ShipmentCreateResult {
|
|
80
|
+
shipmentId: string;
|
|
81
|
+
trackingNumber: string;
|
|
82
|
+
labelUrl?: string;
|
|
83
|
+
raw?: unknown;
|
|
84
|
+
}
|
|
85
|
+
export interface ShipmentLabel {
|
|
86
|
+
contentType: string;
|
|
87
|
+
body: Uint8Array;
|
|
88
|
+
filename?: string;
|
|
89
|
+
}
|
|
90
|
+
export interface CarrierEvent {
|
|
91
|
+
shipmentId: string;
|
|
92
|
+
trackingNumber?: string;
|
|
93
|
+
orderNumber?: string;
|
|
94
|
+
status: 'preparing' | 'sent' | 'done' | 'cancelled' | 'unknown';
|
|
95
|
+
raw: unknown;
|
|
48
96
|
}
|
|
49
97
|
export interface OrderRef {
|
|
50
98
|
id: string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.15.3',
|
|
3
|
+
date: '2026-04-16',
|
|
4
|
+
description: 'Shop: InPost carrier adapter — Geowidget v5 picker + ShipX shipment + webhook auto-status.',
|
|
5
|
+
features: [
|
|
6
|
+
'`inpostAdapter()` ships in `includio-cms/shop` — wires Geowidget v5 (paczkomat picker on the frontend), ShipX (admin "Utwórz przesyłkę InPost" button creates the shipment, auto-buys the offer, fetches the label PDF) and a webhook receiver (`POST /api/shop/carriers/inpost/webhook?secret=...`) that updates order status + tracking number when ShipX events arrive.',
|
|
7
|
+
'`<InpostPicker>` Svelte component (`includio-cms/shop/svelte`) — drop-in widget with `bind:value={carrierRef}`, lazy-loads the geowidget script + CSS, hides itself for `inpost_courier_*` services. Raw API alternative: `GET /api/shop/carriers/inpost`.',
|
|
8
|
+
'Per-shipping-method service config — `shop_shipping_methods.carrier_config` jsonb with `serviceType` (`inpost_locker_standard` / `inpost_locker_express` / `inpost_courier_standard` / `inpost_courier_express`) and `defaultSize` (A/B/C → small/medium/large). Admin shipping form ungates the InPost option and shows the new fields when selected.',
|
|
9
|
+
'Customer order endpoint exposes `trackingNumber` + `trackingUrl` (resolved via `adapter.trackingUrl()`); `<OrderStatus>` renders a "Śledzenie przesyłki" section automatically. Status emails for `preparing` / `sent` / `done` include a tracking block with a clickable carrier link.',
|
|
10
|
+
'Admin order detail page gains a "Przesyłka {carrierType}" panel: Utwórz przesyłkę / Pobierz etykietę PDF / Anuluj przesyłkę. PDF is streamed through an admin-auth proxy at `/api/shop/admin/orders/[id]/label`.',
|
|
11
|
+
'Checkout validates `carrierRef` against the carrier (Operating-only paczkomats via InPost Points API, in-memory cached 5 min). Invalid selections reject with HTTP 400 before the order is created.',
|
|
12
|
+
'CLI scaffold (`pnpm includio scaffold admin`) emits the new routes: `api/shop/carriers/[id]/+server.ts`, `api/shop/carriers/[id]/webhook/+server.ts`, `api/shop/admin/orders/[id]/label/+server.ts`.',
|
|
13
|
+
'CLI scaffolder auto-detects shop usage by scanning `src/lib/cms/cms.config.ts` for an active `shop:` property — projects without the shop module get a clean route tree by default, no flag required. Override with `--shop` / `--no-shop` (or pass `scaffoldAdmin({ shop: false })` programmatically). Combined with the existing config-driven schema generator + admin sidebar guard, shop is now fully opt-in with zero footprint when unused.'
|
|
14
|
+
],
|
|
15
|
+
fixes: [],
|
|
16
|
+
breakingChanges: [],
|
|
17
|
+
sql: 'ALTER TABLE shop_shipping_methods ADD COLUMN carrier_config jsonb;\nALTER TABLE shop_orders ADD COLUMN shipment_id text, ADD COLUMN tracking_number text, ADD COLUMN label_url text, ADD COLUMN shipment_created_at timestamptz;',
|
|
18
|
+
notes: 'Run the SQL once, then `pnpm db:push` to sync the rest of the schema. InPost requires two tokens: a public Geowidget v5 token (browser-side, from `manager.paczkomaty.pl`) and a private ShipX organization token (server-side). On sandbox, ShipX `POST /shipments/:id/buy` is asynchronous and often stalls in `offer_selected` because test accounts have no wallet to pay the offer — the webhook reconciles whatever ends up. See /docs/shop/inpost for full setup.'
|
|
19
|
+
};
|
package/dist/updates/index.js
CHANGED
|
@@ -47,7 +47,8 @@ import { update as update0146 } from './0.14.6/index.js';
|
|
|
47
47
|
import { update as update0150 } from './0.15.0/index.js';
|
|
48
48
|
import { update as update0151 } from './0.15.1/index.js';
|
|
49
49
|
import { update as update0152 } from './0.15.2/index.js';
|
|
50
|
-
|
|
50
|
+
import { update as update0153 } from './0.15.3/index.js';
|
|
51
|
+
export const updates = [update0065, update0066, update0067, update0068, update0069, update010, update011, update012, update013, update014, update015, update020, update022, update050, update051, update052, update053, update054, update055, update056, update057, update058, update060, update061, update062, update070, update071, update072, update073, update080, update090, update0100, update0110, update0120, update0130, update0131, update0132, update0133, update0134, update0140, update0141, update0142, update0143, update0144, update0145, update0146, update0150, update0151, update0152, update0153];
|
|
51
52
|
export const getUpdatesFrom = (fromVersion) => {
|
|
52
53
|
const fromParts = fromVersion.split('.').map(Number);
|
|
53
54
|
return updates.filter((update) => {
|