@mohasinac/appkit 4.62.1 → 4.63.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/client/features/layout/DashboardLayoutClient.js +8 -29
- package/dist/_internal/client/features/layout/filterNavItems.d.ts +28 -0
- package/dist/_internal/client/features/layout/filterNavItems.js +35 -0
- package/dist/_internal/server/features/ads/booking.d.ts +118 -0
- package/dist/_internal/server/features/ads/booking.js +326 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +51 -11
- package/dist/_internal/server/features/checkout/actions.js +335 -80
- package/dist/_internal/server/features/checkout/expand-order-items.d.ts +1 -2
- package/dist/_internal/server/features/checkout/expand-order-items.js +32 -0
- package/dist/_internal/server/features/checkout/freeze-parts.d.ts +50 -0
- package/dist/_internal/server/features/checkout/freeze-parts.js +94 -0
- package/dist/_internal/server/features/checkout/value-otp-gate.d.ts +55 -0
- package/dist/_internal/server/features/checkout/value-otp-gate.js +60 -0
- package/dist/_internal/server/features/dashboard/attention.d.ts +128 -0
- package/dist/_internal/server/features/dashboard/attention.js +183 -0
- package/dist/_internal/server/features/dashboard/data.d.ts +47 -0
- package/dist/_internal/server/features/dashboard/data.js +82 -0
- package/dist/_internal/server/features/inventory/reservations.d.ts +99 -0
- package/dist/_internal/server/features/inventory/reservations.js +273 -0
- package/dist/_internal/server/features/offers/destinations.d.ts +40 -0
- package/dist/_internal/server/features/offers/destinations.js +65 -0
- package/dist/_internal/server/features/orders/adapters.js +21 -0
- package/dist/_internal/server/features/orders/amend-delivery.d.ts +56 -0
- package/dist/_internal/server/features/orders/amend-delivery.js +124 -0
- package/dist/_internal/server/features/orders/balance.d.ts +89 -0
- package/dist/_internal/server/features/orders/balance.js +256 -0
- package/dist/_internal/server/features/pricing/actions.d.ts +58 -0
- package/dist/_internal/server/features/pricing/actions.js +144 -0
- package/dist/_internal/server/features/shipping/book-split.d.ts +86 -0
- package/dist/_internal/server/features/shipping/book-split.js +130 -0
- package/dist/_internal/server/features/shipping/book.d.ts +61 -0
- package/dist/_internal/server/features/shipping/book.js +176 -0
- package/dist/_internal/server/features/shipping/courier-block.js +26 -1
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +14 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +14 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +57 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +2 -0
- package/dist/_internal/server/jobs/core/jobRunners.js +45 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +4 -1
- package/dist/_internal/server/jobs/core/reservationExpiry.d.ts +17 -0
- package/dist/_internal/server/jobs/core/reservationExpiry.js +40 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +15 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/reservationExpiry.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/reservationExpiry.js +2 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +268 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +31 -2
- package/dist/_internal/shared/checkout/order-math.js +22 -36
- package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
- package/dist/_internal/shared/checkout/rules/index.js +1 -1
- package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +18 -0
- package/dist/_internal/shared/checkout/rules/payment-gate.js +25 -0
- package/dist/_internal/shared/checkout/split.d.ts +0 -12
- package/dist/_internal/shared/checkout/split.js +22 -2
- package/dist/_internal/shared/content/disclaimers.js +7 -7
- package/dist/_internal/shared/content/published-values.d.ts +6 -1
- package/dist/_internal/shared/content/published-values.js +8 -0
- package/dist/_internal/shared/features/admin/integrations.d.ts +32 -2
- package/dist/_internal/shared/features/admin/integrations.js +19 -9
- package/dist/_internal/shared/features/dashboard/hub-sections.d.ts +72 -0
- package/dist/_internal/shared/features/dashboard/hub-sections.js +75 -0
- package/dist/_internal/shared/features/emi/choice.d.ts +98 -0
- package/dist/_internal/shared/features/emi/choice.js +69 -0
- package/dist/_internal/shared/features/emi/group.d.ts +84 -0
- package/dist/_internal/shared/features/emi/group.js +126 -0
- package/dist/_internal/shared/features/ledger/posting.d.ts +107 -0
- package/dist/_internal/shared/features/ledger/posting.js +165 -0
- package/dist/_internal/shared/features/ledger/reports.d.ts +76 -0
- package/dist/_internal/shared/features/ledger/reports.js +127 -0
- package/dist/_internal/shared/features/ledger/rules.d.ts +170 -0
- package/dist/_internal/shared/features/ledger/rules.js +221 -0
- package/dist/_internal/shared/features/offers/coordination.d.ts +122 -0
- package/dist/_internal/shared/features/offers/coordination.js +80 -0
- package/dist/_internal/shared/features/orders/delivery-snapshot.d.ts +114 -0
- package/dist/_internal/shared/features/orders/delivery-snapshot.js +116 -0
- package/dist/_internal/shared/features/payouts/pay-period.d.ts +91 -0
- package/dist/_internal/shared/features/payouts/pay-period.js +104 -0
- package/dist/_internal/shared/features/points/accepts.d.ts +1 -8
- package/dist/_internal/shared/features/points/accepts.js +12 -6
- package/dist/_internal/shared/features/pricing/rate-registry.d.ts +150 -0
- package/dist/_internal/shared/features/pricing/rate-registry.js +246 -0
- package/dist/_internal/shared/features/shipping/parcel.d.ts +123 -0
- package/dist/_internal/shared/features/shipping/parcel.js +139 -0
- package/dist/_internal/shared/features/site-settings/fees.d.ts +16 -8
- package/dist/_internal/shared/features/site-settings/fees.js +12 -8
- package/dist/_internal/shared/fees/explain.d.ts +81 -0
- package/dist/_internal/shared/fees/explain.js +101 -0
- package/dist/_internal/shared/history/index.d.ts +1 -1
- package/dist/_internal/shared/history/index.js +1 -1
- package/dist/_internal/shared/history/types.d.ts +7 -1
- package/dist/_internal/shared/history/types.js +25 -0
- package/dist/client.d.ts +11 -1
- package/dist/client.js +13 -0
- package/dist/constants/api-endpoints.d.ts +72 -0
- package/dist/constants/api-endpoints.js +41 -0
- package/dist/constants/field-names.d.ts +41 -0
- package/dist/constants/field-names.js +47 -0
- package/dist/features/account/components/UserOrdersView.js +28 -2
- package/dist/features/admin/components/AdminIntegrationsView.d.ts +55 -0
- package/dist/features/admin/components/AdminIntegrationsView.js +74 -0
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +16 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +72 -3
- package/dist/features/admin/components/AdminOrdersView.js +20 -1
- package/dist/features/admin/schemas/firestore.d.ts +32 -0
- package/dist/features/ads/schemas/booking-write.d.ts +120 -0
- package/dist/features/ads/schemas/booking-write.js +75 -0
- package/dist/features/ads/schemas/creative-form.d.ts +18 -0
- package/dist/features/ads/schemas/creative-form.js +51 -0
- package/dist/features/ads/schemas/firestore.d.ts +140 -0
- package/dist/features/ads/schemas/firestore.js +109 -0
- package/dist/features/auctions/repository/bid.repository.js +3 -1
- package/dist/features/audit-log/schemas/firestore.d.ts +110 -0
- package/dist/features/audit-log/schemas/firestore.js +74 -0
- package/dist/features/auth/actions/profile-actions.d.ts +69 -1
- package/dist/features/auth/actions/profile-actions.js +62 -1
- package/dist/features/auth/schemas/firestore.d.ts +16 -2
- package/dist/features/auth/schemas/firestore.js +2 -1
- package/dist/features/blog/actions/blog-actions.d.ts +6 -0
- package/dist/features/blog/actions/blog-actions.js +17 -2
- package/dist/features/blog/api/route.js +4 -1
- package/dist/features/blog/columns/index.js +5 -0
- package/dist/features/blog/components/BlogIndexPageView.js +2 -1
- package/dist/features/blog/repository/blog.repository.d.ts +4 -0
- package/dist/features/blog/repository/blog.repository.js +5 -0
- package/dist/features/blog/schemas/blog-form.d.ts +3 -0
- package/dist/features/blog/schemas/blog-form.js +1 -0
- package/dist/features/blog/schemas/firestore.d.ts +38 -1
- package/dist/features/blog/schemas/firestore.js +23 -0
- package/dist/features/blog/schemas/index.d.ts +6 -0
- package/dist/features/blog/schemas/index.js +2 -0
- package/dist/features/blog/types/index.d.ts +2 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +12 -1
- package/dist/features/cart/components/CartPriceBreakdown.js +24 -5
- package/dist/features/cart/repository/cart.repository.d.ts +14 -0
- package/dist/features/cart/repository/cart.repository.js +20 -0
- package/dist/features/cart/schemas/firestore.d.ts +24 -0
- package/dist/features/dashboard/components/DashboardHubView.d.ts +83 -0
- package/dist/features/dashboard/components/DashboardHubView.js +56 -0
- package/dist/features/dashboard/components/DashboardSectionsClient.d.ts +23 -0
- package/dist/features/dashboard/components/DashboardSectionsClient.js +66 -0
- package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.d.ts +55 -0
- package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.js +175 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +24 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +28 -0
- package/dist/features/dispute-reasons/schemas/firestore.d.ts +3 -5
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/faq/components/FAQPageContent.js +2 -2
- package/dist/features/gateway-events/schemas/index.d.ts +2 -2
- package/dist/features/homepage/schemas/index.d.ts +8 -8
- package/dist/features/inventory/schemas/stock-reservation.d.ts +114 -0
- package/dist/features/inventory/schemas/stock-reservation.js +103 -0
- package/dist/features/layout/AppLayoutShell.d.ts +9 -1
- package/dist/features/layout/AppLayoutShell.js +12 -4
- package/dist/features/layout/BottomActions.js +2 -1
- package/dist/features/layout/BottomNavbar.d.ts +10 -2
- package/dist/features/layout/SideActionDock.js +52 -2
- package/dist/features/layout/TitleBarLayout.js +13 -4
- package/dist/features/layout/TitleBarLayout.style.css +59 -0
- package/dist/features/layout/useCtaReveal.d.ts +16 -1
- package/dist/features/layout/useCtaReveal.js +17 -26
- package/dist/features/ledger/repository/journal.repository.d.ts +110 -0
- package/dist/features/ledger/repository/journal.repository.js +312 -0
- package/dist/features/ledger/schemas/expenses.d.ts +87 -0
- package/dist/features/ledger/schemas/expenses.js +27 -0
- package/dist/features/ledger/schemas/firestore.d.ts +183 -0
- package/dist/features/ledger/schemas/firestore.js +69 -0
- package/dist/features/orders/components/BalancePaymentPanel.d.ts +12 -0
- package/dist/features/orders/components/BalancePaymentPanel.js +94 -0
- package/dist/features/orders/components/RaiseDisputeForm.js +19 -11
- package/dist/features/orders/repository/orders.repository.d.ts +28 -1
- package/dist/features/orders/repository/orders.repository.js +60 -6
- package/dist/features/orders/schemas/balance.d.ts +40 -0
- package/dist/features/orders/schemas/balance.js +47 -0
- package/dist/features/orders/schemas/firestore.d.ts +202 -0
- package/dist/features/orders/schemas/firestore.js +43 -0
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/orders/types/index.d.ts +21 -0
- package/dist/features/payments/repository/payout.repository.js +3 -2
- package/dist/features/payments/schemas/firestore.d.ts +92 -1
- package/dist/features/payments/schemas/firestore.js +23 -0
- package/dist/features/pricing/components/AdminPricingView.d.ts +13 -0
- package/dist/features/pricing/components/AdminPricingView.js +157 -0
- package/dist/features/products/schemas/firestore.d.ts +26 -1
- package/dist/features/products/schemas/firestore.js +2 -0
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/reviews/repository/reviews.repository.d.ts +14 -0
- package/dist/features/reviews/repository/reviews.repository.js +16 -0
- package/dist/features/scams/repository/scammer.repository.js +2 -1
- package/dist/features/scams/schemas/firestore.d.ts +2 -1
- package/dist/features/scams/schemas/firestore.js +2 -1
- package/dist/features/seller/actions/offer-actions.js +56 -0
- package/dist/features/seller/actions/seller-actions.d.ts +1 -1
- package/dist/features/seller/actions/seller-actions.js +4 -0
- package/dist/features/seller/components/DispatchForm.d.ts +11 -0
- package/dist/features/seller/components/DispatchForm.js +190 -0
- package/dist/features/seller/components/SellerOrdersView.js +94 -2
- package/dist/features/seller/components/index.d.ts +1 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/seller/repository/offer.repository.js +4 -3
- package/dist/features/seller/schemas/firestore.d.ts +23 -2
- package/dist/features/seller/schemas/firestore.js +3 -2
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/seller/schemas/order-forms.d.ts +61 -4
- package/dist/features/seller/schemas/order-forms.js +41 -8
- package/dist/features/shipments/schemas/firestore.d.ts +25 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +39 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +55 -0
- package/dist/features/shipping-boxes/schemas/firestore.d.ts +2 -4
- package/dist/features/stores/repository/store.repository.d.ts +18 -0
- package/dist/features/stores/repository/store.repository.js +27 -1
- package/dist/features/stores/schemas/firestore.d.ts +41 -0
- package/dist/features/stores/schemas/firestore.js +20 -0
- package/dist/features/stores/schemas/index.d.ts +6 -6
- package/dist/features/support/repository/support.repository.js +72 -6
- package/dist/features/support/schemas/firestore.d.ts +17 -0
- package/dist/features/support/schemas/index.d.ts +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +19 -0
- package/dist/next/routing/route-map.d.ts +92 -0
- package/dist/next/routing/route-map.js +53 -0
- package/dist/react/contexts/SessionContext.d.ts +17 -1
- package/dist/schemas/registry.d.ts +22 -16
- package/dist/security/pii-schemas.d.ts +1 -1
- package/dist/security/pii-schemas.js +45 -0
- package/dist/server.d.ts +27 -1
- package/dist/server.js +144 -0
- package/dist/styles.css +349 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.d.ts +10 -1
- package/dist/ui/components/Button.js +25 -3
- package/dist/ui/components/Chart.d.ts +63 -0
- package/dist/ui/components/Chart.js +169 -0
- package/dist/ui/components/Chart.style.css +214 -0
- package/dist/ui/components/Disclaimer.js +10 -1
- package/dist/ui/components/OverflowRow.js +54 -2
- package/dist/ui/components/OverflowRow.style.css +38 -0
- package/dist/ui/components/RichTextEditor.js +99 -8
- package/dist/ui/components/chart-spec.d.ts +68 -0
- package/dist/ui/components/chart-spec.js +140 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/components/surface-tokens.d.ts +1 -1
- package/dist/ui/components/surface-tokens.js +9 -0
- package/dist/ui/components/toolbar-collapse.d.ts +13 -0
- package/dist/ui/components/toolbar-collapse.js +10 -1
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/rich-text/RichText.d.ts +10 -1
- package/dist/ui/rich-text/RichText.js +33 -2
- package/dist/ui/rich-text/RichText.style.css +35 -0
- package/dist/ui/rich-text/RichTextRenderer.d.ts +30 -1
- package/dist/ui/rich-text/RichTextRenderer.js +34 -2
- package/dist/ui/rich-text/chart-blocks.d.ts +62 -0
- package/dist/ui/rich-text/chart-blocks.js +87 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +3 -0
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
import { serverLogger } from "../../../monitoring/index.js";
|
|
8
8
|
import { offerRepository } from "../repository/offer.repository.js";
|
|
9
9
|
import { productRepository } from "../../products/repository/products.repository.js";
|
|
10
|
+
import { categoriesRepository } from "../../categories/repository/categories.repository.js";
|
|
11
|
+
import { resolveParcel } from "../../../_internal/shared/features/shipping/parcel.js";
|
|
12
|
+
import { checkDestinations, assertAllServiceable, } from "../../../_internal/server/features/shipping/book-split.js";
|
|
10
13
|
import { ProductStatusValues } from "../../products/schemas/index.js";
|
|
11
14
|
import { sendNotification } from "../../admin/actions/notification-actions.js";
|
|
12
15
|
import { userRepository } from "../../auth/repository/user.repository.js";
|
|
@@ -184,6 +187,59 @@ export async function respondToOffer(userId, input) {
|
|
|
184
187
|
throw new ValidationError(ERROR_MESSAGES.OFFER.EXPIRED);
|
|
185
188
|
let updated;
|
|
186
189
|
if (action === "accept") {
|
|
190
|
+
/*
|
|
191
|
+
* ── S5a: a coordinated offer must be SHIPPABLE before it is binding ──
|
|
192
|
+
*
|
|
193
|
+
* Accepting locks a price and starts the buyer's checkout deadline, so it
|
|
194
|
+
* is the last moment at which "the courier cannot reach one of these
|
|
195
|
+
* addresses" costs nobody anything. After it, the coordinator has paid
|
|
196
|
+
* and the only remedies left are a refund and an apology.
|
|
197
|
+
*
|
|
198
|
+
* 🛑 Deliberately NOT at booking time, which is the obvious place and the
|
|
199
|
+
* wrong one: by then the money is taken, and a four-destination order
|
|
200
|
+
* that fails on the fourth has three parcels the carrier already accepted
|
|
201
|
+
* and no rollback. `assertAllServiceable` still guards the booking path —
|
|
202
|
+
* this is the check that keeps it from ever being reached in that state.
|
|
203
|
+
*
|
|
204
|
+
* 🛑 Costs NOTHING on an ordinary offer. `coordination` is absent on
|
|
205
|
+
* every non-coordinated one, so this whole block is skipped — a check
|
|
206
|
+
* that charges every seller for a feature most never use is how Rule #6's
|
|
207
|
+
* round-trip budget gets spent by accident.
|
|
208
|
+
*
|
|
209
|
+
* 🛑 The weight comes from `resolveParcel`, NOT from a literal. That
|
|
210
|
+
* module's whole argument is that an item weight systematically
|
|
211
|
+
* under-estimates a packed box (a bey is ~30 g loose and ~300 g boxed),
|
|
212
|
+
* and a hard-coded `1` here would be a fourth, worse tier beside the
|
|
213
|
+
* three it defines. The shipment tier is deliberately absent — nothing is
|
|
214
|
+
* packed yet at accept time, which is exactly the case tier two and three
|
|
215
|
+
* exist for.
|
|
216
|
+
*
|
|
217
|
+
* 🛑 **No resolvable weight SKIPS the check** rather than guessing one.
|
|
218
|
+
* The carrier is being asked "can you reach this pincode with a parcel of
|
|
219
|
+
* this size", so an invented weight asks a question about a parcel that
|
|
220
|
+
* does not exist and may refuse a destination that is perfectly
|
|
221
|
+
* reachable. Not checking is honest; the booking path still has
|
|
222
|
+
* `assertAllServiceable`, and a listing with no weight at any tier has a
|
|
223
|
+
* data problem this is not the place to report.
|
|
224
|
+
*/
|
|
225
|
+
const members = offer.coordination?.members ?? [];
|
|
226
|
+
if (members.length > 0) {
|
|
227
|
+
const offerProduct = await productRepository.findById(offer.productId);
|
|
228
|
+
/*
|
|
229
|
+
* 🛑 `categorySlugs[0]` is the LEAF, and `findById` is the right lookup
|
|
230
|
+
* for it. Products carry the full ancestor chain `[leaf, …, root]` and
|
|
231
|
+
* categories are pure slugs (`id === slug`), so this is one get rather
|
|
232
|
+
* than the `where("slug","==")` query `findBySlugAndType` would run —
|
|
233
|
+
* and the leaf is the tier whose packed default is actually about this
|
|
234
|
+
* kind of item. A root's default would be the average of the catalogue.
|
|
235
|
+
*/
|
|
236
|
+
const leafSlug = offerProduct?.categorySlugs?.[0];
|
|
237
|
+
const leaf = leafSlug ? await categoriesRepository.findById(leafSlug) : null;
|
|
238
|
+
const parcel = resolveParcel(null, offerProduct, leaf?.productDefaults ?? null);
|
|
239
|
+
if (parcel) {
|
|
240
|
+
assertAllServiceable(await checkDestinations(members, parcel.weightG / 1000));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
187
243
|
updated = await offerRepository.accept(offerId, offer.offerAmount, sellerNote, new Date(Date.now() + OFFER_CHECKOUT_WINDOW_MS));
|
|
188
244
|
}
|
|
189
245
|
else if (action === "decline") {
|
|
@@ -181,7 +181,7 @@ export declare function listSellerPayouts(userId: string, params?: {
|
|
|
181
181
|
page?: number;
|
|
182
182
|
pageSize?: number;
|
|
183
183
|
}): Promise<{
|
|
184
|
-
items: import("../../payments").
|
|
184
|
+
items: import("../../payments").StorePayoutDocument[];
|
|
185
185
|
total: number;
|
|
186
186
|
page: number;
|
|
187
187
|
pageSize: number;
|
|
@@ -299,6 +299,8 @@ export async function requestPayout(userId, userName, userEmail, input) {
|
|
|
299
299
|
if (earnings.netAmount <= 0 || earnings.eligibleOrders.length === 0)
|
|
300
300
|
throw new ValidationError("No eligible earnings available for payout.");
|
|
301
301
|
const payout = await payoutRepository.createPayout({
|
|
302
|
+
/* A seller requesting their own earnings — a liability settlement. */
|
|
303
|
+
payeeType: "store",
|
|
302
304
|
storeId: userId,
|
|
303
305
|
sellerName: userName,
|
|
304
306
|
sellerEmail: userEmail,
|
|
@@ -391,6 +393,8 @@ export async function bulkSellerOrder(userId, userRole, userDisplayName, userEma
|
|
|
391
393
|
// The store-funded points, both ends — see the preview path above.
|
|
392
394
|
ordersPointsBorneByStore(eligible));
|
|
393
395
|
const payoutDoc = await payoutRepository.createPayout({
|
|
396
|
+
/* A seller requesting their own earnings — a liability settlement. */
|
|
397
|
+
payeeType: "store",
|
|
394
398
|
storeId: userId,
|
|
395
399
|
sellerName: userDisplayName,
|
|
396
400
|
sellerEmail: userEmail,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export declare function DispatchForm({ orderId,
|
|
3
|
+
/** Already dispatched? Then there is nothing to book and nothing to record. */
|
|
4
|
+
trackingNumber,
|
|
5
|
+
/** Set by `bookShipment`; a booked parcel must not be re-booked. */
|
|
6
|
+
platformBooked, onDispatched, }: {
|
|
7
|
+
orderId: string;
|
|
8
|
+
trackingNumber?: string;
|
|
9
|
+
platformBooked?: boolean;
|
|
10
|
+
onDispatched: () => void;
|
|
11
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
/*
|
|
4
|
+
* WHY: Bug 0e — a seller could not express "book this through us". The write
|
|
5
|
+
* shape said `method: z.literal("custom")`, so there was no option to
|
|
6
|
+
* offer; `platformBookedShipment`, which `order-math.ts` gates shipping
|
|
7
|
+
* reimbursement on, had ZERO writers.
|
|
8
|
+
*
|
|
9
|
+
* The schema now carries a discriminated union and the server does the
|
|
10
|
+
* booking. This is the control that reaches it.
|
|
11
|
+
*
|
|
12
|
+
* WHAT: One dispatch panel with two branches — post it yourself, or book a
|
|
13
|
+
* pickup on the platform's carrier account.
|
|
14
|
+
*
|
|
15
|
+
* EXPORTS: DispatchForm
|
|
16
|
+
*
|
|
17
|
+
* @tag domain:seller,orders,shipping
|
|
18
|
+
* @tag layer:component
|
|
19
|
+
* @tag pattern:form
|
|
20
|
+
* @tag access:client
|
|
21
|
+
* @tag consumers:SellerOrderDetailPanel
|
|
22
|
+
* @tag sideEffects:network
|
|
23
|
+
*/
|
|
24
|
+
import React, { useState } from "react";
|
|
25
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
|
|
26
|
+
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
27
|
+
import { apiClient, ApiClientError } from "../../../http/index.js";
|
|
28
|
+
import { useApiMutation } from "../../../client/api/useApiMutation.js";
|
|
29
|
+
import { useQuery } from "@tanstack/react-query";
|
|
30
|
+
import { normalizeError } from "../../../errors/normalize.js";
|
|
31
|
+
import { toUserMessage } from "../../../errors/error-display-map.js";
|
|
32
|
+
import { Button } from "../../../ui/components/Button.js";
|
|
33
|
+
import { Div } from "../../../ui/components/Div.js";
|
|
34
|
+
import { Row } from "../../../ui/components/Layout.js";
|
|
35
|
+
import { Stack } from "../../../ui/components/Layout.js";
|
|
36
|
+
import { Text } from "../../../ui/components/Typography.js";
|
|
37
|
+
import { Form } from "../../../ui/components/Form.js";
|
|
38
|
+
import { FieldInput } from "../../../ui/forms/FieldInput.js";
|
|
39
|
+
import { FieldSelect } from "../../../ui/forms/FieldSelect.js";
|
|
40
|
+
import { shipOrderSchema } from "../schemas/order-forms.js";
|
|
41
|
+
import { applyZodIssues, rootLevelMessages } from "../../../ui/forms/apply-zod-issues.js";
|
|
42
|
+
/**
|
|
43
|
+
* Box presets, in centimetres and kilograms.
|
|
44
|
+
*
|
|
45
|
+
* 🛑 A PRESET rather than four number inputs, and that is the whole UX
|
|
46
|
+
* argument: a seller packing a Beyblade does not know the parcel's
|
|
47
|
+
* dimensions, and a carrier rejects a wrong one with an opaque error at
|
|
48
|
+
* pickup time rather than at booking time. Four free fields produce four
|
|
49
|
+
* chances to guess. "Custom" exists for the genuinely odd parcel.
|
|
50
|
+
*/
|
|
51
|
+
const BOX_PRESETS = [
|
|
52
|
+
{ id: "small", label: "Small — single top (0.3 kg)", weight: 0.3, length: 20, width: 15, height: 10 },
|
|
53
|
+
{ id: "medium", label: "Medium — a few items (1 kg)", weight: 1, length: 30, width: 25, height: 15 },
|
|
54
|
+
{ id: "large", label: "Large — stadium or bundle (3 kg)", weight: 3, length: 45, width: 35, height: 25 },
|
|
55
|
+
];
|
|
56
|
+
export function DispatchForm({ orderId,
|
|
57
|
+
/** Already dispatched? Then there is nothing to book and nothing to record. */
|
|
58
|
+
trackingNumber,
|
|
59
|
+
/** Set by `bookShipment`; a booked parcel must not be re-booked. */
|
|
60
|
+
platformBooked, onDispatched, }) {
|
|
61
|
+
const [method, setMethod] = useState("custom");
|
|
62
|
+
const [carrier, setCarrier] = useState("");
|
|
63
|
+
const [tracking, setTracking] = useState("");
|
|
64
|
+
const [trackingUrl, setTrackingUrl] = useState("");
|
|
65
|
+
const [pickupId, setPickupId] = useState("");
|
|
66
|
+
const [boxId, setBoxId] = useState(BOX_PRESETS[1].id);
|
|
67
|
+
const [error, setError] = useState(null);
|
|
68
|
+
/*
|
|
69
|
+
* 🛑 Fetched ONLY when the platform branch is open. The custom branch is
|
|
70
|
+
* the common case and needs no address at all, so loading this on mount
|
|
71
|
+
* would be a request on every order panel open for a field most sellers
|
|
72
|
+
* never reach (Rule #6).
|
|
73
|
+
*/
|
|
74
|
+
/*
|
|
75
|
+
* 🛑 `toUserMessage(code)` on failure, never the server's own `error`
|
|
76
|
+
* string — an unmapped code would print server internals into the panel,
|
|
77
|
+
* which is literally how a Node `MODULE_NOT_FOUND` with a require stack
|
|
78
|
+
* reached a buyer-facing modal (Root Cause #86). `ApiClientError` carries
|
|
79
|
+
* the stable code, so the mapping survives the move off raw `fetch`.
|
|
80
|
+
*/
|
|
81
|
+
/*
|
|
82
|
+
* 🛑 A REF, because `setFieldError` comes from `<Form>`'s render-prop and
|
|
83
|
+
* therefore does not exist in component scope where the mutation is
|
|
84
|
+
* declared. The mutation needs a STABLE sink — passing the render-prop
|
|
85
|
+
* helper directly is impossible, and re-creating the mutation per render to
|
|
86
|
+
* close over it would reset its pending state mid-request.
|
|
87
|
+
*
|
|
88
|
+
* It is assigned inside the render-prop below, so by the time any submit
|
|
89
|
+
* can fire it is populated. The `?.` is not defensive decoration: the first
|
|
90
|
+
* render completes before `<Form>` calls its child.
|
|
91
|
+
*/
|
|
92
|
+
const fieldErrorSink = React.useRef(null);
|
|
93
|
+
const shipMutation = useApiMutation({
|
|
94
|
+
/*
|
|
95
|
+
* 🛑 Passed so the server's own per-field `issues` land on the fields
|
|
96
|
+
* they name. The route parses the same `shipOrderSchema` this form does,
|
|
97
|
+
* so a rejection it produces that the client parse did not is real news
|
|
98
|
+
* — a carrier the courier does not recognise, a pickup address that
|
|
99
|
+
* stopped being serviceable — and collapsing that into one generic
|
|
100
|
+
* sentence hides which input to fix.
|
|
101
|
+
*/
|
|
102
|
+
setFieldError: (field, message) => fieldErrorSink.current?.(field, message),
|
|
103
|
+
mutationFn: (payload) => apiClient.post(SELLER_ENDPOINTS.ORDER_SHIP(orderId), payload),
|
|
104
|
+
onSuccess: () => onDispatched(),
|
|
105
|
+
onError: (err) => {
|
|
106
|
+
void normalizeError(err);
|
|
107
|
+
setError(toUserMessage(err instanceof ApiClientError ? err.code : undefined));
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
/*
|
|
111
|
+
* 🛑 `enabled` is what keeps this request off the common path. The custom
|
|
112
|
+
* branch needs no address at all, so loading pickups on mount would fire a
|
|
113
|
+
* request on every order panel a seller opens for a field most of them
|
|
114
|
+
* never reach (Rule #6). `useQuery` also dedupes and caches it, which the
|
|
115
|
+
* hand-rolled effect could not — it refetched on every branch toggle.
|
|
116
|
+
*/
|
|
117
|
+
const pickupsQuery = useQuery({
|
|
118
|
+
queryKey: ["store", "addresses", "pickup"],
|
|
119
|
+
enabled: method === "platform",
|
|
120
|
+
queryFn: () => apiClient.get(SELLER_ENDPOINTS.STORE_ADDRESSES),
|
|
121
|
+
});
|
|
122
|
+
const pickups = Array.isArray(pickupsQuery.data?.items)
|
|
123
|
+
? pickupsQuery.data.items
|
|
124
|
+
: [];
|
|
125
|
+
/*
|
|
126
|
+
* 🛑 A FAILED request and an empty list must not render the same. An empty
|
|
127
|
+
* dropdown reads as "this store has no pickup addresses" — a fact about the
|
|
128
|
+
* store — when it may be a network error, and the two need different
|
|
129
|
+
* actions from the seller.
|
|
130
|
+
*/
|
|
131
|
+
const pickupsFailed = pickupsQuery.isError;
|
|
132
|
+
React.useEffect(() => {
|
|
133
|
+
if (pickups.length === 1 && !pickupId)
|
|
134
|
+
setPickupId(pickups[0].id);
|
|
135
|
+
}, [pickups, pickupId]);
|
|
136
|
+
if (trackingNumber) {
|
|
137
|
+
return (_jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Dispatched" }), _jsx(Text, { size: "xs", color: "muted", children: platformBooked
|
|
138
|
+
? `Booked through LetItRip · ${trackingNumber}`
|
|
139
|
+
: `Posted by you · ${trackingNumber}` })] }));
|
|
140
|
+
}
|
|
141
|
+
const submit = async (
|
|
142
|
+
/* The `<Form>` render-prop's own signature — `applyZodIssues` clears a
|
|
143
|
+
* field by passing `null`, so a `string`-only sink is not assignable. */
|
|
144
|
+
setFieldError, clearErrors) => {
|
|
145
|
+
clearErrors();
|
|
146
|
+
setError(null);
|
|
147
|
+
const box = BOX_PRESETS.find((b) => b.id === boxId) ?? BOX_PRESETS[1];
|
|
148
|
+
const payload = method === "platform"
|
|
149
|
+
? {
|
|
150
|
+
method: "platform",
|
|
151
|
+
pickupAddressId: pickupId,
|
|
152
|
+
dimensions: {
|
|
153
|
+
weight: box.weight,
|
|
154
|
+
length: box.length,
|
|
155
|
+
width: box.width,
|
|
156
|
+
height: box.height,
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
: {
|
|
160
|
+
method: "custom",
|
|
161
|
+
shippingCarrier: carrier.trim(),
|
|
162
|
+
trackingNumber: tracking.trim(),
|
|
163
|
+
trackingUrl: trackingUrl.trim(),
|
|
164
|
+
};
|
|
165
|
+
/*
|
|
166
|
+
* Parsed against the SAME schema the route and the action use, so the
|
|
167
|
+
* branch's required fields are stated once. `applyZodIssues` can only
|
|
168
|
+
* attach path-bearing issues, so root-level refinements are surfaced
|
|
169
|
+
* separately or a refused save shows nothing at all.
|
|
170
|
+
*/
|
|
171
|
+
const parsed = shipOrderSchema.safeParse(payload);
|
|
172
|
+
if (!parsed.success) {
|
|
173
|
+
applyZodIssues(parsed.error.issues, setFieldError);
|
|
174
|
+
const root = rootLevelMessages(parsed.error.issues);
|
|
175
|
+
if (root.length > 0)
|
|
176
|
+
setError(root.join(" "));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
shipMutation.mutate(parsed.data);
|
|
180
|
+
};
|
|
181
|
+
return (_jsxs(Stack, { gap: "3", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Dispatch" }), _jsxs(Row, { gap: "sm", children: [_jsx(Button, { size: "sm", variant: method === "custom" ? "primary" : "outline", "aria-pressed": method === "custom", onClick: () => setMethod("custom"), children: "I posted it" }), _jsx(Button, { size: "sm", variant: method === "platform" ? "primary" : "outline", "aria-pressed": method === "platform", onClick: () => setMethod("platform"), children: "Book through LetItRip" })] }), _jsx(Form, { onSubmit: (e) => e.preventDefault(), children: ({ setFieldError, clearErrors }) => {
|
|
182
|
+
fieldErrorSink.current = setFieldError;
|
|
183
|
+
return (_jsxs(Stack, { gap: "3", children: [method === "custom" ? (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "shippingCarrier", label: "Carrier", required: true, value: carrier, onChange: setCarrier }), _jsx(FieldInput, { name: "trackingNumber", label: "Tracking number", required: true, value: tracking, onChange: setTracking }), _jsx(FieldInput, { name: "trackingUrl", label: "Tracking URL", required: true, type: "url", value: trackingUrl, onChange: setTrackingUrl })] })) : (_jsxs(_Fragment, { children: [pickupsFailed ? (_jsx(Text, { size: "xs", color: "error", role: "alert", children: "Could not load your pickup addresses. Reopen this panel to try again." })) : pickups.length === 0 ? (_jsx(Text, { size: "xs", color: "muted", children: "Loading your pickup addresses\u2026" })) : (_jsx(FieldSelect, { name: "pickupAddressId", label: "Collect from", required: true, value: pickupId, onChange: setPickupId, options: pickups.map((p) => ({
|
|
184
|
+
value: p.id,
|
|
185
|
+
label: [p.label, p.city, p.postalCode].filter(Boolean).join(" · ") || p.id,
|
|
186
|
+
})) })), _jsx(FieldSelect, { name: "box", label: "Parcel size", required: true, value: boxId, onChange: setBoxId, options: BOX_PRESETS.map((b) => ({ value: b.id, label: b.label })) }), _jsx(Text, { size: "xs", color: "muted", children: "The courier is booked on the platform's account and the tracking id comes back from them \u2014 you do not enter one." })] })), error && (_jsx(Div, { textSize: "xs", className: "border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", role: "alert", children: error })), _jsx(Button, { type: "submit", action: method === "platform"
|
|
187
|
+
? ACTIONS.STORE["dispatch-platform"]
|
|
188
|
+
: ACTIONS.STORE["dispatch-custom"], isLoading: shipMutation.isPending, onClick: () => void submit(setFieldError, clearErrors) })] }));
|
|
189
|
+
} })] }));
|
|
190
|
+
}
|
|
@@ -5,6 +5,7 @@ import { OrderAddonBadges } from "../../orders/components/OrderAddonBadges.js";
|
|
|
5
5
|
import { OrderProvenanceCard } from "../../orders/components/OrderProvenanceCard.js";
|
|
6
6
|
import { groupOrderItemsByLine } from "../../orders/utils/bundle-grouping.js";
|
|
7
7
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
8
|
+
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
8
9
|
// The defining module, not a barrel (Root Cause #18). Maps a server error CODE
|
|
9
10
|
// to user-facing copy; it can never return server text, which is the point.
|
|
10
11
|
import { toUserMessage } from "../../../errors/error-display-map.js";
|
|
@@ -27,6 +28,7 @@ import { SectionForm, useSectionFormNav, buildSectionsFromSchema, visibleValues,
|
|
|
27
28
|
import { useFormShellState, FormShellContext } from "../../../ui/forms/index.js";
|
|
28
29
|
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues.js";
|
|
29
30
|
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary.js";
|
|
31
|
+
import { DispatchForm } from "./DispatchForm.js";
|
|
30
32
|
import { PhysicalLocationModal } from "./PhysicalLocationModal.js";
|
|
31
33
|
import { ROUTES } from "../../../constants/index.js";
|
|
32
34
|
import { toRecordArray, toCurrency, toStringValue } from "../hooks/useSellerListingData.js";
|
|
@@ -37,6 +39,13 @@ import { MediaImage } from "../../media/MediaImage.js";
|
|
|
37
39
|
const __O = {
|
|
38
40
|
yAuto: "overflow-y-auto",
|
|
39
41
|
};
|
|
42
|
+
/*
|
|
43
|
+
* The hairline that separates each panel section inside the order drawer.
|
|
44
|
+
* Named because the drawer gained its fourth section with D55's balance
|
|
45
|
+
* collector, and four copies of one border declaration is four places a
|
|
46
|
+
* future token rename has to find.
|
|
47
|
+
*/
|
|
48
|
+
const SECTION_DIVIDER = "border-t border-[var(--appkit-color-border)]";
|
|
40
49
|
const DEFAULT_SORT = "-createdAt";
|
|
41
50
|
const SORT_OPTIONS = [
|
|
42
51
|
{ value: sortBy("createdAt", "DESC"), label: "Newest" },
|
|
@@ -96,6 +105,69 @@ const EMI_INSTALLMENT_BADGE_VARIANT = {
|
|
|
96
105
|
pending: "warning",
|
|
97
106
|
overdue: "danger",
|
|
98
107
|
};
|
|
108
|
+
/**
|
|
109
|
+
* D55 — the seller's half of the pre-order balance.
|
|
110
|
+
*
|
|
111
|
+
* ## 🛑 Opening is an EVENT, and the control has to say so
|
|
112
|
+
*
|
|
113
|
+
* The balance has existed on the order since checkout. What the seller is
|
|
114
|
+
* declaring here is that the GOODS HAVE ARRIVED — a pre-order's premise is
|
|
115
|
+
* that the item does not exist yet, so asking for the balance before then is
|
|
116
|
+
* asking a buyer to fund inventory. The button's label and its blurb both
|
|
117
|
+
* name that, rather than saying "request payment", which would read as a
|
|
118
|
+
* nudge the seller could send at any time.
|
|
119
|
+
*
|
|
120
|
+
* ## Why no Cancel and no confirm dialog
|
|
121
|
+
*
|
|
122
|
+
* Opening takes money NOWHERE and reverses by simply not chasing it — the
|
|
123
|
+
* worst case is a buyer told a week early. A confirmation step on a
|
|
124
|
+
* non-destructive, idempotent action is the dialog people learn to click
|
|
125
|
+
* through, which is what makes the ones on real deletions worthless.
|
|
126
|
+
*
|
|
127
|
+
* ## The states are the seller's, not the buyer's
|
|
128
|
+
*
|
|
129
|
+
* Once opened the seller cannot act again, so every later state is read-only
|
|
130
|
+
* here: "with the buyer", "proof submitted, we are checking it", "paid". The
|
|
131
|
+
* verification is staff-only and deliberately NOT offered — a seller
|
|
132
|
+
* approving payment to themselves is not a review.
|
|
133
|
+
*/
|
|
134
|
+
function SellerBalanceBlock({ orderId, apiBase, balance, balanceDue, currency, onOpened, }) {
|
|
135
|
+
const [opening, setOpening] = React.useState(false);
|
|
136
|
+
const [error, setError] = React.useState(null);
|
|
137
|
+
if (balance) {
|
|
138
|
+
const label = balance.status === "paid"
|
|
139
|
+
? "Balance paid"
|
|
140
|
+
: balance.status === "submitted"
|
|
141
|
+
? "Balance proof under review"
|
|
142
|
+
: balance.status === "rejected"
|
|
143
|
+
? "Balance proof rejected — the buyer has been asked to resend"
|
|
144
|
+
: "Balance open — awaiting the buyer";
|
|
145
|
+
return (_jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Pre-order balance" }), _jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [formatCurrency(balance.amount, currency), " \u2014 ", label, balance.transactionId ? ` · Ref ${balance.transactionId}` : ""] })] }));
|
|
146
|
+
}
|
|
147
|
+
const handleOpen = async () => {
|
|
148
|
+
setOpening(true);
|
|
149
|
+
setError(null);
|
|
150
|
+
try {
|
|
151
|
+
const res = await fetch(`${apiBase}/${orderId}/open-balance`, {
|
|
152
|
+
method: "POST",
|
|
153
|
+
credentials: "include",
|
|
154
|
+
});
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
setError("Could not open the balance. Try again, or contact support if it persists.");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
onOpened();
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
void normalizeError(err);
|
|
163
|
+
setError("Could not reach the server. Check your connection and try again.");
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
setOpening(false);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
return (_jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Pre-order balance" }), _jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [formatCurrency(balanceDue, currency), " is still owed on this order. Open it once the stock is physically with you \u2014 the buyer is notified and gets a week to pay, and the parcel ships when it clears."] }), _jsx(Row, { className: "mt-2", gap: "3", children: _jsx(Button, { variant: "primary", size: "sm", disabled: opening, onClick: handleOpen, children: opening ? "Opening…" : "Stock has arrived — open balance" }) }), error && (_jsx(Div, { textSize: "xs", className: "mt-2 border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: error }))] }));
|
|
170
|
+
}
|
|
99
171
|
/**
|
|
100
172
|
* Fetches + renders one order's full detail content (items, address,
|
|
101
173
|
* payment, EMI, status/tracking update form). Shared by the seller orders
|
|
@@ -299,11 +371,31 @@ export function SellerOrderDetailPanel({ orderId, apiBase, onClose, }) {
|
|
|
299
371
|
const addrLine = typeof rawAddr === "string" && rawAddr.trim()
|
|
300
372
|
? rawAddr.trim()
|
|
301
373
|
: [addr.addressLine1, addr.city, addr.state, addr.pincode].filter(Boolean).join(", ");
|
|
302
|
-
return (_jsxs(_Fragment, { children: [loading && (_jsx(Row, { align: "center", justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })), fetchError && (_jsx(Div, { textSize: "sm", className: "mx-4 mt-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "lg", children: fetchError })), order && !loading && (_jsx(Stack, { gap: "none", children: _jsxs(Stack, { className: `flex-1 ${__O.yAuto}`, gap: "5", padding: "md", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsx(Badge, { variant: STATUS_BADGE_VARIANT[order.status ?? ""] ?? "default", children: order.status ?? "Unknown" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: toRelativeDate(order.createdAt) })] }), _jsx(OrderProvenanceCard, { sourceContext: order.sourceContext, currency: order.currency }), (order.items ?? []).length > 0 && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text)] mb-2", weight: "semibold", children: "Items" }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: groupOrderItemsByLine(order.items ?? []).map((group, gi) => group.kind === "single" ? (sellerOrderItemRow(group.item, `s-${group.index}`)) : (_jsxs(Div, { children: [_jsxs(Row, { paddingY: "y-xs", padding: "x-sm", align: "center", gap: "sm", surface: "muted", children: [_jsx(Text, { size: "xs", weight: "semibold", children: group.groupTitle ?? "Grouped purchase" }), _jsx(Text, { size: "xs", className: "text-[var(--appkit-color-text-muted)]", children: `${group.memberCount} ${group.memberCount === 1 ? "item" : "items"} · one line` })] }), _jsx(Div, {
|
|
374
|
+
return (_jsxs(_Fragment, { children: [loading && (_jsx(Row, { align: "center", justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })), fetchError && (_jsx(Div, { textSize: "sm", className: "mx-4 mt-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "lg", children: fetchError })), order && !loading && (_jsx(Stack, { gap: "none", children: _jsxs(Stack, { className: `flex-1 ${__O.yAuto}`, gap: "5", padding: "md", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsx(Badge, { variant: STATUS_BADGE_VARIANT[order.status ?? ""] ?? "default", children: order.status ?? "Unknown" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: toRelativeDate(order.createdAt) })] }), _jsx(OrderProvenanceCard, { sourceContext: order.sourceContext, currency: order.currency }), (order.items ?? []).length > 0 && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text)] mb-2", weight: "semibold", children: "Items" }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: groupOrderItemsByLine(order.items ?? []).map((group, gi) => group.kind === "single" ? (sellerOrderItemRow(group.item, `s-${group.index}`)) : (_jsxs(Div, { children: [_jsxs(Row, { paddingY: "y-xs", padding: "x-sm", align: "center", gap: "sm", surface: "muted", children: [_jsx(Text, { size: "xs", weight: "semibold", children: group.groupTitle ?? "Grouped purchase" }), _jsx(Text, { size: "xs", className: "text-[var(--appkit-color-text-muted)]", children: `${group.memberCount} ${group.memberCount === 1 ? "item" : "items"} · one line` })] }), _jsx(Div, { paddingX: "l-md", children: group.items.map(({ item, index }) => sellerOrderItemRow(item, `m-${index}`)) })] }, `g-${gi}`))) })] })), _jsx(OrderAddonBadges, { order: order, variant: "detail" }), order.giftWrapAddon && (_jsxs(Div, { className: "border border-[var(--appkit-color-primary-200)] dark:border-[var(--appkit-color-primary-800)]", surface: "subtle", padding: "inline", rounded: "lg", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "\uD83C\uDF81 Gift wrap requested" }), order.giftWrapMessage && (_jsxs(Text, { size: "sm", className: "mt-1 text-[var(--appkit-color-text-muted)]", children: ["\u201C", order.giftWrapMessage, "\u201D"] }))] })), _jsxs(Row, { surface: "muted", padding: "inline", align: "center", justify: "between", rounded: "lg", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Total" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-primary)]", weight: "bold", children: toCurrency(order.totalAmount ?? order.totalPrice ?? 0) })] }), addrLine && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "mb-1", weight: "semibold", children: "Shipping address" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [String(addr.fullName ?? ""), addrLine].filter(Boolean).join(" · ") })] })), order.paymentMethod && (_jsxs(Div, { children: [_jsxs(Row, { align: "center", justify: "between", className: "mb-1", gap: "3", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Payment" }), isManualPaymentMethod(order.paymentMethod) && (_jsx(Badge, { variant: sellerPaymentBadge(order).variant, children: sellerPaymentBadge(order).label }))] }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", transform: "capitalize", children: order.paymentMethod }), isManualPaymentMethod(order.paymentMethod) && order.paymentTransactionId && (_jsxs(Text, { size: "xs", color: "muted", className: "mt-1", children: ["UTR: ", order.paymentTransactionId] }))] })), order.emiEnabled && (_jsxs(Div, { className: SECTION_DIVIDER, padding: "t-md", children: [_jsxs(Row, { align: "center", justify: "between", className: "mb-2", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: ["EMI plan ", order.emiTenureMonths ? `· ${order.emiTenureMonths} months` : ""] }), _jsx(Badge, { variant: order.emiComplete ? "success" : "warning", children: order.emiComplete ? "Fully paid" : "In progress" })] }), _jsxs(Row, { align: "center", justify: "between", className: "mb-2", children: [_jsx(Text, { size: "xs", color: "muted", children: "Token collected" }), _jsx(Text, { size: "xs", weight: "medium", children: toCurrency(order.emiTokenAmount ?? 0) })] }), _jsxs(Row, { align: "center", justify: "between", className: "mb-3", children: [_jsx(Text, { size: "xs", color: "muted", children: "Remaining balance" }), _jsx(Text, { size: "xs", weight: "medium", children: toCurrency(order.emiRemainingBalance ?? 0) })] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: (order.emiInstallments ?? []).map((inst) => (_jsxs(Row, { paddingY: "y-xs-tall", padding: "x-sm", align: "center", justify: "between", gap: "3", children: [_jsxs(Div, { className: "min-w-0", children: [_jsxs(Text, { size: "sm", weight: "medium", children: ["Installment ", inst.index, " \u00B7 ", toCurrency(inst.amount)] }), _jsx(Text, { size: "xs", color: "muted", children: inst.status === "paid" && inst.paidAt
|
|
303
375
|
? `Paid ${toRelativeDate(inst.paidAt)}`
|
|
304
376
|
: inst.dueDate
|
|
305
377
|
? `Due ${toRelativeDate(inst.dueDate)}`
|
|
306
|
-
: "" })] }), _jsxs(Row, { align: "center", gap: "sm", className: "shrink-0", children: [_jsx(Badge, { variant: EMI_INSTALLMENT_BADGE_VARIANT[inst.status] ?? "default", children: inst.status }), inst.status !== "paid" && (_jsx(Button, { action: ACTIONS.STORE["mark-installment-paid"], size: "sm", variant: "outline", isLoading: markingPaidIndex === inst.index, disabled: markingPaidIndex !== null, onClick: () => handleMarkInstallmentPaid(inst.index), children: "Mark paid" }))] })] }, inst.index))) }), emiError && (_jsx(Div, { textSize: "xs", className: "mt-2 border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: emiError }))] })),
|
|
378
|
+
: "" })] }), _jsxs(Row, { align: "center", gap: "sm", className: "shrink-0", children: [_jsx(Badge, { variant: EMI_INSTALLMENT_BADGE_VARIANT[inst.status] ?? "default", children: inst.status }), inst.status !== "paid" && (_jsx(Button, { action: ACTIONS.STORE["mark-installment-paid"], size: "sm", variant: "outline", isLoading: markingPaidIndex === inst.index, disabled: markingPaidIndex !== null, onClick: () => handleMarkInstallmentPaid(inst.index), children: "Mark paid" }))] })] }, inst.index))) }), emiError && (_jsx(Div, { textSize: "xs", className: "mt-2 border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: emiError }))] })), order.sourceContext?.path === "pre-order" &&
|
|
379
|
+
order.sourceContext.balanceDue > 0 && (_jsx(Stack, { className: SECTION_DIVIDER, padding: "t-md", gap: "3", children: _jsx(SellerBalanceBlock, { orderId: orderId, apiBase: apiBase, balance: order.balance, balanceDue: order.sourceContext.balanceDue, currency: order.currency, onOpened: () => {
|
|
380
|
+
showToast("Balance opened — the buyer has been notified", "success");
|
|
381
|
+
void fetch(`${apiBase}/${orderId}`)
|
|
382
|
+
.then((r) => r.json())
|
|
383
|
+
.then((json) => setOrder((json?.data ?? json)))
|
|
384
|
+
.catch((err) => void normalizeError(err));
|
|
385
|
+
} }) })), _jsx(Stack, { className: SECTION_DIVIDER, padding: "t-md", gap: "3", children: _jsx(DispatchForm, { orderId: orderId, trackingNumber: order.trackingNumber, platformBooked: order.platformBookedShipment, onDispatched: () => {
|
|
386
|
+
showToast("Dispatch recorded", "success");
|
|
387
|
+
/*
|
|
388
|
+
* Re-read rather than patching local state: the platform
|
|
389
|
+
* branch's tracking id is produced by the CARRIER, so there
|
|
390
|
+
* is no value here to optimistically write. On that branch
|
|
391
|
+
* the job is still running, so this first re-read correctly
|
|
392
|
+
* shows no tracking number yet.
|
|
393
|
+
*/
|
|
394
|
+
void fetch(`${apiBase}/${orderId}`)
|
|
395
|
+
.then((r) => r.json())
|
|
396
|
+
.then((json) => setOrder((json?.data ?? json)))
|
|
397
|
+
.catch((err) => void normalizeError(err));
|
|
398
|
+
} }) }), _jsxs(Stack, { className: SECTION_DIVIDER, padding: "t-md", gap: "3", children: [_jsxs(FormShellContext.Provider, { value: form.shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: draft, onChange: (partial) => setDraft((d) => ({ ...d, ...partial })), onSubmit: () => {
|
|
307
399
|
// SectionForm scrolls to the first erroring section and then
|
|
308
400
|
// submits regardless, so the guard lives here.
|
|
309
401
|
form.clearErrors();
|
|
@@ -5,6 +5,7 @@ export { SellerListingTypeView, SELLER_LISTING_TYPE_SPECS } from "./SellerListin
|
|
|
5
5
|
export type { SellerListingTypeViewProps, SellerListingTypeSpec, SellerListingTypeKey, SellerListingRow } from "./SellerListingTypeView";
|
|
6
6
|
export type { SellerProductsViewProps } from "./SellerProductsView";
|
|
7
7
|
export { PrintCenterView } from "./PrintCenterView";
|
|
8
|
+
export { DispatchForm } from "./DispatchForm";
|
|
8
9
|
export { SellerOrdersView, SellerOrderDetailPanel } from "./SellerOrdersView";
|
|
9
10
|
export type { SellerOrdersViewProps } from "./SellerOrdersView";
|
|
10
11
|
export { SellerOffersView } from "./SellerOffersView";
|
|
@@ -2,6 +2,7 @@ export { SellerDashboardView } from "./SellerDashboardView.js";
|
|
|
2
2
|
export { SellerProductsView } from "./SellerProductsView.js";
|
|
3
3
|
export { SellerListingTypeView, SELLER_LISTING_TYPE_SPECS } from "./SellerListingTypeView.js";
|
|
4
4
|
export { PrintCenterView } from "./PrintCenterView.js";
|
|
5
|
+
export { DispatchForm } from "./DispatchForm.js";
|
|
5
6
|
export { SellerOrdersView, SellerOrderDetailPanel } from "./SellerOrdersView.js";
|
|
6
7
|
export { SellerOffersView } from "./SellerOffersView.js";
|
|
7
8
|
export { SellerCouponsView } from "./SellerCouponsView.js";
|
|
@@ -247,9 +247,10 @@ class OfferRepository extends BaseRepository {
|
|
|
247
247
|
trigger: ctx?.trigger ?? "offerRepository.updateStatus",
|
|
248
248
|
reason: ctx?.reason,
|
|
249
249
|
note: ctx?.note,
|
|
250
|
-
// buyerName/buyerEmail live on this document. `encryptPiiFields`
|
|
251
|
-
//
|
|
252
|
-
//
|
|
250
|
+
// buyerName/buyerEmail live on this document. `encryptPiiFields` covers
|
|
251
|
+
// only NAMED paths, and nothing declares them under
|
|
252
|
+
// `statusHistory[].changes` — so without this scrub they persist in
|
|
253
|
+
// PLAINTEXT inside the history.
|
|
253
254
|
piiFields: OFFER_PII_FIELDS,
|
|
254
255
|
historyField: OFFER_FIELDS.STATUS_HISTORY,
|
|
255
256
|
truncatedField: OFFER_FIELDS.STATUS_HISTORY_TRUNCATED,
|
|
@@ -5,6 +5,7 @@ import { type GenerateOfferIdInput } from "../../../utils/id-generators";
|
|
|
5
5
|
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
6
6
|
import type { StatusChangeEntry } from "../../../_internal/shared/history/index";
|
|
7
7
|
import type { Encrypted, Plaintext } from "../../../security/encrypted";
|
|
8
|
+
import type { OfferCoordination } from "../../../_internal/shared/features/offers/coordination";
|
|
8
9
|
export type OfferStatus = "pending" | "accepted" | "declined" | "countered" | "expired" | "withdrawn" | "paid";
|
|
9
10
|
/** Runtime-accessible offer status values — use instead of bare string literals. */
|
|
10
11
|
export declare const OfferStatusValues: {
|
|
@@ -50,6 +51,25 @@ export interface OfferDocument extends BaseDocument {
|
|
|
50
51
|
acceptedAt?: Date;
|
|
51
52
|
checkoutDeadline?: Date;
|
|
52
53
|
respondedAt?: Date;
|
|
54
|
+
/**
|
|
55
|
+
* Present when this offer is a COORDINATED purchase — several people
|
|
56
|
+
* chipping in on one listing.
|
|
57
|
+
*
|
|
58
|
+
* 🛑 A field on the offer, not a new document and not a new lifecycle.
|
|
59
|
+
* Acceptance produces an ordinary `offer-accepted` order; `unitPriceFor`
|
|
60
|
+
* already honours `lockedPrice`, so tax, payout base, refund ceiling and
|
|
61
|
+
* the invoice all follow with no new money term. See
|
|
62
|
+
* `_internal/shared/features/offers/coordination.ts` for the full list of
|
|
63
|
+
* things this deliberately does NOT add.
|
|
64
|
+
*
|
|
65
|
+
* 🛑 No member NAME is stored in here. `encryptPiiFields` CAN reach into an
|
|
66
|
+
* array (`coordination.members[].name`) — the claim that it cannot was
|
|
67
|
+
* wrong — but it covers only what is NAMED, and the near-miss `"name"`
|
|
68
|
+
* reads as covering it while storing plaintext. Members are uids; names
|
|
69
|
+
* resolve at render time, which removes the field rather than classifying
|
|
70
|
+
* it. See `coordination.ts`.
|
|
71
|
+
*/
|
|
72
|
+
coordination?: OfferCoordination;
|
|
53
73
|
/** Set by `markPaid` once the buyer's order exists — makes "paid" auditable. */
|
|
54
74
|
paidOrderId?: string;
|
|
55
75
|
paidAt?: Date;
|
|
@@ -93,8 +113,9 @@ export interface OfferDocument extends BaseDocument {
|
|
|
93
113
|
/**
|
|
94
114
|
* Fields whose changes are recorded in an offer's `statusHistory`.
|
|
95
115
|
*
|
|
96
|
-
* Excludes `buyerName`/`buyerEmail` (PII —
|
|
97
|
-
*
|
|
116
|
+
* Excludes `buyerName`/`buyerEmail` (PII — nothing declares them under
|
|
117
|
+
* `statusHistory[].changes`, and `encryptPiiFields` covers only NAMED paths,
|
|
118
|
+
* so they would persist in plaintext inside the history),
|
|
98
119
|
* `buyerNote`/`sellerNote` (they ride in the entry's `note`), and
|
|
99
120
|
* `updatedAt`/`respondedAt` (noise that would burn the FIFO cap).
|
|
100
121
|
*/
|
|
@@ -15,8 +15,9 @@ export const OfferStatusValues = {
|
|
|
15
15
|
/**
|
|
16
16
|
* Fields whose changes are recorded in an offer's `statusHistory`.
|
|
17
17
|
*
|
|
18
|
-
* Excludes `buyerName`/`buyerEmail` (PII —
|
|
19
|
-
*
|
|
18
|
+
* Excludes `buyerName`/`buyerEmail` (PII — nothing declares them under
|
|
19
|
+
* `statusHistory[].changes`, and `encryptPiiFields` covers only NAMED paths,
|
|
20
|
+
* so they would persist in plaintext inside the history),
|
|
20
21
|
* `buyerNote`/`sellerNote` (they ride in the entry's `note`), and
|
|
21
22
|
* `updatedAt`/`respondedAt` (noise that would burn the FIFO cap).
|
|
22
23
|
*/
|
|
@@ -72,13 +72,13 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
72
72
|
totalReviews: z.ZodNumber;
|
|
73
73
|
averageRating: z.ZodOptional<z.ZodNumber>;
|
|
74
74
|
}, "strip", z.ZodTypeAny, {
|
|
75
|
-
itemsSold: number;
|
|
76
75
|
totalProducts: number;
|
|
76
|
+
itemsSold: number;
|
|
77
77
|
totalReviews: number;
|
|
78
78
|
averageRating?: number | undefined;
|
|
79
79
|
}, {
|
|
80
|
-
itemsSold: number;
|
|
81
80
|
totalProducts: number;
|
|
81
|
+
itemsSold: number;
|
|
82
82
|
totalReviews: number;
|
|
83
83
|
averageRating?: number | undefined;
|
|
84
84
|
}>>;
|
|
@@ -96,8 +96,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
96
96
|
location?: string | undefined;
|
|
97
97
|
returnPolicy?: string | undefined;
|
|
98
98
|
stats?: {
|
|
99
|
-
itemsSold: number;
|
|
100
99
|
totalProducts: number;
|
|
100
|
+
itemsSold: number;
|
|
101
101
|
totalReviews: number;
|
|
102
102
|
averageRating?: number | undefined;
|
|
103
103
|
} | undefined;
|
|
@@ -128,8 +128,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
128
128
|
location?: string | undefined;
|
|
129
129
|
returnPolicy?: string | undefined;
|
|
130
130
|
stats?: {
|
|
131
|
-
itemsSold: number;
|
|
132
131
|
totalProducts: number;
|
|
132
|
+
itemsSold: number;
|
|
133
133
|
totalReviews: number;
|
|
134
134
|
averageRating?: number | undefined;
|
|
135
135
|
} | undefined;
|