@mohasinac/appkit 4.60.1 → 4.61.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/dist/.build-stamp.json +3 -3
- package/dist/_internal/server/features/auctions/actions.d.ts +1 -1
- package/dist/_internal/server/features/auctions/actions.js +12 -1
- package/dist/_internal/server/features/blog/actions.d.ts +1 -1
- package/dist/_internal/server/features/blog/actions.js +12 -1
- package/dist/_internal/server/features/brands/actions.d.ts +1 -1
- package/dist/_internal/server/features/brands/actions.js +12 -1
- package/dist/_internal/server/features/cart/actions.d.ts +1 -1
- package/dist/_internal/server/features/cart/actions.js +12 -1
- package/dist/_internal/server/features/checkout/actions.js +0 -1
- package/dist/_internal/server/features/events/actions.d.ts +1 -1
- package/dist/_internal/server/features/events/actions.js +12 -1
- package/dist/_internal/server/features/gateway-events/redact.js +14 -2
- package/dist/_internal/server/features/history/actions.d.ts +1 -1
- package/dist/_internal/server/features/history/actions.js +12 -1
- package/dist/_internal/server/features/lottery/actions.d.ts +1 -1
- package/dist/_internal/server/features/lottery/actions.js +12 -1
- package/dist/_internal/server/features/orders/actions.d.ts +1 -1
- package/dist/_internal/server/features/orders/actions.js +12 -1
- package/dist/_internal/server/features/orders/cancellation-refund.js +0 -1
- package/dist/_internal/server/features/orders/service.js +31 -1
- package/dist/_internal/server/features/payouts/actions.d.ts +1 -1
- package/dist/_internal/server/features/payouts/actions.js +12 -1
- package/dist/_internal/server/features/points/award.js +9 -1
- package/dist/_internal/server/features/pre-orders/actions.d.ts +1 -1
- package/dist/_internal/server/features/pre-orders/actions.js +12 -1
- package/dist/_internal/server/features/prize-draws/actions.d.ts +1 -1
- package/dist/_internal/server/features/prize-draws/actions.js +12 -1
- package/dist/_internal/server/features/products/actions.d.ts +1 -1
- package/dist/_internal/server/features/products/actions.js +12 -1
- package/dist/_internal/server/features/promotions/actions.d.ts +1 -1
- package/dist/_internal/server/features/promotions/actions.js +12 -1
- package/dist/_internal/server/features/protection-claims/actions.d.ts +2 -2
- package/dist/_internal/server/features/protection-claims/actions.js +21 -12
- package/dist/_internal/server/features/raffle/actions.d.ts +1 -1
- package/dist/_internal/server/features/raffle/actions.js +12 -1
- package/dist/_internal/server/features/refunds/actions.d.ts +1 -3
- package/dist/_internal/server/features/refunds/actions.js +14 -2
- package/dist/_internal/server/features/reviews/actions.d.ts +1 -1
- package/dist/_internal/server/features/reviews/actions.js +12 -1
- package/dist/_internal/server/features/wishlist/actions.d.ts +1 -1
- package/dist/_internal/server/features/wishlist/actions.js +12 -1
- package/dist/_internal/server/jobs/core/obligationSweep.js +0 -1
- package/dist/_internal/server/jobs/core/paymentReconcile.js +84 -57
- package/dist/_internal/server/jobs/core/testDataCleanup.d.ts +1 -1
- package/dist/_internal/server/jobs/core/testDataCleanup.js +22 -1
- package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +16 -4
- package/dist/_internal/shared/features/promotions/schema.d.ts +66 -0
- package/dist/_internal/shared/features/promotions/schema.js +7 -0
- package/dist/_internal/shared/obligations/deadlines.js +11 -1
- package/dist/_internal/shared/tester/window.d.ts +25 -0
- package/dist/_internal/shared/tester/window.js +66 -0
- package/dist/core/unit-of-work.js +15 -1
- package/dist/errors/firestore-codes.d.ts +37 -0
- package/dist/errors/firestore-codes.js +52 -0
- package/dist/errors/index.d.ts +0 -1
- package/dist/errors/index.js +12 -2
- package/dist/features/admin/components/AdminCouponEditorView.js +8 -0
- package/dist/features/admin/components/AdminGatewayEventsView.js +2 -2
- package/dist/features/admin/components/DataListingView.js +2 -0
- package/dist/features/admin/components/analytics/AdminStatCard.js +4 -1
- package/dist/features/admin/repository/site-settings.repository.d.ts +2 -1
- package/dist/features/admin/repository/site-settings.repository.js +7 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +1 -1
- package/dist/features/catalogue/actions/approve-catalogue-listing.d.ts +1 -1
- package/dist/features/catalogue/actions/approve-catalogue-listing.js +12 -1
- package/dist/features/catalogue/actions/list-from-catalogue.d.ts +1 -1
- package/dist/features/catalogue/actions/list-from-catalogue.js +12 -1
- package/dist/features/catalogue/actions/submit-for-approval.d.ts +1 -1
- package/dist/features/catalogue/actions/submit-for-approval.js +12 -1
- package/dist/features/categories/repository/price-observation.repository.d.ts +19 -0
- package/dist/features/categories/repository/price-observation.repository.js +21 -2
- package/dist/features/events/actions/event-actions.js +4 -1
- package/dist/features/events/components/AdminEventEditorView.js +72 -45
- package/dist/features/events/components/EventsListPageView.js +2 -1
- package/dist/features/events/schemas/firestore.d.ts +17 -0
- package/dist/features/events/schemas/firestore.js +1 -0
- package/dist/features/events/utils/sort-pinned.d.ts +33 -0
- package/dist/features/events/utils/sort-pinned.js +46 -0
- package/dist/features/gateway-events/repository/gateway-events.repository.js +3 -40
- package/dist/features/gateway-events/schemas/index.d.ts +2 -2
- package/dist/features/inventory/components/AdminStorageBoxesView.js +55 -39
- package/dist/features/inventory/components/ReceiveStationView.js +61 -17
- package/dist/features/inventory/repository/storage-boxes.repository.d.ts +3 -1
- package/dist/features/inventory/repository/storage-boxes.repository.js +36 -3
- package/dist/features/obligations/repository/obligations.repository.js +10 -1
- package/dist/features/orders/schemas/firestore.d.ts +19 -0
- package/dist/features/orders/schemas/firestore.js +19 -0
- package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +1 -0
- package/dist/features/payment-cases/repository/payment-cases.repository.js +3 -14
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +1 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -1
- package/dist/features/products/components/ProductForm.js +2 -2
- package/dist/features/products/components/ProductGrid.js +1 -1
- package/dist/features/promotions/repository/claimed-coupons.repository.js +31 -1
- package/dist/features/promotions/schemas/firestore.d.ts +22 -0
- package/dist/features/promotions/schemas/firestore.js +22 -0
- package/dist/features/promotions/schemas/index.d.ts +479 -6
- package/dist/features/promotions/schemas/index.js +153 -0
- package/dist/features/protection-claims/repository/protection-claims.repository.js +22 -15
- package/dist/features/seller/schemas/coupon-form.d.ts +5 -0
- package/dist/features/seller/schemas/coupon-form.js +18 -0
- package/dist/features/shell/SectionForm.js +31 -9
- package/dist/features/sticker-trace/server.d.ts +2 -0
- package/dist/http/api-handler.js +4 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/next/routing/route-map.d.ts +0 -2
- package/dist/next/routing/route-map.js +10 -1
- package/dist/schemas/registry.d.ts +11 -6
- package/dist/server.d.ts +0 -1
- package/dist/server.js +5 -3
- package/dist/styles.css +51 -0
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +1822 -1812
- package/dist/features/tester/utils/phases.d.ts +0 -48
- package/dist/features/tester/utils/phases.js +0 -139
|
@@ -18,9 +18,32 @@ export const discountConfigSchema = z.object({
|
|
|
18
18
|
maxDiscount: rupeesSchema.optional(),
|
|
19
19
|
minPurchase: rupeesSchema.optional(),
|
|
20
20
|
});
|
|
21
|
+
/*
|
|
22
|
+
* 🛑 THIS IS THE ONLY bxgy VALIDATOR. The four admin write paths — the POST
|
|
23
|
+
* and PATCH routes, both server actions, and `createCouponSchema` below — all
|
|
24
|
+
* import it rather than restating the shape.
|
|
25
|
+
*
|
|
26
|
+
* That is not tidiness. Until 2026-10-22 none of those four declared `bxgy` at
|
|
27
|
+
* all, and every one of them is a `z.object()`, which STRIPS unknown keys. So
|
|
28
|
+
* `AdminCouponEditorView` built a correct `bxgy` payload, the route answered
|
|
29
|
+
* 200, and the field was never written — an admin could create a BOGO coupon
|
|
30
|
+
* that discounted nothing, which is the exact silent-strip trap the `tiers`
|
|
31
|
+
* comment in the POST route already describes, recurring one field over.
|
|
32
|
+
*
|
|
33
|
+
* `maxItemPrice` is deliberately `.optional()` with no default: absent means
|
|
34
|
+
* "no ceiling", which is what every BOGO coupon written before it existed
|
|
35
|
+
* means. A default would retroactively shrink them.
|
|
36
|
+
*/
|
|
21
37
|
export const bxgyConfigSchema = z.object({
|
|
22
38
|
buyQuantity: z.number().int().positive(),
|
|
23
39
|
getQuantity: z.number().int().positive(),
|
|
40
|
+
// `.refine` rather than `.positive()` — `rupeesSchema` is a ZodEffects (it
|
|
41
|
+
// carries the 2-decimal check), and ZodNumber's numeric helpers are not on
|
|
42
|
+
// it. A zero ceiling would disqualify every unit, so it is rejected here as
|
|
43
|
+
// well as being treated as absent by `calculateBxgyDiscount`.
|
|
44
|
+
maxItemPrice: rupeesSchema
|
|
45
|
+
.refine((n) => n > 0, { message: "Price ceiling must be greater than zero" })
|
|
46
|
+
.optional(),
|
|
24
47
|
applicableProducts: z.array(z.string()).optional(),
|
|
25
48
|
applicableCategories: z.array(z.string()).optional(),
|
|
26
49
|
});
|
|
@@ -70,6 +93,136 @@ export const couponFirestoreSchema = z.object({
|
|
|
70
93
|
stats: couponStatsSchema,
|
|
71
94
|
...auditTimestampsShape,
|
|
72
95
|
});
|
|
96
|
+
/**
|
|
97
|
+
* The admin create-coupon REQUEST BODY.
|
|
98
|
+
*
|
|
99
|
+
* 🛑 It lives here, in appkit, because `bxgyConfigSchema` does — and a zod
|
|
100
|
+
* schema CANNOT cross the version boundary. The consumer root is on zod 4 and
|
|
101
|
+
* appkit depends on zod 3, so `/api/admin/coupons` composing
|
|
102
|
+
* `bxgy: bxgyConfigSchema.optional()` into a consumer `z.object({...})` did
|
|
103
|
+
* not merely infer `unknown`: zod 4 **throws** on it —
|
|
104
|
+
*
|
|
105
|
+
* Invalid element at key "bx": expected a Zod schema
|
|
106
|
+
*
|
|
107
|
+
* — so every POST to that route failed. The type error was the only signal,
|
|
108
|
+
* and it reads as a cosmetic inference problem.
|
|
109
|
+
*
|
|
110
|
+
* Composed from the pieces above rather than restated, which is the concern
|
|
111
|
+
* the route's own comment raised about `bxgy`: "four write paths validate this
|
|
112
|
+
* shape and a fifth copy is how they drift". Re-declaring the shape in
|
|
113
|
+
* consumer zod would have been that fifth copy; moving the whole body schema
|
|
114
|
+
* to where the pieces live is the version of that fix which also works.
|
|
115
|
+
*/
|
|
116
|
+
export const couponCreateBodySchema = z
|
|
117
|
+
.object({
|
|
118
|
+
code: z.string().min(2).max(50),
|
|
119
|
+
name: z.string().min(1),
|
|
120
|
+
description: z.string().default(""),
|
|
121
|
+
type: couponTypeEnumSchema,
|
|
122
|
+
tiers: z.array(tieredDiscountSchema).max(6).optional(),
|
|
123
|
+
bxgy: bxgyConfigSchema.optional(),
|
|
124
|
+
discount: z.object({
|
|
125
|
+
value: z.number().min(0),
|
|
126
|
+
maxDiscount: z.number().optional(),
|
|
127
|
+
minPurchase: z.number().optional(),
|
|
128
|
+
}),
|
|
129
|
+
usage: z.object({
|
|
130
|
+
totalLimit: z.number().optional(),
|
|
131
|
+
perUserLimit: z.number().optional(),
|
|
132
|
+
currentUsage: z.number().default(0),
|
|
133
|
+
}),
|
|
134
|
+
validity: z.object({
|
|
135
|
+
startDate: z.string().transform((v) => new Date(v)),
|
|
136
|
+
endDate: z
|
|
137
|
+
.string()
|
|
138
|
+
.optional()
|
|
139
|
+
.transform((v) => (v ? new Date(v) : undefined)),
|
|
140
|
+
isActive: z.boolean().default(false),
|
|
141
|
+
}),
|
|
142
|
+
restrictions: z
|
|
143
|
+
.object({
|
|
144
|
+
applicableProducts: z.array(z.string()).optional(),
|
|
145
|
+
applicableCategories: z.array(z.string()).optional(),
|
|
146
|
+
applicableSellers: z.array(z.string()).optional(),
|
|
147
|
+
excludeProducts: z.array(z.string()).optional(),
|
|
148
|
+
excludeCategories: z.array(z.string()).optional(),
|
|
149
|
+
firstTimeUserOnly: z.boolean().default(false),
|
|
150
|
+
})
|
|
151
|
+
.default({ firstTimeUserOnly: false }),
|
|
152
|
+
createdBy: z.string().optional().default("admin"),
|
|
153
|
+
})
|
|
154
|
+
.superRefine((data, ctx) => {
|
|
155
|
+
if (data.type === "percentage" && data.discount.value > 100) {
|
|
156
|
+
ctx.addIssue({
|
|
157
|
+
code: z.ZodIssueCode.custom,
|
|
158
|
+
message: "Percentage discount cannot exceed 100%",
|
|
159
|
+
path: ["discount", "value"],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
/**
|
|
164
|
+
* The admin update-coupon REQUEST BODY (`PATCH /api/admin/coupons/[id]`).
|
|
165
|
+
*
|
|
166
|
+
* 🛑 HERE FOR THE SAME REASON AS `couponCreateBodySchema` — read its note
|
|
167
|
+
* first. It embeds `bxgyConfigSchema`, and a zod-3 schema composed into a
|
|
168
|
+
* consumer zod-4 `z.object({...})` THROWS at parse time rather than merely
|
|
169
|
+
* mistyping. The POST route was migrated when that was discovered; this is
|
|
170
|
+
* the sibling PATCH, which has the same embed and would fail the same way.
|
|
171
|
+
*
|
|
172
|
+
* 🛑 NOT `couponCreateBodySchema.partial()`. That schema carries a
|
|
173
|
+
* `.superRefine`, so it is a ZodEffects and has no `.partial()` — and the
|
|
174
|
+
* update body is genuinely a different shape anyway: no `code` (a coupon's
|
|
175
|
+
* code is immutable after creation), plus the `action` discriminator the
|
|
176
|
+
* route uses for activate/deactivate.
|
|
177
|
+
*
|
|
178
|
+
* Every nested object stays OPTIONAL as a whole, which is what lets the route
|
|
179
|
+
* merge `validity` and `restrictions` against the stored document instead of
|
|
180
|
+
* replacing them — a caller sending only `{isActive: false}` must not wipe
|
|
181
|
+
* the coupon's real dates.
|
|
182
|
+
*/
|
|
183
|
+
export const couponUpdateBodySchema = z.object({
|
|
184
|
+
name: z.string().min(1).optional(),
|
|
185
|
+
description: z.string().optional(),
|
|
186
|
+
tiers: z.array(tieredDiscountSchema).max(6).optional(),
|
|
187
|
+
bxgy: bxgyConfigSchema.optional(),
|
|
188
|
+
discount: z
|
|
189
|
+
.object({
|
|
190
|
+
value: z.number().min(0),
|
|
191
|
+
maxDiscount: z.number().optional(),
|
|
192
|
+
minPurchase: z.number().optional(),
|
|
193
|
+
})
|
|
194
|
+
.optional(),
|
|
195
|
+
usage: z
|
|
196
|
+
.object({
|
|
197
|
+
totalLimit: z.number().optional(),
|
|
198
|
+
perUserLimit: z.number().optional(),
|
|
199
|
+
})
|
|
200
|
+
.optional(),
|
|
201
|
+
validity: z
|
|
202
|
+
.object({
|
|
203
|
+
startDate: z
|
|
204
|
+
.string()
|
|
205
|
+
.transform((v) => new Date(v))
|
|
206
|
+
.optional(),
|
|
207
|
+
endDate: z
|
|
208
|
+
.string()
|
|
209
|
+
.optional()
|
|
210
|
+
.transform((v) => (v ? new Date(v) : undefined)),
|
|
211
|
+
isActive: z.boolean().optional(),
|
|
212
|
+
})
|
|
213
|
+
.optional(),
|
|
214
|
+
restrictions: z
|
|
215
|
+
.object({
|
|
216
|
+
applicableProducts: z.array(z.string()).optional(),
|
|
217
|
+
applicableCategories: z.array(z.string()).optional(),
|
|
218
|
+
applicableSellers: z.array(z.string()).optional(),
|
|
219
|
+
excludeProducts: z.array(z.string()).optional(),
|
|
220
|
+
excludeCategories: z.array(z.string()).optional(),
|
|
221
|
+
firstTimeUserOnly: z.boolean().optional(),
|
|
222
|
+
})
|
|
223
|
+
.optional(),
|
|
224
|
+
action: z.enum(["activate", "deactivate"]).optional(),
|
|
225
|
+
});
|
|
73
226
|
export const couponUsageFirestoreSchema = z.object({
|
|
74
227
|
id: z.string(),
|
|
75
228
|
userId: z.string(),
|
|
@@ -1,20 +1,11 @@
|
|
|
1
|
+
// One shared predicate — this file had its own copy; see firestore-codes.ts.
|
|
2
|
+
import { isAlreadyExistsError } from "../../../errors/firestore-codes.js";
|
|
1
3
|
import { BaseRepository, prepareForFirestore } from "../../../providers/db-firebase/index.js";
|
|
2
4
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
3
5
|
import { buildHistoryEntry } from "../../../_internal/shared/history/history.js";
|
|
4
6
|
import { ConflictError } from "../../../errors/index.js";
|
|
5
7
|
import { PROTECTION_CLAIMS_COLLECTION, PROTECTION_CLAIM_ACTIVE_STATUSES, PROTECTION_CLAIM_FIELDS, createProtectionClaimId, } from "../schemas/firestore.js";
|
|
6
|
-
|
|
7
|
-
function isAlreadyExists(err) {
|
|
8
|
-
if (typeof err !== "object" || err === null)
|
|
9
|
-
return false;
|
|
10
|
-
const code = err.code;
|
|
11
|
-
if (code === GRPC_ALREADY_EXISTS)
|
|
12
|
-
return true;
|
|
13
|
-
if (code === "already-exists" || code === "ALREADY_EXISTS")
|
|
14
|
-
return true;
|
|
15
|
-
const message = err.message;
|
|
16
|
-
return typeof message === "string" && message.includes("ALREADY_EXISTS");
|
|
17
|
-
}
|
|
8
|
+
import { serverLogger } from "../../../monitoring/index.js";
|
|
18
9
|
export class ProtectionClaimsRepository extends BaseRepository {
|
|
19
10
|
constructor() {
|
|
20
11
|
super(PROTECTION_CLAIMS_COLLECTION);
|
|
@@ -64,7 +55,7 @@ export class ProtectionClaimsRepository extends BaseRepository {
|
|
|
64
55
|
return doc;
|
|
65
56
|
}
|
|
66
57
|
catch (err) {
|
|
67
|
-
if (
|
|
58
|
+
if (isAlreadyExistsError(err)) {
|
|
68
59
|
void normalizeError(err);
|
|
69
60
|
throw new ConflictError(`A ${input.kind} protection claim is already open on this order. Add to that claim rather than filing a second one.`);
|
|
70
61
|
}
|
|
@@ -118,7 +109,15 @@ export class ProtectionClaimsRepository extends BaseRepository {
|
|
|
118
109
|
await this.getCollection().doc(id).update({ refundId, updatedAt: now });
|
|
119
110
|
}
|
|
120
111
|
catch (err) {
|
|
121
|
-
|
|
112
|
+
/*
|
|
113
|
+
* Best effort, but RECORDED. `void normalizeError(err)` alone types the
|
|
114
|
+
* error and reports it nowhere — as silent as `catch {}` while looking
|
|
115
|
+
* handled, which is what audit-silent-degrade calls normalize theatre.
|
|
116
|
+
* Swallowing is still correct here (the claim row is already durable; this is a best-effort annotation); losing the reason is not.
|
|
117
|
+
*/
|
|
118
|
+
serverLogger.warn("protectionClaims.write failed", {
|
|
119
|
+
error: normalizeError(err).message,
|
|
120
|
+
});
|
|
122
121
|
}
|
|
123
122
|
}
|
|
124
123
|
async setStatus(id, status, reason, now = new Date()) {
|
|
@@ -142,7 +141,15 @@ export class ProtectionClaimsRepository extends BaseRepository {
|
|
|
142
141
|
});
|
|
143
142
|
}
|
|
144
143
|
catch (err) {
|
|
145
|
-
|
|
144
|
+
/*
|
|
145
|
+
* Best effort, but RECORDED. `void normalizeError(err)` alone types the
|
|
146
|
+
* error and reports it nowhere — as silent as `catch {}` while looking
|
|
147
|
+
* handled, which is what audit-silent-degrade calls normalize theatre.
|
|
148
|
+
* Swallowing is still correct here (the claim row is already durable; this is a best-effort annotation); losing the reason is not.
|
|
149
|
+
*/
|
|
150
|
+
serverLogger.warn("protectionClaims.write failed", {
|
|
151
|
+
error: normalizeError(err).message,
|
|
152
|
+
});
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
155
|
async findByOrderAndKind(orderId, kind) {
|
|
@@ -199,6 +199,7 @@ export declare const adminCouponFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
199
199
|
description: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
200
200
|
buyQuantity: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
201
201
|
getQuantity: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
202
|
+
maxItemPrice: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
202
203
|
currentUsage: z.ZodOptional<z.ZodNumber>;
|
|
203
204
|
tiers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
204
205
|
minAmount: z.ZodNumber;
|
|
@@ -233,6 +234,7 @@ export declare const adminCouponFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
233
234
|
discountValue: number;
|
|
234
235
|
}[] | undefined;
|
|
235
236
|
currentUsage?: number | undefined;
|
|
237
|
+
maxItemPrice?: string | undefined;
|
|
236
238
|
maxDiscount?: string | undefined;
|
|
237
239
|
buyQuantity?: string | undefined;
|
|
238
240
|
getQuantity?: string | undefined;
|
|
@@ -257,6 +259,7 @@ export declare const adminCouponFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
257
259
|
discountValue: number;
|
|
258
260
|
}[] | undefined;
|
|
259
261
|
currentUsage?: number | undefined;
|
|
262
|
+
maxItemPrice?: string | undefined;
|
|
260
263
|
maxDiscount?: string | undefined;
|
|
261
264
|
buyQuantity?: string | undefined;
|
|
262
265
|
getQuantity?: string | undefined;
|
|
@@ -281,6 +284,7 @@ export declare const adminCouponFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
281
284
|
discountValue: number;
|
|
282
285
|
}[] | undefined;
|
|
283
286
|
currentUsage?: number | undefined;
|
|
287
|
+
maxItemPrice?: string | undefined;
|
|
284
288
|
maxDiscount?: string | undefined;
|
|
285
289
|
buyQuantity?: string | undefined;
|
|
286
290
|
getQuantity?: string | undefined;
|
|
@@ -305,6 +309,7 @@ export declare const adminCouponFormSchema: z.ZodEffects<z.ZodObject<{
|
|
|
305
309
|
discountValue: number;
|
|
306
310
|
}[] | undefined;
|
|
307
311
|
currentUsage?: number | undefined;
|
|
312
|
+
maxItemPrice?: string | undefined;
|
|
308
313
|
maxDiscount?: string | undefined;
|
|
309
314
|
buyQuantity?: string | undefined;
|
|
310
315
|
getQuantity?: string | undefined;
|
|
@@ -236,6 +236,24 @@ export const adminCouponFormSchema = couponFormBase
|
|
|
236
236
|
section: "basics", order: 7, row: "pair", kind: "number",
|
|
237
237
|
when: (v) => v.type === "buy_x_get_y",
|
|
238
238
|
}),
|
|
239
|
+
/*
|
|
240
|
+
* The per-ITEM price ceiling — "buy 3 get 1 free on items under ₹2,000".
|
|
241
|
+
*
|
|
242
|
+
* 🛑 Genuinely optional, unlike the buy/get pair: `superRefine` below does
|
|
243
|
+
* NOT demand it when the type is buy_x_get_y, because a BOGO with no
|
|
244
|
+
* ceiling is a perfectly ordinary offer and every one written before
|
|
245
|
+
* 2026-10-22 is exactly that. Leaving it blank means no ceiling.
|
|
246
|
+
*
|
|
247
|
+
* 🛑 Not to be confused with `minPurchase`, which is the ORDER total. This
|
|
248
|
+
* is one unit's price, so a line of three ₹1,500 tops qualifies on all
|
|
249
|
+
* three units rather than failing a ₹4,500 test.
|
|
250
|
+
*/
|
|
251
|
+
maxItemPrice: annotate(numericString("Maximum item price").optional(), {
|
|
252
|
+
section: "basics", order: 8, row: "pair", kind: "number",
|
|
253
|
+
when: (v) => v.type === "buy_x_get_y",
|
|
254
|
+
label: "Max item price (₹)",
|
|
255
|
+
help: "Only items at or below this price qualify. Leave blank for no limit.",
|
|
256
|
+
}),
|
|
239
257
|
/*
|
|
240
258
|
* Written by orders, never by this form. `t3-mirror` renders it read-only
|
|
241
259
|
* instead of as a box an admin could set to whatever they liked — which is
|
|
@@ -391,6 +391,21 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
|
|
|
391
391
|
* sections").
|
|
392
392
|
*/
|
|
393
393
|
const showRail = ordered.length >= 3;
|
|
394
|
+
/**
|
|
395
|
+
* One rail click: open the section if it is not already, then scroll to it.
|
|
396
|
+
* Named rather than inline in the button's `onClick`, where the `if` body
|
|
397
|
+
* sat a brace too deep inside the mapped JSX.
|
|
398
|
+
*
|
|
399
|
+
* Opening is conditional on purpose — in `single` mode an unconditional
|
|
400
|
+
* `setOpenIds([id])` would COLLAPSE the section you are already in when you
|
|
401
|
+
* click its own rail entry, so the rail would look like it closed the form.
|
|
402
|
+
*/
|
|
403
|
+
const openAndScrollTo = (id) => {
|
|
404
|
+
if (!openIds.includes(id)) {
|
|
405
|
+
setOpenIds(expandMode === "single" ? [id] : [...openIds, id]);
|
|
406
|
+
}
|
|
407
|
+
scrollToSection(id);
|
|
408
|
+
};
|
|
394
409
|
/*
|
|
395
410
|
* `sticky` under the measured header, and it publishes NO height: per the
|
|
396
411
|
* three-tier rule a sticky element CONSUMES the tier and never joins it.
|
|
@@ -408,21 +423,28 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
|
|
|
408
423
|
* comment syntax does not parse. That is the fourth time today; the rule is
|
|
409
424
|
* simply that JSX comments belong in CHILDREN position and nowhere else.
|
|
410
425
|
*/
|
|
411
|
-
const rail = showRail ? (_jsx(Div, { overflow: "auto",
|
|
426
|
+
const rail = showRail ? (_jsx(Div, { overflow: "auto",
|
|
427
|
+
/*
|
|
428
|
+
* <StickyToolbar> is a horizontal translucent bar: it ships `border-b`, a
|
|
429
|
+
* z-10 layer and the dismiss/re-expand CollapseStrip, and offers no
|
|
430
|
+
* `self-start`, `max-h` or overflow scrolling. Using it here would paint
|
|
431
|
+
* a bottom border under a 14rem sidebar and give it a horizontal bar's
|
|
432
|
+
* collapse affordance.
|
|
433
|
+
*
|
|
434
|
+
* 🛑 The marker below must stay on the line DIRECTLY above the className.
|
|
435
|
+
* Every one of these audits reads `lines[i - 1]` and nothing further, so
|
|
436
|
+
* a tidy explanation with the marker on its first line suppresses
|
|
437
|
+
* nothing while looking suppressed to a human (CLAUDE.md § Rule #5).
|
|
438
|
+
*/
|
|
439
|
+
// audit-raw-sticky-toolbar-ok: a vertical RAIL, not a toolbar — see above.
|
|
440
|
+
className: "hidden lg:block w-56 flex-shrink-0 sticky self-start top-[calc(var(--header-height,3.5rem)+1rem)] max-h-[calc(100vh-var(--header-height,3.5rem)-2rem)]", children: _jsx(Nav, { "aria-label": "Form sections", children: _jsx(Stack, { gap: "px", children: ordered.map((section) => {
|
|
412
441
|
const errorCount = errorCountFor(section);
|
|
413
442
|
const isOpen = section.required === true ||
|
|
414
443
|
openIds.includes(section.id) ||
|
|
415
444
|
forceOpenIds.includes(section.id);
|
|
416
445
|
return (_jsxs(Button, { type: "button",
|
|
417
446
|
/* `ghost`, not `bare` — UI_BUTTON has no `bare` variant. */
|
|
418
|
-
variant: "ghost", size: "sm", justify: "between", gap: "sm", className: `w-full text-left ${isOpen ? "appkit-section-rail__item--active" : ""}`, "aria-current": isOpen ? "true" : undefined, onClick: () => {
|
|
419
|
-
if (!openIds.includes(section.id)) {
|
|
420
|
-
setOpenIds(expandMode === "single"
|
|
421
|
-
? [section.id]
|
|
422
|
-
: [...openIds, section.id]);
|
|
423
|
-
}
|
|
424
|
-
scrollToSection(section.id);
|
|
425
|
-
}, children: [_jsx(Span, { size: "sm", color: isOpen ? "primary" : "muted", children: section.label }), errorCount > 0 && (_jsx(Span, { size: "xs", color: "error", weight: "semibold", children: errorCount }))] }, section.id));
|
|
447
|
+
variant: "ghost", size: "sm", justify: "between", gap: "sm", className: `w-full text-left ${isOpen ? "appkit-section-rail__item--active" : ""}`, "aria-current": isOpen ? "true" : undefined, onClick: () => openAndScrollTo(section.id), children: [_jsx(Span, { size: "sm", color: isOpen ? "primary" : "muted", children: section.label }), errorCount > 0 && (_jsx(Span, { size: "xs", color: "error", weight: "semibold", children: errorCount }))] }, section.id));
|
|
426
448
|
}) }) }) })) : null;
|
|
427
449
|
const panels = (_jsxs(Stack, { gap: "md", children: [ordered.map((section) => {
|
|
428
450
|
const isRequired = section.required === true;
|
|
@@ -6,3 +6,5 @@ export { rasterisePartToBox } from "../../_internal/shared/features/sticker-trac
|
|
|
6
6
|
export { buildCutFileBytes, buildCutFileForRun } from "../../_internal/server/features/sticker-trace/build-cut-file";
|
|
7
7
|
export { collectMosaicInputs, type MosaicInputs, type MosaicPartInput, } from "../../_internal/server/features/sticker-trace/mosaic-inputs";
|
|
8
8
|
export type { PrintPageSizeId } from "../../_internal/shared/features/sticker-trace/export/page-sizes";
|
|
9
|
+
export type { DecodeOptions, ImageCodec, PreviewOptions, ProbeResult, } from "../../_internal/shared/features/sticker-trace/codec";
|
|
10
|
+
export type { DecodedRaster, EncodedImage, Rect as StickerRect, } from "../../_internal/shared/features/sticker-trace/types";
|
package/dist/http/api-handler.js
CHANGED
|
@@ -2,7 +2,10 @@ import { normalizeError } from "../errors/normalize.js";
|
|
|
2
2
|
import { isAccountDisabled } from "../features/auth/role-predicates.js";
|
|
3
3
|
import { createApiHandlerFactory } from "../next/api/apiHandler.js";
|
|
4
4
|
import { applyRateLimit } from "../security/index.js";
|
|
5
|
-
import { AuthenticationError, AuthorizationError, ERROR_MESSAGES,
|
|
5
|
+
import { AuthenticationError, AuthorizationError, ERROR_MESSAGES, } from "../errors/index.js";
|
|
6
|
+
// Next-coupled, so it comes from the defining module — see errors/index.ts.
|
|
7
|
+
// This file is an API-route handler, so `next/server` here is correct.
|
|
8
|
+
import { handleApiError } from "../errors/error-handler.js";
|
|
6
9
|
import { errorResponse } from "../next/api/api-response.js";
|
|
7
10
|
import { serverLogger } from "../monitoring/index.js";
|
|
8
11
|
import { getProviders } from "../contracts/index.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -321,9 +321,9 @@ export { ERROR_CODES, BID_ERROR_CODES, OFFER_ERROR_CODES } from "./errors/index"
|
|
|
321
321
|
export { ERROR_MESSAGES } from "./errors/index";
|
|
322
322
|
export { NotFoundError } from "./errors/index";
|
|
323
323
|
export { ValidationError } from "./errors/index";
|
|
324
|
-
export { handleApiError } from "./errors/
|
|
325
|
-
export { isAppError } from "./errors/
|
|
326
|
-
export { logError } from "./errors/
|
|
324
|
+
export { handleApiError } from "./errors/error-handler";
|
|
325
|
+
export { isAppError } from "./errors/error-handler";
|
|
326
|
+
export { logError } from "./errors/error-handler";
|
|
327
327
|
export type { ErrorCode } from "./errors/index";
|
|
328
328
|
export { PaymentProviderUnreachableError } from "./errors/index";
|
|
329
329
|
export { mapToHttpError, HTTP_ERROR_CODES } from "./errors/index";
|
package/dist/index.js
CHANGED
|
@@ -552,13 +552,13 @@ export { NotFoundError } from "./errors/index.js";
|
|
|
552
552
|
export { ValidationError } from "./errors/index.js";
|
|
553
553
|
// [UTIL]-Pure utility â€" no framework or runtime dependency; safe to import from any environment.
|
|
554
554
|
// handleApiError - Model for handle api error.
|
|
555
|
-
export { handleApiError } from "./errors/
|
|
555
|
+
export { handleApiError } from "./errors/error-handler.js";
|
|
556
556
|
// [UTIL]-Pure utility â€" no framework or runtime dependency; safe to import from any environment.
|
|
557
557
|
// isAppError - Model for is app error.
|
|
558
|
-
export { isAppError } from "./errors/
|
|
558
|
+
export { isAppError } from "./errors/error-handler.js";
|
|
559
559
|
// [UTIL]-Pure utility â€" no framework or runtime dependency; safe to import from any environment.
|
|
560
560
|
// logError - Helper for log error.
|
|
561
|
-
export { logError } from "./errors/
|
|
561
|
+
export { logError } from "./errors/error-handler.js";
|
|
562
562
|
// [UTIL]-Pure utility — no framework or runtime dependency; safe to import from any environment.
|
|
563
563
|
// PaymentProviderUnreachableError - Thrown when the payment gateway API exhausts retries.
|
|
564
564
|
export { PaymentProviderUnreachableError } from "./errors/index.js";
|
|
@@ -299,7 +299,6 @@ export declare const DEFAULT_ROUTE_MAP: {
|
|
|
299
299
|
readonly ADMIN: {
|
|
300
300
|
readonly DASHBOARD: "/admin/dashboard";
|
|
301
301
|
readonly SHIPMENTS: "/admin/shipments";
|
|
302
|
-
readonly INVENTORY: "/admin/inventory";
|
|
303
302
|
readonly INVENTORY_BOXES: "/admin/inventory/boxes";
|
|
304
303
|
readonly INVENTORY_BOX_DETAIL: (id: string) => string;
|
|
305
304
|
readonly INVENTORY_BOX_LABELS: "/admin/inventory/boxes/labels";
|
|
@@ -752,7 +751,6 @@ export declare const ROUTES: {
|
|
|
752
751
|
readonly ADMIN: {
|
|
753
752
|
readonly DASHBOARD: "/admin/dashboard";
|
|
754
753
|
readonly SHIPMENTS: "/admin/shipments";
|
|
755
|
-
readonly INVENTORY: "/admin/inventory";
|
|
756
754
|
readonly INVENTORY_BOXES: "/admin/inventory/boxes";
|
|
757
755
|
readonly INVENTORY_BOX_DETAIL: (id: string) => string;
|
|
758
756
|
readonly INVENTORY_BOX_LABELS: "/admin/inventory/boxes/labels";
|
|
@@ -433,7 +433,16 @@ export const DEFAULT_ROUTE_MAP = {
|
|
|
433
433
|
ADMIN: {
|
|
434
434
|
DASHBOARD: "/admin/dashboard",
|
|
435
435
|
SHIPMENTS: "/admin/shipments",
|
|
436
|
-
|
|
436
|
+
/*
|
|
437
|
+
* 🛑 There is deliberately NO `INVENTORY: "/admin/inventory"` key.
|
|
438
|
+
*
|
|
439
|
+
* I added one and never built the page behind it, and nothing ever
|
|
440
|
+
* referenced it — `audit-dead-route-key` caught it. A ROUTES key is a
|
|
441
|
+
* promise, and the two real surfaces (`INVENTORY_BOXES`,
|
|
442
|
+
* `INVENTORY_MOVEMENTS`) each carry their own nav entry, so an index page
|
|
443
|
+
* would be a third place to list the same two links. Build it only when
|
|
444
|
+
* there is a third child worth grouping.
|
|
445
|
+
*/
|
|
437
446
|
INVENTORY_BOXES: "/admin/inventory/boxes",
|
|
438
447
|
INVENTORY_BOX_DETAIL: (id) => `/admin/inventory/boxes/${id}`,
|
|
439
448
|
/*
|
|
@@ -1994,6 +1994,9 @@ export declare const SCHEMAS: {
|
|
|
1994
1994
|
}, import("zod").ZodTypeAny, "passthrough">;
|
|
1995
1995
|
userId: string;
|
|
1996
1996
|
couponCode: string;
|
|
1997
|
+
claimedAt: string | Date | import("zod").objectOutputType<{
|
|
1998
|
+
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
1999
|
+
}, import("zod").ZodTypeAny, "passthrough">;
|
|
1997
2000
|
couponId: string;
|
|
1998
2001
|
couponSnapshot: {
|
|
1999
2002
|
type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
|
|
@@ -2015,9 +2018,6 @@ export declare const SCHEMAS: {
|
|
|
2015
2018
|
storeId?: string | undefined;
|
|
2016
2019
|
description?: string | undefined;
|
|
2017
2020
|
};
|
|
2018
|
-
claimedAt: string | Date | import("zod").objectOutputType<{
|
|
2019
|
-
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
2020
|
-
}, import("zod").ZodTypeAny, "passthrough">;
|
|
2021
2021
|
expiresAt?: string | Date | import("zod").objectOutputType<{
|
|
2022
2022
|
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
2023
2023
|
}, import("zod").ZodTypeAny, "passthrough"> | null | undefined;
|
|
@@ -2034,6 +2034,9 @@ export declare const SCHEMAS: {
|
|
|
2034
2034
|
}, import("zod").ZodTypeAny, "passthrough">;
|
|
2035
2035
|
userId: string;
|
|
2036
2036
|
couponCode: string;
|
|
2037
|
+
claimedAt: string | Date | import("zod").objectInputType<{
|
|
2038
|
+
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
2039
|
+
}, import("zod").ZodTypeAny, "passthrough">;
|
|
2037
2040
|
couponId: string;
|
|
2038
2041
|
couponSnapshot: {
|
|
2039
2042
|
type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
|
|
@@ -2055,9 +2058,6 @@ export declare const SCHEMAS: {
|
|
|
2055
2058
|
storeId?: string | undefined;
|
|
2056
2059
|
description?: string | undefined;
|
|
2057
2060
|
};
|
|
2058
|
-
claimedAt: string | Date | import("zod").objectInputType<{
|
|
2059
|
-
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
2060
|
-
}, import("zod").ZodTypeAny, "passthrough">;
|
|
2061
2061
|
expiresAt?: string | Date | import("zod").objectInputType<{
|
|
2062
2062
|
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
2063
2063
|
}, import("zod").ZodTypeAny, "passthrough"> | null | undefined;
|
|
@@ -2105,16 +2105,19 @@ export declare const SCHEMAS: {
|
|
|
2105
2105
|
bxgy: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
2106
2106
|
buyQuantity: import("zod").ZodNumber;
|
|
2107
2107
|
getQuantity: import("zod").ZodNumber;
|
|
2108
|
+
maxItemPrice: import("zod").ZodOptional<import("zod").ZodEffects<import("zod").ZodEffects<import("zod").ZodNumber, number, number>, number, number>>;
|
|
2108
2109
|
applicableProducts: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
|
|
2109
2110
|
applicableCategories: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
|
|
2110
2111
|
}, "strip", import("zod").ZodTypeAny, {
|
|
2111
2112
|
buyQuantity: number;
|
|
2112
2113
|
getQuantity: number;
|
|
2114
|
+
maxItemPrice?: number | undefined;
|
|
2113
2115
|
applicableProducts?: string[] | undefined;
|
|
2114
2116
|
applicableCategories?: string[] | undefined;
|
|
2115
2117
|
}, {
|
|
2116
2118
|
buyQuantity: number;
|
|
2117
2119
|
getQuantity: number;
|
|
2120
|
+
maxItemPrice?: number | undefined;
|
|
2118
2121
|
applicableProducts?: string[] | undefined;
|
|
2119
2122
|
applicableCategories?: string[] | undefined;
|
|
2120
2123
|
}>>;
|
|
@@ -2261,6 +2264,7 @@ export declare const SCHEMAS: {
|
|
|
2261
2264
|
bxgy?: {
|
|
2262
2265
|
buyQuantity: number;
|
|
2263
2266
|
getQuantity: number;
|
|
2267
|
+
maxItemPrice?: number | undefined;
|
|
2264
2268
|
applicableProducts?: string[] | undefined;
|
|
2265
2269
|
applicableCategories?: string[] | undefined;
|
|
2266
2270
|
} | undefined;
|
|
@@ -2319,6 +2323,7 @@ export declare const SCHEMAS: {
|
|
|
2319
2323
|
bxgy?: {
|
|
2320
2324
|
buyQuantity: number;
|
|
2321
2325
|
getQuantity: number;
|
|
2326
|
+
maxItemPrice?: number | undefined;
|
|
2322
2327
|
applicableProducts?: string[] | undefined;
|
|
2323
2328
|
applicableCategories?: string[] | undefined;
|
|
2324
2329
|
} | undefined;
|
package/dist/server.d.ts
CHANGED
|
@@ -571,7 +571,6 @@ export type { ActionIndexValidationContext, ActionIndexValidationIssue, } from "
|
|
|
571
571
|
export { ActionIndexRepository, actionIndexRepository, } from "./features/search/action-index/repository";
|
|
572
572
|
export type { ActionIndexDocument } from "./features/search/action-index/repository";
|
|
573
573
|
export { REFUND_COPY } from "./_internal/shared/features/orders/refund-copy";
|
|
574
|
-
export { wouldBeFullRefund } from "./_internal/shared/checkout/refundable-base";
|
|
575
574
|
export { RETURN_REASON, FINAL_SALE_EXEMPT_REASONS, RETURN_REASON_LABEL, RETURN_REASON_GROUP_LABEL, isFinalSaleExempt, isReturnReason, selectableReturnReasons, } from "./_internal/shared/features/orders/return-reasons";
|
|
576
575
|
export type { ReturnReason } from "./_internal/shared/features/orders/return-reasons";
|
|
577
576
|
export { draftToProductInput, productToDraft, } from "./features/seller/utils/product-draft-mapping";
|
package/dist/server.js
CHANGED
|
@@ -1529,10 +1529,12 @@ export { validateActionIndexEntry, normaliseRoute, } from "./features/search/act
|
|
|
1529
1529
|
export { ActionIndexRepository, actionIndexRepository, } from "./features/search/action-index/repository.js";
|
|
1530
1530
|
export { REFUND_COPY } from "./_internal/shared/features/orders/refund-copy.js";
|
|
1531
1531
|
/*
|
|
1532
|
-
* `wouldBeFullRefund`
|
|
1533
|
-
*
|
|
1532
|
+
* 🛑 `wouldBeFullRefund` is deliberately NOT exported. I added it here, and
|
|
1533
|
+
* `audit-appkit-reexports` was right to refuse it: `_internal/shared/checkout/`
|
|
1534
|
+
* is not a `features/` module, so exporting it would have made a money
|
|
1535
|
+
* predicate public purely so one route could duplicate a decision
|
|
1536
|
+
* `processRefundAction` already makes — see the note on `ProcessRefundInput`.
|
|
1534
1537
|
*/
|
|
1535
|
-
export { wouldBeFullRefund } from "./_internal/shared/checkout/refundable-base.js";
|
|
1536
1538
|
// Return / refund reasons — the closed enum the final-sale gate branches on.
|
|
1537
1539
|
// See the module docstring for why the exempt set is "seller or carrier fault"
|
|
1538
1540
|
// rather than "serious reasons".
|
package/dist/styles.css
CHANGED
|
@@ -11350,6 +11350,57 @@ body:has(.appkit-page-breadcrumb) .appkit-auto-breadcrumbs {
|
|
|
11350
11350
|
* plain file like this one can never win an importance tie.
|
|
11351
11351
|
*/
|
|
11352
11352
|
|
|
11353
|
+
/* The kiss-cut label sheet. Shipped unbundled when it was written — the
|
|
11354
|
+
`.appkit-label-sheet` grid and its `@page` rules reached no consumer, so
|
|
11355
|
+
every printed sheet fell back to browser defaults and the die-cut
|
|
11356
|
+
alignment the component exists to produce could not happen. */
|
|
11357
|
+
/*
|
|
11358
|
+
* Print-layout classes for physical labels and printable documents.
|
|
11359
|
+
*
|
|
11360
|
+
* 🛑 THESE LIVE IN A STATIC STYLESHEET, NOT ONLY IN `labelPageCss`'s STRING.
|
|
11361
|
+
*
|
|
11362
|
+
* `labelPageCss()` and `documentPageCss()` emit an `@page` rule plus these
|
|
11363
|
+
* same `break-*` declarations into a runtime `<style>`. That is necessary for
|
|
11364
|
+
* `@page` — its `size` is computed from the chosen profile, so it cannot be
|
|
11365
|
+
* static — but it meant `.appkit-label` and `.appkit-label-sheet` existed
|
|
11366
|
+
* ONLY inside a template literal.
|
|
11367
|
+
*
|
|
11368
|
+
* Consequence: `audit-dead-css-classes` reported them as classes generating no
|
|
11369
|
+
* CSS rule, correctly, because nothing it can read defines them. A class whose
|
|
11370
|
+
* only definition is a string concatenation is invisible to every tool — the
|
|
11371
|
+
* audit, an editor's go-to-definition, and anyone grepping the stylesheets.
|
|
11372
|
+
* Declaring them here makes them real; the runtime string still carries its
|
|
11373
|
+
* own copy because a print view may be opened in a context where the bundled
|
|
11374
|
+
* stylesheet has not loaded, and a duplicated `break-inside: avoid` is
|
|
11375
|
+
* harmless where a missing one splits a label across two pages.
|
|
11376
|
+
*
|
|
11377
|
+
* @tag domain:print
|
|
11378
|
+
* @tag layer:feature
|
|
11379
|
+
*/
|
|
11380
|
+
|
|
11381
|
+
@media print {
|
|
11382
|
+
/*
|
|
11383
|
+
* A label must never be split across a page boundary. On thermal stock a
|
|
11384
|
+
* page IS a label, so a split would print the top half on one label and the
|
|
11385
|
+
* bottom half on the next — and the operator discovers it after both have
|
|
11386
|
+
* been peeled and stuck to something.
|
|
11387
|
+
*/
|
|
11388
|
+
.appkit-label {
|
|
11389
|
+
break-inside: avoid;
|
|
11390
|
+
page-break-inside: avoid;
|
|
11391
|
+
}
|
|
11392
|
+
|
|
11393
|
+
/*
|
|
11394
|
+
* A sheet is one A4 page of die-cut labels, or one contents sheet. Same
|
|
11395
|
+
* rule, different unit — splitting it would offset every label after the
|
|
11396
|
+
* break from its die-cut hole.
|
|
11397
|
+
*/
|
|
11398
|
+
.appkit-label-sheet {
|
|
11399
|
+
break-inside: avoid;
|
|
11400
|
+
page-break-inside: avoid;
|
|
11401
|
+
}
|
|
11402
|
+
}
|
|
11403
|
+
|
|
11353
11404
|
/*
|
|
11354
11405
|
* Third-party stylesheet, vendored into dist/vendor by copy-assets.mjs.
|
|
11355
11406
|
* It used to be a JS import inside ImageEditor.tsx, which made it invisible to
|
|
Binary file
|