includio-cms 0.34.0 → 0.35.0
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/API.md +5 -2
- package/CHANGELOG.md +27 -0
- package/DOCS.md +1 -1
- package/dist/admin/client/index.d.ts +1 -0
- package/dist/admin/client/index.js +1 -0
- package/dist/admin/client/shop/coupon-edit-page.svelte +1 -0
- package/dist/admin/client/shop/coupon-form.svelte +62 -2
- package/dist/admin/client/shop/coupon-schema.d.ts +5 -0
- package/dist/admin/client/shop/coupon-schema.js +2 -0
- package/dist/admin/client/shop/shop-order-detail-page.svelte +72 -2
- package/dist/admin/components/fields/date-field.svelte +81 -27
- package/dist/admin/components/fields/date-field.svelte.d.ts +3 -0
- package/dist/admin/components/fields/datetime-field.svelte +142 -29
- package/dist/admin/components/fields/datetime-field.svelte.d.ts +3 -0
- package/dist/admin/remote/shop.remote.d.ts +6 -0
- package/dist/admin/remote/shop.remote.js +4 -0
- package/dist/cli/scaffold/admin.js +1 -1
- package/dist/core/server/generator/generator.js +3 -2
- package/dist/db-postgres/schema/shop/coupons.d.ts +20 -0
- package/dist/db-postgres/schema/shop/coupons.js +3 -0
- package/dist/paraglide/messages/_index.d.ts +36 -3
- package/dist/paraglide/messages/_index.js +71 -3
- package/dist/paraglide/messages/en.d.ts +5 -0
- package/dist/paraglide/messages/en.js +14 -0
- package/dist/paraglide/messages/pl.d.ts +5 -0
- package/dist/paraglide/messages/pl.js +14 -0
- package/dist/shop/client/index.d.ts +1 -0
- package/dist/shop/http/order-handler.js +2 -1
- package/dist/shop/pricing.d.ts +18 -6
- package/dist/shop/pricing.js +33 -8
- package/dist/shop/server/coupons.js +3 -2
- package/dist/shop/server/email.js +30 -0
- package/dist/shop/templates/_partials/items.en.html +10 -0
- package/dist/shop/templates/_partials/items.pl.html +10 -0
- package/dist/updates/0.34.1/index.d.ts +2 -0
- package/dist/updates/0.34.1/index.js +11 -0
- package/dist/updates/0.35.0/index.d.ts +2 -0
- package/dist/updates/0.35.0/index.js +16 -0
- package/dist/updates/index.js +5 -1
- package/package.json +1 -1
- package/dist/paraglide/messages/hello_world.d.ts +0 -5
- package/dist/paraglide/messages/hello_world.js +0 -33
- package/dist/paraglide/messages/login_hello.d.ts +0 -16
- package/dist/paraglide/messages/login_hello.js +0 -34
- package/dist/paraglide/messages/login_please_login.d.ts +0 -16
- package/dist/paraglide/messages/login_please_login.js +0 -34
|
@@ -1,53 +1,166 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
2
|
+
import { CalendarDate, type DateValue } from '@internationalized/date';
|
|
3
|
+
import { Calendar } from '../../../components/ui/calendar/index.js';
|
|
4
|
+
import * as Popover from '../../../components/ui/popover/index.js';
|
|
5
|
+
import * as Select from '../../../components/ui/select/index.js';
|
|
6
|
+
import CalendarIcon from '@lucide/svelte/icons/calendar';
|
|
7
|
+
import ClockIcon from '@lucide/svelte/icons/clock';
|
|
3
8
|
import type { DateTimeField } from '../../../types/fields.js';
|
|
9
|
+
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
4
10
|
import { onMount } from 'svelte';
|
|
11
|
+
import { cn } from '../../../utils.js';
|
|
5
12
|
|
|
6
13
|
type Props = {
|
|
7
14
|
field: DateTimeField;
|
|
8
15
|
value: string | undefined;
|
|
16
|
+
id?: string;
|
|
17
|
+
'aria-invalid'?: string | boolean | undefined;
|
|
18
|
+
'aria-describedby'?: string | undefined;
|
|
9
19
|
};
|
|
10
20
|
|
|
11
21
|
let { field, value = $bindable(), ...props }: Props = $props();
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
const interfaceLanguage = useInterfaceLanguage();
|
|
24
|
+
const localeMap: Record<string, string> = { pl: 'pl-PL', en: 'en-GB' };
|
|
25
|
+
const locale = $derived(localeMap[interfaceLanguage.current] ?? 'en-GB');
|
|
26
|
+
|
|
27
|
+
const lang = {
|
|
28
|
+
pl: { pick: 'Wybierz datę i godzinę', time: 'Godzina' },
|
|
29
|
+
en: { pick: 'Pick date and time', time: 'Time' }
|
|
30
|
+
} as const;
|
|
31
|
+
const t = $derived(lang[interfaceLanguage.current as 'pl' | 'en'] ?? lang.en);
|
|
32
|
+
|
|
33
|
+
function parseValue(iso: string | undefined): {
|
|
34
|
+
date: CalendarDate | undefined;
|
|
35
|
+
hour: string;
|
|
36
|
+
minute: string;
|
|
37
|
+
} {
|
|
38
|
+
if (!iso) return { date: undefined, hour: '09', minute: '00' };
|
|
39
|
+
const d = new Date(iso);
|
|
40
|
+
if (isNaN(d.getTime())) return { date: undefined, hour: '09', minute: '00' };
|
|
41
|
+
return {
|
|
42
|
+
date: new CalendarDate(d.getFullYear(), d.getMonth() + 1, d.getDate()),
|
|
43
|
+
hour: String(d.getHours()).padStart(2, '0'),
|
|
44
|
+
minute: String(d.getMinutes()).padStart(2, '0')
|
|
45
|
+
};
|
|
19
46
|
}
|
|
20
47
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
48
|
+
function parseMinMax(iso: string | undefined): CalendarDate | undefined {
|
|
49
|
+
if (!iso) return undefined;
|
|
50
|
+
const d = new Date(iso);
|
|
51
|
+
if (isNaN(d.getTime())) return undefined;
|
|
52
|
+
return new CalendarDate(d.getFullYear(), d.getMonth() + 1, d.getDate());
|
|
25
53
|
}
|
|
26
54
|
|
|
27
|
-
|
|
55
|
+
const initial = parseValue(value);
|
|
56
|
+
let dateValue = $state<DateValue | undefined>(initial.date);
|
|
57
|
+
let hour = $state(initial.hour);
|
|
58
|
+
let minute = $state(initial.minute);
|
|
59
|
+
let open = $state(false);
|
|
60
|
+
|
|
61
|
+
let lastEmittedValue: string | undefined = value;
|
|
28
62
|
|
|
29
63
|
$effect(() => {
|
|
30
|
-
|
|
64
|
+
if (value === lastEmittedValue) return;
|
|
65
|
+
const next = parseValue(value);
|
|
66
|
+
dateValue = next.date;
|
|
67
|
+
hour = next.hour;
|
|
68
|
+
minute = next.minute;
|
|
69
|
+
lastEmittedValue = value;
|
|
31
70
|
});
|
|
32
71
|
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
72
|
+
$effect(() => {
|
|
73
|
+
const d = dateValue;
|
|
74
|
+
const h = hour;
|
|
75
|
+
const m = minute;
|
|
38
76
|
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
|
|
77
|
+
let next: string;
|
|
78
|
+
if (!d) {
|
|
79
|
+
next = '';
|
|
80
|
+
} else {
|
|
81
|
+
const hourNum = Number.parseInt(h, 10);
|
|
82
|
+
const minuteNum = Number.parseInt(m, 10);
|
|
83
|
+
const js = new Date(
|
|
84
|
+
d.year,
|
|
85
|
+
d.month - 1,
|
|
86
|
+
d.day,
|
|
87
|
+
Number.isFinite(hourNum) ? hourNum : 0,
|
|
88
|
+
Number.isFinite(minuteNum) ? minuteNum : 0,
|
|
89
|
+
0,
|
|
90
|
+
0
|
|
91
|
+
);
|
|
92
|
+
next = js.toISOString();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (next !== lastEmittedValue) {
|
|
96
|
+
lastEmittedValue = next;
|
|
97
|
+
value = next;
|
|
42
98
|
}
|
|
43
99
|
});
|
|
100
|
+
|
|
101
|
+
onMount(() => {
|
|
102
|
+
if (value === undefined) value = '';
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const minDate = $derived(parseMinMax(field.minDate));
|
|
106
|
+
const maxDate = $derived(parseMinMax(field.maxDate));
|
|
107
|
+
|
|
108
|
+
const display = $derived.by(() => {
|
|
109
|
+
if (!value) return t.pick;
|
|
110
|
+
const d = new Date(value);
|
|
111
|
+
if (isNaN(d.getTime())) return t.pick;
|
|
112
|
+
return new Intl.DateTimeFormat(locale, {
|
|
113
|
+
dateStyle: 'long',
|
|
114
|
+
timeStyle: 'short'
|
|
115
|
+
}).format(d);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0'));
|
|
119
|
+
const minutes = ['00', '15', '30', '45'];
|
|
44
120
|
</script>
|
|
45
121
|
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
122
|
+
<Popover.Root bind:open>
|
|
123
|
+
<Popover.Trigger
|
|
124
|
+
{...props}
|
|
125
|
+
class={cn(
|
|
126
|
+
'border-input bg-card flex h-9 w-full items-center gap-2 rounded-md border px-3 py-1 text-left text-sm shadow-xs outline-none transition-[color,box-shadow]',
|
|
127
|
+
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
|
128
|
+
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
|
129
|
+
!value && 'text-muted-foreground'
|
|
130
|
+
)}
|
|
131
|
+
>
|
|
132
|
+
<CalendarIcon class="size-4 opacity-50" />
|
|
133
|
+
<span class="flex-1 truncate">{display}</span>
|
|
134
|
+
</Popover.Trigger>
|
|
135
|
+
<Popover.Content class="w-auto p-0" align="start">
|
|
136
|
+
<Calendar
|
|
137
|
+
type="single"
|
|
138
|
+
bind:value={dateValue}
|
|
139
|
+
{locale}
|
|
140
|
+
minValue={minDate}
|
|
141
|
+
maxValue={maxDate}
|
|
142
|
+
captionLayout="dropdown"
|
|
143
|
+
/>
|
|
144
|
+
<div class="border-border flex items-center gap-2 border-t p-3">
|
|
145
|
+
<ClockIcon class="size-4 opacity-50" />
|
|
146
|
+
<span class="text-muted-foreground text-xs font-semibold">{t.time}</span>
|
|
147
|
+
<Select.Root type="single" bind:value={hour}>
|
|
148
|
+
<Select.Trigger class="h-8 w-[68px]">{hour}</Select.Trigger>
|
|
149
|
+
<Select.Content>
|
|
150
|
+
{#each hours as h (h)}
|
|
151
|
+
<Select.Item value={h} label={h}>{h}</Select.Item>
|
|
152
|
+
{/each}
|
|
153
|
+
</Select.Content>
|
|
154
|
+
</Select.Root>
|
|
155
|
+
<span class="text-muted-foreground">:</span>
|
|
156
|
+
<Select.Root type="single" bind:value={minute}>
|
|
157
|
+
<Select.Trigger class="h-8 w-[68px]">{minute}</Select.Trigger>
|
|
158
|
+
<Select.Content>
|
|
159
|
+
{#each minutes as m (m)}
|
|
160
|
+
<Select.Item value={m} label={m}>{m}</Select.Item>
|
|
161
|
+
{/each}
|
|
162
|
+
</Select.Content>
|
|
163
|
+
</Select.Root>
|
|
164
|
+
</div>
|
|
165
|
+
</Popover.Content>
|
|
166
|
+
</Popover.Root>
|
|
@@ -2,6 +2,9 @@ import type { DateTimeField } from '../../../types/fields.js';
|
|
|
2
2
|
type Props = {
|
|
3
3
|
field: DateTimeField;
|
|
4
4
|
value: string | undefined;
|
|
5
|
+
id?: string;
|
|
6
|
+
'aria-invalid'?: string | boolean | undefined;
|
|
7
|
+
'aria-describedby'?: string | undefined;
|
|
5
8
|
};
|
|
6
9
|
declare const DatetimeField: import("svelte").Component<Props, {}, "value">;
|
|
7
10
|
type DatetimeField = ReturnType<typeof DatetimeField>;
|
|
@@ -413,6 +413,7 @@ export declare const listCouponsAdmin: import("@sveltejs/kit").RemoteQueryFuncti
|
|
|
413
413
|
code: string;
|
|
414
414
|
type: import("../../db-postgres/schema/shop/index.js").ShopCouponType;
|
|
415
415
|
value: string;
|
|
416
|
+
appliesTo: import("../../db-postgres/schema/shop/index.js").ShopCouponAppliesTo;
|
|
416
417
|
minOrderAmount: number | null;
|
|
417
418
|
maxUses: number | null;
|
|
418
419
|
usedCount: number;
|
|
@@ -426,6 +427,7 @@ export declare const getCouponAdmin: import("@sveltejs/kit").RemoteQueryFunction
|
|
|
426
427
|
code: string;
|
|
427
428
|
type: import("../../db-postgres/schema/shop/index.js").ShopCouponType;
|
|
428
429
|
value: string;
|
|
430
|
+
appliesTo: import("../../db-postgres/schema/shop/index.js").ShopCouponAppliesTo;
|
|
429
431
|
minOrderAmount: number | null;
|
|
430
432
|
maxUses: number | null;
|
|
431
433
|
usedCount: number;
|
|
@@ -438,6 +440,7 @@ export declare const createCouponCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
|
438
440
|
code: string;
|
|
439
441
|
type: "fixed" | "percent";
|
|
440
442
|
value: number;
|
|
443
|
+
appliesTo?: "net" | "gross" | undefined;
|
|
441
444
|
minOrderAmount?: number | null | undefined;
|
|
442
445
|
maxUses?: number | null | undefined;
|
|
443
446
|
expiresAt?: string | null | undefined;
|
|
@@ -451,6 +454,7 @@ export declare const createCouponCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
|
451
454
|
expiresAt: Date | null;
|
|
452
455
|
value: string;
|
|
453
456
|
type: import("../../db-postgres/schema/shop/index.js").ShopCouponType;
|
|
457
|
+
appliesTo: import("../../db-postgres/schema/shop/index.js").ShopCouponAppliesTo;
|
|
454
458
|
minOrderAmount: number | null;
|
|
455
459
|
maxUses: number | null;
|
|
456
460
|
usedCount: number;
|
|
@@ -460,6 +464,7 @@ export declare const updateCouponCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
|
460
464
|
input: {
|
|
461
465
|
code?: string | undefined;
|
|
462
466
|
type?: "fixed" | "percent" | undefined;
|
|
467
|
+
appliesTo?: "net" | "gross" | undefined;
|
|
463
468
|
value?: number | undefined;
|
|
464
469
|
minOrderAmount?: number | null | undefined;
|
|
465
470
|
maxUses?: number | null | undefined;
|
|
@@ -471,6 +476,7 @@ export declare const updateCouponCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
|
471
476
|
code: string;
|
|
472
477
|
type: import("../../db-postgres/schema/shop/index.js").ShopCouponType;
|
|
473
478
|
value: string;
|
|
479
|
+
appliesTo: import("../../db-postgres/schema/shop/index.js").ShopCouponAppliesTo;
|
|
474
480
|
minOrderAmount: number | null;
|
|
475
481
|
maxUses: number | null;
|
|
476
482
|
usedCount: number;
|
|
@@ -346,6 +346,7 @@ export const generateBalanceLinkForOrder = command(z.string(), async (orderId) =
|
|
|
346
346
|
const couponInputSchema = z.object({
|
|
347
347
|
code: z.string().min(1).max(64),
|
|
348
348
|
type: z.enum(['percent', 'fixed']),
|
|
349
|
+
appliesTo: z.enum(['net', 'gross']).optional(),
|
|
349
350
|
value: z.number().nonnegative().max(1e9),
|
|
350
351
|
minOrderAmount: z.number().int().nonnegative().nullable().optional(),
|
|
351
352
|
maxUses: z.number().int().positive().nullable().optional(),
|
|
@@ -372,6 +373,7 @@ export const createCouponCmd = command(couponInputSchema, async (input) => {
|
|
|
372
373
|
.values({
|
|
373
374
|
code,
|
|
374
375
|
type: input.type,
|
|
376
|
+
appliesTo: input.appliesTo ?? 'net',
|
|
375
377
|
value: String(input.value),
|
|
376
378
|
minOrderAmount: input.minOrderAmount ?? null,
|
|
377
379
|
maxUses: input.maxUses ?? null,
|
|
@@ -389,6 +391,8 @@ export const updateCouponCmd = command(z.object({ id: z.string(), input: couponI
|
|
|
389
391
|
patch.code = input.code.trim().toUpperCase();
|
|
390
392
|
if (input.type !== undefined)
|
|
391
393
|
patch.type = input.type;
|
|
394
|
+
if (input.appliesTo !== undefined)
|
|
395
|
+
patch.appliesTo = input.appliesTo;
|
|
392
396
|
if (input.value !== undefined)
|
|
393
397
|
patch.value = String(input.value);
|
|
394
398
|
if (input.minOrderAmount !== undefined)
|
|
@@ -160,7 +160,7 @@ export async function load({ params }) {
|
|
|
160
160
|
path: 'admin/(afterLogin)/account/+page.svelte',
|
|
161
161
|
content: `${GENERATED_COMMENT}
|
|
162
162
|
<script lang="ts">
|
|
163
|
-
import { AccountPage } from 'includio-cms/admin/client
|
|
163
|
+
import { AccountPage } from 'includio-cms/admin/client';
|
|
164
164
|
</script>
|
|
165
165
|
|
|
166
166
|
<AccountPage />
|
|
@@ -178,10 +178,11 @@ function generateAPI(config) {
|
|
|
178
178
|
const cmsDir = join(process.cwd(), 'src/lib/cms/runtime');
|
|
179
179
|
const filePath = join(cmsDir, 'api.ts');
|
|
180
180
|
let code = `// This file is auto-generated. Do not edit directly.\n\n`;
|
|
181
|
+
const hasForms = !!(config.forms && config.forms.length > 0);
|
|
181
182
|
code += `
|
|
182
183
|
|
|
183
|
-
import type { SingleEntryMap, SingleSlug, CollectionEntryMap, CollectionSlug
|
|
184
|
-
import { resolveEntry, resolveEntries, countEntries
|
|
184
|
+
import type { SingleEntryMap, SingleSlug, CollectionEntryMap, CollectionSlug,${hasForms ? ' FormEntryMap,' : ''} SiteLanguage } from './types';
|
|
185
|
+
import { resolveEntry, resolveEntries, countEntries,${hasForms ? ' createFormSubmission,' : ''} type PopulateConfig } from 'includio-cms/sveltekit/server';
|
|
185
186
|
|
|
186
187
|
`;
|
|
187
188
|
code += `
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type ShopCouponType = 'percent' | 'fixed';
|
|
2
|
+
export type ShopCouponAppliesTo = 'net' | 'gross';
|
|
2
3
|
export declare const shopCouponsTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
|
|
3
4
|
name: "shop_coupons";
|
|
4
5
|
schema: undefined;
|
|
@@ -73,6 +74,25 @@ export declare const shopCouponsTable: import("drizzle-orm/pg-core/table", { wit
|
|
|
73
74
|
identity: undefined;
|
|
74
75
|
generated: undefined;
|
|
75
76
|
}, {}, {}>;
|
|
77
|
+
appliesTo: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
78
|
+
name: "applies_to";
|
|
79
|
+
tableName: "shop_coupons";
|
|
80
|
+
dataType: "string";
|
|
81
|
+
columnType: "PgText";
|
|
82
|
+
data: ShopCouponAppliesTo;
|
|
83
|
+
driverParam: string;
|
|
84
|
+
notNull: true;
|
|
85
|
+
hasDefault: true;
|
|
86
|
+
isPrimaryKey: false;
|
|
87
|
+
isAutoincrement: false;
|
|
88
|
+
hasRuntimeDefault: false;
|
|
89
|
+
enumValues: [string, ...string[]];
|
|
90
|
+
baseColumn: never;
|
|
91
|
+
identity: undefined;
|
|
92
|
+
generated: undefined;
|
|
93
|
+
}, {}, {
|
|
94
|
+
$type: ShopCouponAppliesTo;
|
|
95
|
+
}>;
|
|
76
96
|
minOrderAmount: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
77
97
|
name: "min_order_amount";
|
|
78
98
|
tableName: "shop_coupons";
|
|
@@ -6,6 +6,9 @@ export const shopCouponsTable = pgTable('shop_coupons', {
|
|
|
6
6
|
type: text('type').$type().notNull(),
|
|
7
7
|
// percent → integer 0-100 stored as numeric for symmetry; fixed → PLN value (precision 20,6 like product.basePrice)
|
|
8
8
|
value: numeric('value', { precision: 20, scale: 6 }).notNull(),
|
|
9
|
+
// Whether `value` applies to the net or gross subtotal. Storage of the
|
|
10
|
+
// resulting discount is always net (canonical) — see calculateCouponDiscountNet.
|
|
11
|
+
appliesTo: text('applies_to').$type().notNull().default('net'),
|
|
9
12
|
minOrderAmount: integer('min_order_amount'),
|
|
10
13
|
maxUses: integer('max_uses'),
|
|
11
14
|
usedCount: integer('used_count').notNull().default(0),
|
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export function hello_world(inputs: {
|
|
2
|
+
name: NonNullable<unknown>;
|
|
3
|
+
}, options?: {
|
|
4
|
+
locale?: "en" | "pl";
|
|
5
|
+
}): string;
|
|
6
|
+
/**
|
|
7
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
8
|
+
*
|
|
9
|
+
* - Changing this function will be over-written by the next build.
|
|
10
|
+
*
|
|
11
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
12
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
13
|
+
*
|
|
14
|
+
* @param {{}} inputs
|
|
15
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
declare function login_hello(inputs?: {}, options?: {
|
|
19
|
+
locale?: "en" | "pl";
|
|
20
|
+
}): string;
|
|
21
|
+
/**
|
|
22
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
23
|
+
*
|
|
24
|
+
* - Changing this function will be over-written by the next build.
|
|
25
|
+
*
|
|
26
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
27
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
28
|
+
*
|
|
29
|
+
* @param {{}} inputs
|
|
30
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
declare function login_please_login(inputs?: {}, options?: {
|
|
34
|
+
locale?: "en" | "pl";
|
|
35
|
+
}): string;
|
|
36
|
+
export { login_hello as login.hello, login_please_login as login.please_login };
|
|
@@ -1,4 +1,72 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from "../runtime.js"
|
|
3
|
+
import * as en from "./en.js"
|
|
4
|
+
import * as pl from "./pl.js"
|
|
5
|
+
/**
|
|
6
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
7
|
+
*
|
|
8
|
+
* - Changing this function will be over-written by the next build.
|
|
9
|
+
*
|
|
10
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
11
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
12
|
+
*
|
|
13
|
+
* @param {{ name: NonNullable<unknown> }} inputs
|
|
14
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
18
|
+
export const hello_world = (inputs, options = {}) => {
|
|
19
|
+
if (experimentalMiddlewareLocaleSplitting && isServer === false) {
|
|
20
|
+
return /** @type {any} */ (globalThis).__paraglide_ssr.hello_world(inputs)
|
|
21
|
+
}
|
|
22
|
+
const locale = options.locale ?? getLocale()
|
|
23
|
+
trackMessageCall("hello_world", locale)
|
|
24
|
+
if (locale === "en") return en.hello_world(inputs)
|
|
25
|
+
return pl.hello_world(inputs)
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
29
|
+
*
|
|
30
|
+
* - Changing this function will be over-written by the next build.
|
|
31
|
+
*
|
|
32
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
33
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
34
|
+
*
|
|
35
|
+
* @param {{}} inputs
|
|
36
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
40
|
+
const login_hello = (inputs = {}, options = {}) => {
|
|
41
|
+
if (experimentalMiddlewareLocaleSplitting && isServer === false) {
|
|
42
|
+
return /** @type {any} */ (globalThis).__paraglide_ssr.login_hello(inputs)
|
|
43
|
+
}
|
|
44
|
+
const locale = options.locale ?? getLocale()
|
|
45
|
+
trackMessageCall("login_hello", locale)
|
|
46
|
+
if (locale === "en") return en.login_hello(inputs)
|
|
47
|
+
return pl.login_hello(inputs)
|
|
48
|
+
};
|
|
49
|
+
export { login_hello as "login.hello" }
|
|
50
|
+
/**
|
|
51
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
52
|
+
*
|
|
53
|
+
* - Changing this function will be over-written by the next build.
|
|
54
|
+
*
|
|
55
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
56
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
57
|
+
*
|
|
58
|
+
* @param {{}} inputs
|
|
59
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
63
|
+
const login_please_login = (inputs = {}, options = {}) => {
|
|
64
|
+
if (experimentalMiddlewareLocaleSplitting && isServer === false) {
|
|
65
|
+
return /** @type {any} */ (globalThis).__paraglide_ssr.login_please_login(inputs)
|
|
66
|
+
}
|
|
67
|
+
const locale = options.locale ?? getLocale()
|
|
68
|
+
trackMessageCall("login_please_login", locale)
|
|
69
|
+
if (locale === "en") return en.login_please_login(inputs)
|
|
70
|
+
return pl.login_please_login(inputs)
|
|
71
|
+
};
|
|
72
|
+
export { login_please_login as "login.please_login" }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
|
|
5
|
+
return `Hello, ${i.name} from en!`
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const login_hello = /** @type {(inputs: {}) => string} */ () => {
|
|
9
|
+
return `Welcome back`
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const login_please_login = /** @type {(inputs: {}) => string} */ () => {
|
|
13
|
+
return `Login to your account`
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
|
|
5
|
+
return `Hello, ${i.name} from pl!`
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const login_hello = /** @type {(inputs: {}) => string} */ () => {
|
|
9
|
+
return `Witaj ponownie`
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const login_please_login = /** @type {(inputs: {}) => string} */ () => {
|
|
13
|
+
return `Zaloguj się na swoje konto`
|
|
14
|
+
};
|
|
@@ -76,7 +76,8 @@ export function createOrderHandler() {
|
|
|
76
76
|
trackingNumber: order.trackingNumber,
|
|
77
77
|
trackingUrl,
|
|
78
78
|
language: order.language,
|
|
79
|
-
createdAt: order.createdAt
|
|
79
|
+
createdAt: order.createdAt,
|
|
80
|
+
notes: order.notes ?? null
|
|
80
81
|
},
|
|
81
82
|
items: items.map((i) => ({
|
|
82
83
|
id: i.id,
|
package/dist/shop/pricing.d.ts
CHANGED
|
@@ -21,14 +21,26 @@ export interface CouponDiscountInput {
|
|
|
21
21
|
type: 'percent' | 'fixed';
|
|
22
22
|
/** percent: 0-100; fixed: PLN value (precision 20,6). */
|
|
23
23
|
value: number;
|
|
24
|
+
/** Whether `value` is applied to the net or gross subtotal. Defaults to 'net'. */
|
|
25
|
+
appliesTo?: 'net' | 'gross';
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
28
|
* Compute discount in cents (minor units) applied to a net subtotal.
|
|
27
|
-
* - `percent`: subtotalNet × (value / 100), rounded to nearest cent.
|
|
28
|
-
* - `fixed`: PLN value converted to cents, capped at subtotalNet (never negative).
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* Behavior by mode:
|
|
31
|
+
* - `appliesTo='net'` (default):
|
|
32
|
+
* - `percent`: subtotalNet × (value / 100), rounded to nearest cent.
|
|
33
|
+
* - `fixed`: PLN value converted to cents, capped at subtotalNet.
|
|
34
|
+
* - `appliesTo='gross'` (requires `subtotalGross`):
|
|
35
|
+
* - `percent`: discount expressed against the gross subtotal, then converted
|
|
36
|
+
* to net via the aggregate net/gross ratio (handles mixed VAT rates).
|
|
37
|
+
* Mathematically yields the same proportional factor as net-percent —
|
|
38
|
+
* kept explicit for clarity and rounding parity with the fixed-gross path.
|
|
39
|
+
* - `fixed`: PLN value interpreted as a gross amount, capped at subtotalGross,
|
|
40
|
+
* then converted to net via `× subtotalNet / subtotalGross`.
|
|
41
|
+
*
|
|
42
|
+
* Discount storage is always net (canonical) — VAT is applied to lines after
|
|
43
|
+
* the discount factor, so the rebate appears proportionally on each line.
|
|
44
|
+
* Result is bounded to `[0, subtotalNet]`.
|
|
33
45
|
*/
|
|
34
|
-
export declare function calculateCouponDiscountNet(subtotalNet: number, coupon: CouponDiscountInput): number;
|
|
46
|
+
export declare function calculateCouponDiscountNet(subtotalNet: number, coupon: CouponDiscountInput, subtotalGross?: number): number;
|
package/dist/shop/pricing.js
CHANGED
|
@@ -49,23 +49,48 @@ export function resolveI18n(value, language, fallback) {
|
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
51
|
* Compute discount in cents (minor units) applied to a net subtotal.
|
|
52
|
-
* - `percent`: subtotalNet × (value / 100), rounded to nearest cent.
|
|
53
|
-
* - `fixed`: PLN value converted to cents, capped at subtotalNet (never negative).
|
|
54
52
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
53
|
+
* Behavior by mode:
|
|
54
|
+
* - `appliesTo='net'` (default):
|
|
55
|
+
* - `percent`: subtotalNet × (value / 100), rounded to nearest cent.
|
|
56
|
+
* - `fixed`: PLN value converted to cents, capped at subtotalNet.
|
|
57
|
+
* - `appliesTo='gross'` (requires `subtotalGross`):
|
|
58
|
+
* - `percent`: discount expressed against the gross subtotal, then converted
|
|
59
|
+
* to net via the aggregate net/gross ratio (handles mixed VAT rates).
|
|
60
|
+
* Mathematically yields the same proportional factor as net-percent —
|
|
61
|
+
* kept explicit for clarity and rounding parity with the fixed-gross path.
|
|
62
|
+
* - `fixed`: PLN value interpreted as a gross amount, capped at subtotalGross,
|
|
63
|
+
* then converted to net via `× subtotalNet / subtotalGross`.
|
|
64
|
+
*
|
|
65
|
+
* Discount storage is always net (canonical) — VAT is applied to lines after
|
|
66
|
+
* the discount factor, so the rebate appears proportionally on each line.
|
|
67
|
+
* Result is bounded to `[0, subtotalNet]`.
|
|
58
68
|
*/
|
|
59
|
-
export function calculateCouponDiscountNet(subtotalNet, coupon) {
|
|
69
|
+
export function calculateCouponDiscountNet(subtotalNet, coupon, subtotalGross) {
|
|
60
70
|
if (subtotalNet <= 0)
|
|
61
71
|
return 0;
|
|
72
|
+
const appliesTo = coupon.appliesTo ?? 'net';
|
|
73
|
+
const useGross = appliesTo === 'gross' && typeof subtotalGross === 'number' && subtotalGross > 0;
|
|
62
74
|
let raw = 0;
|
|
63
75
|
if (coupon.type === 'percent') {
|
|
64
76
|
const pct = Math.max(0, Math.min(100, coupon.value));
|
|
65
|
-
|
|
77
|
+
if (useGross) {
|
|
78
|
+
const discountGross = Math.round(subtotalGross * (pct / 100));
|
|
79
|
+
raw = Math.round((discountGross * subtotalNet) / subtotalGross);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
raw = Math.round(subtotalNet * (pct / 100));
|
|
83
|
+
}
|
|
66
84
|
}
|
|
67
85
|
else if (coupon.type === 'fixed') {
|
|
68
|
-
|
|
86
|
+
const fixedCents = toCents(Math.max(0, coupon.value));
|
|
87
|
+
if (useGross) {
|
|
88
|
+
const cappedGross = Math.min(fixedCents, subtotalGross);
|
|
89
|
+
raw = Math.round((cappedGross * subtotalNet) / subtotalGross);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
raw = fixedCents;
|
|
93
|
+
}
|
|
69
94
|
}
|
|
70
95
|
return Math.max(0, Math.min(raw, subtotalNet));
|
|
71
96
|
}
|
|
@@ -40,8 +40,9 @@ export async function validateCoupon(input) {
|
|
|
40
40
|
}
|
|
41
41
|
const discountNet = calculateCouponDiscountNet(input.subtotalNet, {
|
|
42
42
|
type: row.type,
|
|
43
|
-
value: Number(row.value)
|
|
44
|
-
|
|
43
|
+
value: Number(row.value),
|
|
44
|
+
appliesTo: (row.appliesTo ?? 'net')
|
|
45
|
+
}, input.subtotalGross);
|
|
45
46
|
return { row, discountNet };
|
|
46
47
|
}
|
|
47
48
|
/**
|