@mohasinac/appkit 4.70.0 → 4.71.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/PROVIDERS.md +190 -0
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/lottery/LotteryAdminSlotView.js +2 -1
- package/dist/_internal/server/features/checkout/actions.js +43 -8
- package/dist/_internal/server/features/orders/actions.js +2 -1
- package/dist/_internal/server/features/orders/adapters.js +18 -0
- package/dist/_internal/server/features/orders/balance.js +2 -1
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +0 -89
- package/dist/_internal/server/features/orders/cancellation-refund.js +3 -2
- package/dist/_internal/server/features/payouts/actions.js +2 -1
- package/dist/_internal/server/features/points/award.js +3 -2
- package/dist/_internal/server/features/protection-claims/actions.js +2 -1
- package/dist/_internal/server/features/refunds/actions.js +3 -2
- package/dist/_internal/server/features/shipping/courier-block.js +5 -2
- package/dist/_internal/server/features/shipping/reconcile.js +3 -2
- package/dist/_internal/server/features/shipping/reimbursement.js +3 -2
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +12 -2
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +7 -3
- package/dist/_internal/server/jobs/core/emiInstallmentReminder.js +2 -1
- package/dist/_internal/server/jobs/core/obligationSweep.js +5 -4
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +2 -1
- package/dist/_internal/server/jobs/core/paymentReconcile.js +5 -4
- package/dist/_internal/shared/checkout/rules/auction.rule.js +25 -7
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +26 -8
- package/dist/_internal/shared/config/schema.d.ts +15 -0
- package/dist/_internal/shared/content/published-values.d.ts +2 -2
- package/dist/_internal/shared/content/published-values.js +31 -12
- package/dist/_internal/shared/features/auctions/errors.js +2 -1
- package/dist/_internal/shared/features/orders/refund-copy.js +3 -2
- package/dist/_internal/shared/features/promotions/errors.js +2 -1
- package/dist/constants/field-names.d.ts +23 -0
- package/dist/constants/field-names.js +23 -0
- package/dist/constants/storage-keys.d.ts +90 -0
- package/dist/constants/storage-keys.js +119 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/payment.d.ts +42 -0
- package/dist/contracts/registry.d.ts +8 -0
- package/dist/contracts/tax.d.ts +118 -0
- package/dist/contracts/tax.js +3 -0
- package/dist/core/brand-resolver.d.ts +92 -0
- package/dist/core/brand-resolver.js +120 -0
- package/dist/core/brand-text.d.ts +15 -0
- package/dist/core/brand-text.js +73 -0
- package/dist/core/hooks/useNewsletterSubscribe.d.ts +30 -0
- package/dist/core/hooks/useNewsletterSubscribe.js +121 -0
- package/dist/features/about/components/DeveloperView.js +2 -1
- package/dist/features/account/components/BuyerAccountGuideView.js +2 -1
- package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -3
- package/dist/features/account/components/BuyerOrdersGuideView.js +6 -5
- package/dist/features/account/components/BuyerShoppingGuideView.js +3 -2
- package/dist/features/account/components/UserOffersPanel.js +2 -1
- package/dist/features/account/hooks/useCollapsedSections.js +5 -3
- package/dist/features/account/schemas/index.js +1 -1
- package/dist/features/admin/actions/notification-actions.js +9 -2
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +2 -1
- package/dist/features/admin/components/AdminBundlesView.js +2 -1
- package/dist/features/admin/components/AdminCatalogGuideView.js +2 -1
- package/dist/features/admin/components/AdminContentGuideView.js +2 -1
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -1
- package/dist/features/admin/components/AdminGatewayEventsView.js +2 -1
- package/dist/features/admin/components/AdminGuideHubView.js +2 -1
- package/dist/features/admin/components/AdminMoneyCasesView.js +2 -1
- package/dist/features/admin/components/AdminOrdersGuideView.js +2 -1
- package/dist/features/admin/components/AdminProductsView.js +3 -2
- package/dist/features/admin/components/AdminSectionsView.js +3 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +8 -7
- package/dist/features/admin/components/AdminStoresGuideView.js +2 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +2 -1
- package/dist/features/admin/components/AdminTeamGuideView.js +2 -1
- package/dist/features/admin/components/AdminTrustGuideView.js +3 -2
- package/dist/features/admin/components/AdminUsersGuideView.js +2 -1
- package/dist/features/admin/components/sections/adminSectionsTypes.js +1 -1
- package/dist/features/auth/actions/user-slug.js +21 -2
- package/dist/features/auth/hooks/useAuth.js +11 -4
- package/dist/features/cart/utils/pending-ops.js +16 -3
- package/dist/features/catalogue/utils/product-from-item.d.ts +18 -2
- package/dist/features/catalogue/utils/product-from-item.js +23 -4
- package/dist/features/categories/components/BundleItemsPicker.js +2 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -1
- package/dist/features/categories/components/ContextualTrail.d.ts +9 -2
- package/dist/features/categories/components/ContextualTrail.js +11 -3
- package/dist/features/categories/components/LastCategoryBeacon.js +2 -2
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +9 -1
- package/dist/features/contact/email.js +11 -12
- package/dist/features/email/notification-templates.js +4 -5
- package/dist/features/email/render-static-markup.d.ts +34 -0
- package/dist/features/email/render-static-markup.js +4 -0
- package/dist/features/events/utils/event-charts.js +2 -1
- package/dist/features/homepage/components/AnnouncementBar.js +5 -3
- package/dist/features/homepage/components/WelcomeSection.d.ts +3 -1
- package/dist/features/homepage/components/WelcomeSection.js +2 -2
- package/dist/features/homepage/lib/section-renderer.d.ts +8 -0
- package/dist/features/homepage/lib/section-renderer.js +1 -1
- package/dist/features/layout/AppLayoutShell.d.ts +3 -1
- package/dist/features/layout/AppLayoutShell.js +2 -2
- package/dist/features/layout/TitleBarLayout.d.ts +8 -1
- package/dist/features/layout/TitleBarLayout.js +2 -2
- package/dist/features/media/MediaVideo.js +2 -1
- package/dist/features/orders/actions/refund-actions.js +3 -2
- package/dist/features/orders/components/RefundRequestView.js +15 -2
- package/dist/features/orders/repository/orders.repository.js +2 -1
- package/dist/features/orders/types/index.d.ts +14 -0
- package/dist/features/orders/utils/payment-proof-message.js +5 -2
- package/dist/features/price-index/components/PriceIndexToolView.js +6 -5
- package/dist/features/pricing/components/AdminPricingView.js +2 -1
- package/dist/features/print/components/labels.js +2 -1
- package/dist/features/products/components/CityCollectiblesView.js +2 -1
- package/dist/features/products/components/MakeOfferButton.js +2 -1
- package/dist/features/products/components/PrizeDrawCollage.js +3 -2
- package/dist/features/products/components/PrizeRevealModal.js +2 -1
- package/dist/features/products/repository/products.repository.js +2 -1
- package/dist/features/promotions/components/CouponCard.js +3 -2
- package/dist/features/promotions/components/CouponHelpDetails.js +2 -1
- package/dist/features/promotions/constants/coupon-help.d.ts +1 -1
- package/dist/features/promotions/constants/coupon-help.js +8 -1
- package/dist/features/scams/components/ScamAwarenessModal.js +2 -1
- package/dist/features/scams/components/ScamProfileView.js +5 -3
- package/dist/features/scams/components/ScamRegistryView.js +2 -1
- package/dist/features/scams/constants/scam-types.d.ts +13 -1
- package/dist/features/scams/constants/scam-types.js +67 -23
- package/dist/features/scams/schemas/firestore.js +1 -1
- package/dist/features/search/components/Search.js +5 -1
- package/dist/features/seller/actions/offer-actions.js +47 -9
- package/dist/features/seller/components/DispatchForm.js +3 -2
- package/dist/features/seller/components/PrintCenterView.js +4 -2
- package/dist/features/seller/components/SellerBundlesView.js +2 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +3 -2
- package/dist/features/seller/components/SellerProductShell.js +3 -2
- package/dist/features/seller/components/SellerProductsView.js +4 -3
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +30 -4
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +46 -7
- package/dist/features/store-extensions/schemas/firestore.d.ts +22 -3
- package/dist/features/store-extensions/schemas/firestore.js +20 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +7 -6
- package/dist/features/stores/components/StoreFinanceGuideView.js +6 -5
- package/dist/features/stores/components/StoreGuideHubView.js +2 -1
- package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
- package/dist/features/stores/components/StoreOrdersGuideView.js +5 -4
- package/dist/features/stores/components/StoreSettingsGuideView.js +2 -1
- package/dist/features/support/schemas/index.d.ts +2 -2
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +1 -1
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +3 -2
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +5 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/providers/payment-manual/index.d.ts +11 -0
- package/dist/providers/payment-manual/index.js +13 -0
- package/dist/providers/payment-phonepe/index.d.ts +23 -7
- package/dist/providers/payment-phonepe/index.js +15 -0
- package/dist/providers/payment-razorpay/index.d.ts +13 -0
- package/dist/providers/payment-razorpay/index.js +21 -1
- package/dist/providers/tax-india-gst/index.d.ts +45 -0
- package/dist/providers/tax-india-gst/index.js +78 -0
- package/dist/react/hooks/useScrollToTop.d.ts +5 -0
- package/dist/react/hooks/useScrollToTop.js +70 -0
- package/dist/schemas/registry.d.ts +2 -2
- package/dist/seo/json-ld.js +25 -4
- package/dist/ui/components/SiteLogo.d.ts +20 -2
- package/dist/ui/components/SiteLogo.js +28 -6
- package/dist/ui/components/SiteMark.d.ts +1 -1
- package/dist/ui/components/SiteMark.js +21 -6
- package/dist/utils/date.formatter.d.ts +20 -0
- package/dist/utils/date.formatter.js +25 -0
- package/dist/utils/number.formatter.d.ts +47 -0
- package/dist/utils/number.formatter.js +79 -1
- package/dist/validation/phone.validator.d.ts +0 -1
- package/dist/validation/phone.validator.js +17 -3
- package/package.json +17 -3
|
@@ -40,6 +40,19 @@ export class ManualPaymentProvider extends IPaymentProvider {
|
|
|
40
40
|
verifyWebhook(_payload, _signature) {
|
|
41
41
|
return false;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Manual payment has no gateway callback to verify, so there is nothing that
|
|
45
|
+
* could make this true.
|
|
46
|
+
*
|
|
47
|
+
* 🛑 `false`, never `true`. This is the check that decides whether a payment
|
|
48
|
+
* is genuine; a provider that cannot verify must REJECT, not wave through.
|
|
49
|
+
* Returning `true` here would mean a forged gateway callback is accepted on
|
|
50
|
+
* any install where the gateway flag is off — which is exactly the install
|
|
51
|
+
* where nobody is watching for one.
|
|
52
|
+
*/
|
|
53
|
+
verifyCheckoutSignature(_payload, _secret) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
43
56
|
/**
|
|
44
57
|
* Manual payments are considered captured the moment the buyer's
|
|
45
58
|
* reference/proof is recorded — there is no separate auth+capture step.
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
*/
|
|
39
39
|
import type { JsonValue } from "@mohasinac/appkit";
|
|
40
40
|
import { IPaymentProvider } from "../../contracts";
|
|
41
|
-
import type { PaymentOrder, PaymentCapture, Refund, Dispute, GatewayPaymentRecord } from "../../contracts";
|
|
41
|
+
import type { CheckoutIntentMetadata, PaymentOrder, PaymentCapture, Refund, Dispute, GatewayPaymentRecord } from "../../contracts";
|
|
42
42
|
/**
|
|
43
43
|
* PhonePe's `metaInfo` (udf1-udf5, ≤256 chars each) is the only channel that
|
|
44
44
|
* survives from `createOrder()` through to BOTH the Order Status API AND the
|
|
@@ -48,12 +48,15 @@ import type { PaymentOrder, PaymentCapture, Refund, Dispute, GatewayPaymentRecor
|
|
|
48
48
|
* the tab mid-payment. See `confirmAndPlacePhonePeOrderAction` in
|
|
49
49
|
* `_internal/server/features/checkout/actions.ts`.
|
|
50
50
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
/**
|
|
52
|
+
* @deprecated Use `CheckoutIntentMetadata` from `contracts/payment`.
|
|
53
|
+
*
|
|
54
|
+
* Kept as an alias so existing imports keep compiling. Every field is ours —
|
|
55
|
+
* the only PhonePe-specific thing was the name, and it forced generic checkout
|
|
56
|
+
* code to import this provider for a TYPE (A4). PhonePe carries it in
|
|
57
|
+
* `udf1..udf4`; that is a packing detail, not a definition.
|
|
58
|
+
*/
|
|
59
|
+
export type PhonePeCheckoutIntent = CheckoutIntentMetadata;
|
|
57
60
|
export interface PhonePeConfig {
|
|
58
61
|
clientId: string;
|
|
59
62
|
clientSecret: string;
|
|
@@ -97,6 +100,19 @@ export declare class PhonePeProvider extends IPaymentProvider {
|
|
|
97
100
|
* the existing webhook route already does.
|
|
98
101
|
*/
|
|
99
102
|
verifyWebhook(payload: string, signature: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* PhonePe has no buyer-present SIGNED callback of this shape.
|
|
105
|
+
*
|
|
106
|
+
* Its redirect carries no signature — confirmation comes from the Order
|
|
107
|
+
* Status API or the webhook (`verifyWebhook` above, which does the real
|
|
108
|
+
* `validateCallback`). So there is nothing here to verify, and the honest
|
|
109
|
+
* answer is rejection.
|
|
110
|
+
*
|
|
111
|
+
* 🛑 `false`, never `true`. "No signature to check" and "the signature is
|
|
112
|
+
* good" must never collapse into the same return value on the call that
|
|
113
|
+
* decides whether a payment is genuine.
|
|
114
|
+
*/
|
|
115
|
+
verifyCheckoutSignature(_payload: Record<string, string>, _secret: string): boolean;
|
|
100
116
|
/** Wraps the Order Status API. `orderId` here is our `merchantOrderId`. */
|
|
101
117
|
capturePayment(orderId: string): Promise<PaymentCapture>;
|
|
102
118
|
/**
|
|
@@ -158,6 +158,21 @@ export class PhonePeProvider extends IPaymentProvider {
|
|
|
158
158
|
return false;
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* PhonePe has no buyer-present SIGNED callback of this shape.
|
|
163
|
+
*
|
|
164
|
+
* Its redirect carries no signature — confirmation comes from the Order
|
|
165
|
+
* Status API or the webhook (`verifyWebhook` above, which does the real
|
|
166
|
+
* `validateCallback`). So there is nothing here to verify, and the honest
|
|
167
|
+
* answer is rejection.
|
|
168
|
+
*
|
|
169
|
+
* 🛑 `false`, never `true`. "No signature to check" and "the signature is
|
|
170
|
+
* good" must never collapse into the same return value on the call that
|
|
171
|
+
* decides whether a payment is genuine.
|
|
172
|
+
*/
|
|
173
|
+
verifyCheckoutSignature(_payload, _secret) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
161
176
|
/** Wraps the Order Status API. `orderId` here is our `merchantOrderId`. */
|
|
162
177
|
async capturePayment(orderId) {
|
|
163
178
|
const status = await this.client.getOrderStatus(orderId, true);
|
|
@@ -89,6 +89,19 @@ export declare class RazorpayProvider extends IPaymentProvider {
|
|
|
89
89
|
* is not guaranteed to reproduce the bytes that were signed.
|
|
90
90
|
*/
|
|
91
91
|
verifyWebhook(payload: string, signature: string): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* The buyer-present callback from `checkout.js`.
|
|
94
|
+
*
|
|
95
|
+
* 🛑 Delegates to the SAME `verifyPaymentSignature` the checkout action
|
|
96
|
+
* called directly before A4b — identical HMAC, identical `hexEqual`
|
|
97
|
+
* comparison, identical `if (!keySecret) return false`. The dispatch moved
|
|
98
|
+
* behind the contract; the arithmetic did not move at all. That is what let
|
|
99
|
+
* this be proven equivalent rather than argued about.
|
|
100
|
+
*
|
|
101
|
+
* The three fields are checked before the cast: a callback missing any of
|
|
102
|
+
* them cannot be verified, and "cannot verify" is rejection.
|
|
103
|
+
*/
|
|
104
|
+
verifyCheckoutSignature(payload: Record<string, string>, secret: string): boolean;
|
|
92
105
|
/**
|
|
93
106
|
* Resolves a `pay_…` id for either id shape.
|
|
94
107
|
*
|
|
@@ -54,7 +54,9 @@ import Razorpay from "razorpay";
|
|
|
54
54
|
import { IPaymentProvider } from "../../contracts/index.js";
|
|
55
55
|
import { getDefaultCurrency } from "../../core/baseline-resolver.js";
|
|
56
56
|
import { paiseToRupees } from "../../core/money.js";
|
|
57
|
-
|
|
57
|
+
// `verifyPaymentSignature` is re-exported below, but a re-export does not bring
|
|
58
|
+
// it into THIS module's scope — `verifyCheckoutSignature` calls it directly.
|
|
59
|
+
import { verifyPaymentSignature, verifyWebhookSignature } from "./signature.js";
|
|
58
60
|
export { verifyPaymentSignature, verifyWebhookSignature, } from "./signature.js";
|
|
59
61
|
/** Razorpay's own order status vocabulary → the contract's. */
|
|
60
62
|
function mapOrderStatus(status) {
|
|
@@ -144,6 +146,24 @@ export class RazorpayProvider extends IPaymentProvider {
|
|
|
144
146
|
return false;
|
|
145
147
|
return verifyWebhookSignature(payload, signature, this.webhookSecret);
|
|
146
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* The buyer-present callback from `checkout.js`.
|
|
151
|
+
*
|
|
152
|
+
* 🛑 Delegates to the SAME `verifyPaymentSignature` the checkout action
|
|
153
|
+
* called directly before A4b — identical HMAC, identical `hexEqual`
|
|
154
|
+
* comparison, identical `if (!keySecret) return false`. The dispatch moved
|
|
155
|
+
* behind the contract; the arithmetic did not move at all. That is what let
|
|
156
|
+
* this be proven equivalent rather than argued about.
|
|
157
|
+
*
|
|
158
|
+
* The three fields are checked before the cast: a callback missing any of
|
|
159
|
+
* them cannot be verified, and "cannot verify" is rejection.
|
|
160
|
+
*/
|
|
161
|
+
verifyCheckoutSignature(payload, secret) {
|
|
162
|
+
const { razorpay_order_id, razorpay_payment_id, razorpay_signature } = payload;
|
|
163
|
+
if (!razorpay_order_id || !razorpay_payment_id || !razorpay_signature)
|
|
164
|
+
return false;
|
|
165
|
+
return verifyPaymentSignature({ razorpay_order_id, razorpay_payment_id, razorpay_signature }, secret);
|
|
166
|
+
}
|
|
147
167
|
/**
|
|
148
168
|
* Resolves a `pay_…` id for either id shape.
|
|
149
169
|
*
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* India GST — the default `ITaxProvider`.
|
|
3
|
+
*
|
|
4
|
+
* ## 🛑 This DELEGATES. Every figure still comes from `calculateGst` /
|
|
5
|
+
* `calculateGstInclusive`.
|
|
6
|
+
*
|
|
7
|
+
* Not a rewrite, on purpose. Those two functions split the intra-state half
|
|
8
|
+
* differently and the difference is load-bearing:
|
|
9
|
+
*
|
|
10
|
+
* calculateGst cgst = sgst = round(gst / 2); gst RE-DERIVED as half*2
|
|
11
|
+
* calculateGstInclusive cgst = round(gst / 2); sgst = round(gst - cgst)
|
|
12
|
+
*
|
|
13
|
+
* Measured on ₹66.67 at 18% inclusive: cgst 5.09, sgst 5.08 — the remainder
|
|
14
|
+
* lands in sgst so the parts still sum to 10.17. Reimplementing the maths here
|
|
15
|
+
* to make the two agree would move a paisa on figures GST is filed on. The
|
|
16
|
+
* seam's whole value is that it adds an abstraction WITHOUT touching arithmetic.
|
|
17
|
+
*
|
|
18
|
+
* Verified by `.build-memory/tax-roundtrip.mjs` — 397 breakdowns across every
|
|
19
|
+
* slab, both supply directions and the rounding edges, byte-identical.
|
|
20
|
+
*
|
|
21
|
+
* @tag domain:tax
|
|
22
|
+
* @tag layer:provider
|
|
23
|
+
* @tag access:isomorphic
|
|
24
|
+
*/
|
|
25
|
+
import { ITaxProvider, type TaxBreakdown, type TaxJurisdiction, type TaxableItem } from "../../contracts/tax";
|
|
26
|
+
export declare class IndiaGstTaxProvider extends ITaxProvider {
|
|
27
|
+
readonly id = "india-gst";
|
|
28
|
+
/**
|
|
29
|
+
* Intra-state when origin and destination states match.
|
|
30
|
+
*
|
|
31
|
+
* 🛑 Absent information is treated as INTRA-state, which preserves the
|
|
32
|
+
* existing behaviour exactly rather than improving on it. Every current
|
|
33
|
+
* caller passes an `intraState` boolean it computed itself, and the defaults
|
|
34
|
+
* across those call sites resolve to intra-state when a place of supply is
|
|
35
|
+
* unknown — changing that here would alter tax on orders with an incomplete
|
|
36
|
+
* delivery snapshot, which is precisely the class of row most likely to
|
|
37
|
+
* exist. A stricter rule is a product decision, not a side effect of adding
|
|
38
|
+
* an interface.
|
|
39
|
+
*/
|
|
40
|
+
isSameRegion(jurisdiction: TaxJurisdiction): boolean;
|
|
41
|
+
onExclusive(item: TaxableItem, jurisdiction: TaxJurisdiction, taxableAmount: number): TaxBreakdown;
|
|
42
|
+
onInclusive(item: TaxableItem, jurisdiction: TaxJurisdiction, gross: number): TaxBreakdown;
|
|
43
|
+
}
|
|
44
|
+
/** The default instance. A consumer in another jurisdiction registers its own. */
|
|
45
|
+
export declare const indiaGstTaxProvider: IndiaGstTaxProvider;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* India GST — the default `ITaxProvider`.
|
|
3
|
+
*
|
|
4
|
+
* ## 🛑 This DELEGATES. Every figure still comes from `calculateGst` /
|
|
5
|
+
* `calculateGstInclusive`.
|
|
6
|
+
*
|
|
7
|
+
* Not a rewrite, on purpose. Those two functions split the intra-state half
|
|
8
|
+
* differently and the difference is load-bearing:
|
|
9
|
+
*
|
|
10
|
+
* calculateGst cgst = sgst = round(gst / 2); gst RE-DERIVED as half*2
|
|
11
|
+
* calculateGstInclusive cgst = round(gst / 2); sgst = round(gst - cgst)
|
|
12
|
+
*
|
|
13
|
+
* Measured on ₹66.67 at 18% inclusive: cgst 5.09, sgst 5.08 — the remainder
|
|
14
|
+
* lands in sgst so the parts still sum to 10.17. Reimplementing the maths here
|
|
15
|
+
* to make the two agree would move a paisa on figures GST is filed on. The
|
|
16
|
+
* seam's whole value is that it adds an abstraction WITHOUT touching arithmetic.
|
|
17
|
+
*
|
|
18
|
+
* Verified by `.build-memory/tax-roundtrip.mjs` — 397 breakdowns across every
|
|
19
|
+
* slab, both supply directions and the rounding edges, byte-identical.
|
|
20
|
+
*
|
|
21
|
+
* @tag domain:tax
|
|
22
|
+
* @tag layer:provider
|
|
23
|
+
* @tag access:isomorphic
|
|
24
|
+
*/
|
|
25
|
+
import { ITaxProvider, } from "../../contracts/tax.js";
|
|
26
|
+
import { calculateGst, calculateGstInclusive, } from "../../_internal/shared/fees/calculator.js";
|
|
27
|
+
/** CGST and SGST are each HALF the headline rate; IGST carries all of it. */
|
|
28
|
+
function componentsFrom(breakdown, rate) {
|
|
29
|
+
const half = rate / 2;
|
|
30
|
+
if (breakdown.igst > 0 || (breakdown.cgst === 0 && breakdown.sgst === 0 && breakdown.gstAmount > 0)) {
|
|
31
|
+
return [{ code: "igst", label: "IGST", rate, amount: breakdown.igst }];
|
|
32
|
+
}
|
|
33
|
+
if (breakdown.gstAmount === 0)
|
|
34
|
+
return [];
|
|
35
|
+
return [
|
|
36
|
+
{ code: "cgst", label: "CGST", rate: half, amount: breakdown.cgst },
|
|
37
|
+
{ code: "sgst", label: "SGST", rate: half, amount: breakdown.sgst },
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
function toBreakdown(breakdown, rate) {
|
|
41
|
+
return {
|
|
42
|
+
taxableAmount: breakdown.taxableAmount,
|
|
43
|
+
taxAmount: breakdown.gstAmount,
|
|
44
|
+
components: componentsFrom(breakdown, rate),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export class IndiaGstTaxProvider extends ITaxProvider {
|
|
48
|
+
constructor() {
|
|
49
|
+
super(...arguments);
|
|
50
|
+
this.id = "india-gst";
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Intra-state when origin and destination states match.
|
|
54
|
+
*
|
|
55
|
+
* 🛑 Absent information is treated as INTRA-state, which preserves the
|
|
56
|
+
* existing behaviour exactly rather than improving on it. Every current
|
|
57
|
+
* caller passes an `intraState` boolean it computed itself, and the defaults
|
|
58
|
+
* across those call sites resolve to intra-state when a place of supply is
|
|
59
|
+
* unknown — changing that here would alter tax on orders with an incomplete
|
|
60
|
+
* delivery snapshot, which is precisely the class of row most likely to
|
|
61
|
+
* exist. A stricter rule is a product decision, not a side effect of adding
|
|
62
|
+
* an interface.
|
|
63
|
+
*/
|
|
64
|
+
isSameRegion(jurisdiction) {
|
|
65
|
+
const { origin, destination } = jurisdiction;
|
|
66
|
+
if (!origin || !destination)
|
|
67
|
+
return true;
|
|
68
|
+
return origin.trim().toUpperCase() === destination.trim().toUpperCase();
|
|
69
|
+
}
|
|
70
|
+
onExclusive(item, jurisdiction, taxableAmount) {
|
|
71
|
+
return toBreakdown(calculateGst(item.rate, this.isSameRegion(jurisdiction), taxableAmount), item.rate);
|
|
72
|
+
}
|
|
73
|
+
onInclusive(item, jurisdiction, gross) {
|
|
74
|
+
return toBreakdown(calculateGstInclusive(item.rate, this.isSameRegion(jurisdiction), gross), item.rate);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** The default instance. A consumer in another jurisdiction registers its own. */
|
|
78
|
+
export const indiaGstTaxProvider = new IndiaGstTaxProvider();
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* useScrollToTop — jump to the top whenever a key changes.
|
|
4
|
+
*
|
|
5
|
+
* ## Why a HOOK and not a `<ScrollToTop />` component
|
|
6
|
+
*
|
|
7
|
+
* The consumer had a 17-line component doing exactly this. Lifting it as a
|
|
8
|
+
* component would not work: the only interesting line is
|
|
9
|
+
* `usePathname()` from `@/i18n/navigation`, and that is the consumer's own
|
|
10
|
+
* routing choice (next-intl, locale-prefixed). appkit cannot import it, and
|
|
11
|
+
* taking a `pathname` prop would mean a component whose entire job is to pass
|
|
12
|
+
* one value to one effect.
|
|
13
|
+
*
|
|
14
|
+
* So the mechanism moves and the binding stays: the consumer keeps a four-line
|
|
15
|
+
* mount that supplies its own `usePathname()`.
|
|
16
|
+
*
|
|
17
|
+
* ## 🛑 `behavior: "auto"`, deliberately
|
|
18
|
+
*
|
|
19
|
+
* Not `"smooth"`. A route change should look instantaneous; smooth-scrolling a
|
|
20
|
+
* fresh page animates content the reader has not seen yet, and on a long page
|
|
21
|
+
* the animation is still running when they start reading. It also ignores
|
|
22
|
+
* `prefers-reduced-motion`, which `"auto"` sidesteps entirely by not animating.
|
|
23
|
+
*
|
|
24
|
+
* ## 🛑 Does NOT fire on mount
|
|
25
|
+
*
|
|
26
|
+
* `useEffect` with a dependency runs on mount too, which would fight the
|
|
27
|
+
* browser's own scroll restoration on a back-navigation — the reader returns to
|
|
28
|
+
* a list, the browser restores their place, and this yanks them to the top.
|
|
29
|
+
* The first run is skipped so only genuine changes scroll.
|
|
30
|
+
*
|
|
31
|
+
* That is a behaviour FIX, not a port: the consumer's component scrolled on
|
|
32
|
+
* mount. It was unnoticeable there because Next remounts the tree on a hard
|
|
33
|
+
* load anyway, but it is wrong for a hook anyone may mount mid-tree.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* "use client";
|
|
38
|
+
* import { usePathname } from "@/i18n/navigation";
|
|
39
|
+
* import { useScrollToTop } from "@mohasinac/appkit/react/hooks/useScrollToTop";
|
|
40
|
+
*
|
|
41
|
+
* export function ScrollToTop() {
|
|
42
|
+
* useScrollToTop(usePathname());
|
|
43
|
+
* return null;
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @tag domain:routing
|
|
48
|
+
* @tag layer:client
|
|
49
|
+
* @tag access:client
|
|
50
|
+
* @tag sideEffects:dom
|
|
51
|
+
*/
|
|
52
|
+
import { useEffect, useRef } from "react";
|
|
53
|
+
export function useScrollToTop(key, { enabled = true } = {}) {
|
|
54
|
+
const previous = useRef(undefined);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!enabled)
|
|
57
|
+
return;
|
|
58
|
+
// First run: record and do nothing — see the note on scroll restoration.
|
|
59
|
+
if (previous.current === undefined) {
|
|
60
|
+
previous.current = key;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (previous.current === key)
|
|
64
|
+
return;
|
|
65
|
+
previous.current = key;
|
|
66
|
+
if (typeof window === "undefined")
|
|
67
|
+
return;
|
|
68
|
+
window.scrollTo({ top: 0, left: 0, behavior: "auto" });
|
|
69
|
+
}, [key, enabled]);
|
|
70
|
+
}
|
|
@@ -4608,7 +4608,7 @@ export declare const SCHEMAS: {
|
|
|
4608
4608
|
status: "open" | "closed" | "in_progress" | "waiting_on_user" | "resolved";
|
|
4609
4609
|
id: string;
|
|
4610
4610
|
description: string;
|
|
4611
|
-
category: "general" | "
|
|
4611
|
+
category: "general" | "order_issue" | "billing_payment" | "account" | "listing_dispute" | "scam_report" | "refund_request" | "auction_dispute" | "store_change_request" | "order_modification_request" | "unban_request";
|
|
4612
4612
|
createdAt: string | Date | import("zod").objectOutputType<{
|
|
4613
4613
|
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
4614
4614
|
}, import("zod").ZodTypeAny, "passthrough">;
|
|
@@ -4651,7 +4651,7 @@ export declare const SCHEMAS: {
|
|
|
4651
4651
|
status: "open" | "closed" | "in_progress" | "waiting_on_user" | "resolved";
|
|
4652
4652
|
id: string;
|
|
4653
4653
|
description: string;
|
|
4654
|
-
category: "general" | "
|
|
4654
|
+
category: "general" | "order_issue" | "billing_payment" | "account" | "listing_dispute" | "scam_report" | "refund_request" | "auction_dispute" | "store_change_request" | "order_modification_request" | "unban_request";
|
|
4655
4655
|
createdAt: string | Date | import("zod").objectInputType<{
|
|
4656
4656
|
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
4657
4657
|
}, import("zod").ZodTypeAny, "passthrough">;
|
package/dist/seo/json-ld.js
CHANGED
|
@@ -13,7 +13,27 @@
|
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "";
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The brand name, from the injected identity rather than an env var.
|
|
18
|
+
*
|
|
19
|
+
* 🛑 A FUNCTION, not a module `const`, and that matters twice over. This was
|
|
20
|
+
* `const SITE_NAME = process.env.NEXT_PUBLIC_SITE_NAME ?? "App"`, which:
|
|
21
|
+
*
|
|
22
|
+
* - captured its value at IMPORT time, so even after `configureBrand()` runs
|
|
23
|
+
* it would have kept whatever it read first; and
|
|
24
|
+
* - read the env var this file's own comment at `SiteIdentityOptions` records
|
|
25
|
+
* as having shipped `"Letitrip"` to production — the wrong casing, in the
|
|
26
|
+
* `Organization.name` Google may put in a Knowledge Panel.
|
|
27
|
+
*
|
|
28
|
+
* That comment fixed the two site-wide nodes by taking `opts.siteName`. It did
|
|
29
|
+
* not fix `publisher` below, which has no opts path, so every article still
|
|
30
|
+
* emitted the env value. Resolving through `getBrandName()` closes both: there
|
|
31
|
+
* is now one owner (`appkit.config.js → brand`) and no mis-typeable env var in
|
|
32
|
+
* the path.
|
|
33
|
+
*/
|
|
34
|
+
function siteName() {
|
|
35
|
+
return getBrandName();
|
|
36
|
+
}
|
|
17
37
|
/**
|
|
18
38
|
* schema.org requires ABSOLUTE URLs. A relative `image` or `item` is not a
|
|
19
39
|
* lenient-parse case — Google rejects the property, and `image` is *required*
|
|
@@ -57,6 +77,7 @@ function absoluteUrl(value, base = SITE_URL) {
|
|
|
57
77
|
return `${base.replace(/\/+$/, "")}/${v.replace(/^\/+/, "")}`;
|
|
58
78
|
}
|
|
59
79
|
import { ProductStatusValues } from "../features/products/schemas/index.js";
|
|
80
|
+
import { getBrandName } from "../core/brand-resolver.js";
|
|
60
81
|
import { getDefaultCurrency } from "../core/baseline-resolver.js";
|
|
61
82
|
// The defining module, never the `/client` entry: this file is reached from
|
|
62
83
|
// server-only metadata paths and must not pull in a `"use client"` module
|
|
@@ -252,12 +273,12 @@ export function blogPostJsonLd(post) {
|
|
|
252
273
|
author: post.authorName
|
|
253
274
|
? { "@type": "Person", name: post.authorName, image: post.authorAvatar }
|
|
254
275
|
: undefined,
|
|
255
|
-
publisher: { "@type": "Organization", name:
|
|
276
|
+
publisher: { "@type": "Organization", name: siteName(), url: SITE_URL },
|
|
256
277
|
mainEntityOfPage: { "@type": "WebPage", "@id": url },
|
|
257
278
|
};
|
|
258
279
|
}
|
|
259
280
|
export function organizationJsonLd(opts) {
|
|
260
|
-
const name = opts?.siteName?.trim() ||
|
|
281
|
+
const name = opts?.siteName?.trim() || siteName();
|
|
261
282
|
const base = (opts?.siteUrl?.trim() || SITE_URL).replace(/\/+$/, "");
|
|
262
283
|
// `sameAs: []` was hardcoded empty while appkit.config.js carried real
|
|
263
284
|
// socialUrls that nothing read. An empty array is worse than omitting the key.
|
|
@@ -305,7 +326,7 @@ export function organizationJsonLd(opts) {
|
|
|
305
326
|
};
|
|
306
327
|
}
|
|
307
328
|
export function searchBoxJsonLd(opts) {
|
|
308
|
-
const name = opts?.siteName?.trim() ||
|
|
329
|
+
const name = opts?.siteName?.trim() || siteName();
|
|
309
330
|
const base = (opts?.siteUrl?.trim() || SITE_URL).replace(/\/+$/, "");
|
|
310
331
|
return {
|
|
311
332
|
"@context": "https://schema.org",
|
|
@@ -15,8 +15,26 @@ export interface SiteLogoProps {
|
|
|
15
15
|
* dark-primary surfaces in light mode etc.
|
|
16
16
|
*/
|
|
17
17
|
tone?: SiteLogoTone;
|
|
18
|
-
/** Accessible title
|
|
18
|
+
/** Accessible title. Defaults to `wordmark` + `tld`, so one prop usually suffices. */
|
|
19
19
|
title?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The wordmark GLYPHS. Defaults to `title`.
|
|
22
|
+
*
|
|
23
|
+
* 🛑 Separate from `title` because they are different things and conflating
|
|
24
|
+
* them is what hid the brand here. `title` is the accessible name; this is
|
|
25
|
+
* the text actually drawn in the SVG — and until A5 that text was the literal
|
|
26
|
+
* `LetItRip`, unreachable by any prop. A consumer could rename the accessible
|
|
27
|
+
* title and still render someone else's brand.
|
|
28
|
+
*/
|
|
29
|
+
wordmark?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Optional TLD badge drawn in a pill after the wordmark, e.g. `"in"`.
|
|
32
|
+
*
|
|
33
|
+
* Omitted by default: a library must not assume its consumer has a TLD worth
|
|
34
|
+
* showing, let alone which one. `TitleBarLayout` threads `brandTld` through
|
|
35
|
+
* for the installs that want it.
|
|
36
|
+
*/
|
|
37
|
+
tld?: string;
|
|
20
38
|
/**
|
|
21
39
|
* Optional admin-uploaded image URL. When present, renders an `<MediaImage>`
|
|
22
40
|
* instead of the SVG wordmark (so the watermark proxy applies). Falls back
|
|
@@ -24,4 +42,4 @@ export interface SiteLogoProps {
|
|
|
24
42
|
*/
|
|
25
43
|
src?: string;
|
|
26
44
|
}
|
|
27
|
-
export declare function SiteLogo({ size, tone, title, src, }: SiteLogoProps): import("react").JSX.Element;
|
|
45
|
+
export declare function SiteLogo({ size, tone, title, wordmark, tld, src, }: SiteLogoProps): import("react").JSX.Element;
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Site logo — an SVG wordmark with an optional TLD badge.
|
|
5
|
+
*
|
|
6
|
+
* 🛑 The glyphs are the CONSUMER's. This header used to read "SVG wordmark
|
|
7
|
+
* LetItRip + small .in superscript", and that was an accurate description: the
|
|
8
|
+
* text was baked into the SVG where no prop could reach it, so every install of
|
|
9
|
+
* appkit rendered one Indian marketplace's name in its own header. Both are
|
|
10
|
+
* props now (`wordmark`, `tld`), threaded from `appkit.config.js` through
|
|
11
|
+
* `AppLayoutShell` -> `TitleBarLayout`.
|
|
5
12
|
*
|
|
6
13
|
* Single source of truth for the site brand mark. The wordmark gradient
|
|
7
14
|
* follows the active theme via `var(--appkit-gradient-logo)`, so a custom
|
|
8
15
|
* admin theme automatically restyles the logo without forking the SVG.
|
|
9
16
|
*
|
|
17
|
+
* 🛑 The 214-unit viewBox was drawn around an eight-glyph wordmark. A much
|
|
18
|
+
* longer name will overflow it — pass `src` and use a real logo image rather
|
|
19
|
+
* than expecting the SVG to re-letter itself.
|
|
20
|
+
*
|
|
10
21
|
* Sizes are responsive presets — the variant catalogue forbids consumer-side
|
|
11
22
|
* className overrides on primitive components.
|
|
12
23
|
*/
|
|
@@ -38,18 +49,29 @@ function resolveFill(tone, gradientId) {
|
|
|
38
49
|
return `url(#${gradientId})`;
|
|
39
50
|
}
|
|
40
51
|
}
|
|
41
|
-
export function SiteLogo({ size = "md", tone = "brand",
|
|
52
|
+
export function SiteLogo({ size = "md", tone = "brand",
|
|
53
|
+
/*
|
|
54
|
+
* 🛑 No brand default. This read `title = "LetItRip.in"`, which put one
|
|
55
|
+
* consumer's name in a library primitive — and the SVG below ignored it
|
|
56
|
+
* anyway, drawing the literal `LetItRip` that no prop could reach.
|
|
57
|
+
*
|
|
58
|
+
* `"Site"` is deliberately bland: an unconfigured install should look
|
|
59
|
+
* obviously unconfigured rather than silently wear someone else's brand.
|
|
60
|
+
*/
|
|
61
|
+
title, wordmark, tld, src, }) {
|
|
62
|
+
const text = wordmark ?? title ?? "Site";
|
|
63
|
+
const accessibleTitle = title ?? (tld ? `${text}.${tld}` : text);
|
|
42
64
|
const uid = useId();
|
|
43
65
|
const gradientId = `${GRADIENT_ID_PREFIX}-${uid.replace(/:/g, "")}`;
|
|
44
66
|
const heightCls = SIZE_HEIGHTS[size];
|
|
45
67
|
if (src) {
|
|
46
|
-
return (_jsx(MediaImage, { src: src, alt:
|
|
68
|
+
return (_jsx(MediaImage, { src: src, alt: accessibleTitle, size: "thumbnail", objectFit: "contain",
|
|
47
69
|
// backed by the catalogued `size` enum; no consumer-provided overrides.
|
|
48
70
|
className: `block w-auto ${heightCls}` }));
|
|
49
71
|
}
|
|
50
72
|
const fill = resolveFill(tone, gradientId);
|
|
51
73
|
const isGradient = tone === "brand";
|
|
52
|
-
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 214 56", role: "img", "aria-label":
|
|
74
|
+
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 214 56", role: "img", "aria-label": accessibleTitle, "data-testid": "site-logo",
|
|
53
75
|
// backed by the catalogued `size` enum; no consumer-provided overrides.
|
|
54
|
-
className: `block w-auto ${heightCls}`, children: [_jsx("title", { children:
|
|
76
|
+
className: `block w-auto ${heightCls}`, children: [_jsx("title", { children: accessibleTitle }), isGradient && (_jsx("defs", { children: _jsxs("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "100%", y2: "0%", children: [_jsx("stop", { offset: "0%", style: { stopColor: "var(--appkit-logo-stop-from)" } }), _jsx("stop", { offset: "55%", style: { stopColor: "var(--appkit-logo-stop-mid)" } }), _jsx("stop", { offset: "100%", style: { stopColor: "var(--appkit-logo-stop-to)" } })] }) })), _jsx("text", { x: "0", y: "44", fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif", fontWeight: "800", fontSize: "44", letterSpacing: "-1.5", fill: fill, children: text }), tld && (_jsxs(_Fragment, { children: [_jsx("rect", { x: "169", y: "5", width: "43", height: "21", rx: "10.5", fill: fill, opacity: "0.12" }), _jsxs("text", { x: "174", y: "21", fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif", fontWeight: "700", fontSize: "15", letterSpacing: "0.2", fill: fill, children: [".", tld] })] }))] }));
|
|
55
77
|
}
|
|
@@ -11,7 +11,7 @@ export interface SiteMarkProps {
|
|
|
11
11
|
* `"on-primary"` renders the mark in `var(--appkit-color-text)`.
|
|
12
12
|
*/
|
|
13
13
|
tone?: SiteMarkTone;
|
|
14
|
-
/** Accessible title
|
|
14
|
+
/** Accessible title. Defaults to the configured brand name. */
|
|
15
15
|
title?: string;
|
|
16
16
|
/**
|
|
17
17
|
* Optional admin-uploaded image URL. When present, renders an `<MediaImage>`
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Site mark — the icon glyph (distinct from the `SiteLogo` text wordmark).
|
|
5
|
+
* Sourced from `public/logo.svg`'s path data, recolored to follow
|
|
6
6
|
* the active theme via the same `--appkit-logo-stop-*` variables `SiteLogo`
|
|
7
7
|
* uses, so a custom admin theme restyles both consistently.
|
|
8
8
|
*
|
|
@@ -10,6 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
10
|
* className overrides on primitive components.
|
|
11
11
|
*/
|
|
12
12
|
import { useId } from "react";
|
|
13
|
+
import { getBrandName } from "../../core/brand-resolver.js";
|
|
13
14
|
import { MediaImage } from "../../features/media/MediaImage.js";
|
|
14
15
|
// An SVG <linearGradient> ID prefix, not a class — suffixed with useId()
|
|
15
16
|
// and referenced as fill="url(#…)".
|
|
@@ -62,18 +63,32 @@ const MARK_TRANSFORMS = [
|
|
|
62
63
|
"translate(123,146)",
|
|
63
64
|
"translate(75,187)",
|
|
64
65
|
];
|
|
65
|
-
export function SiteMark({ size = "md", tone = "brand",
|
|
66
|
+
export function SiteMark({ size = "md", tone = "brand",
|
|
67
|
+
/*
|
|
68
|
+
* 🛑 Was `title = "LetItRip.in"` — one consumer's name as a library default.
|
|
69
|
+
*
|
|
70
|
+
* Unlike `SiteLogo`, this is NOT the unreachable-glyph bug: the mark is
|
|
71
|
+
* geometric paths with no text, so `title` really is only the accessible
|
|
72
|
+
* name. That makes it the quieter half of the same problem — a screen reader
|
|
73
|
+
* announced this marketplace on every install, and nothing on screen
|
|
74
|
+
* disagreed, so there was no way to notice.
|
|
75
|
+
*
|
|
76
|
+
* Resolved per render rather than at module load, so it reflects
|
|
77
|
+
* `configureBrand()` whenever that ran.
|
|
78
|
+
*/
|
|
79
|
+
title, src, }) {
|
|
80
|
+
const accessibleTitle = title ?? getBrandName();
|
|
66
81
|
const uid = useId();
|
|
67
82
|
const gradientId = `${GRADIENT_ID_PREFIX}-${uid.replace(/:/g, "")}`;
|
|
68
83
|
const heightCls = SIZE_HEIGHTS[size];
|
|
69
84
|
if (src) {
|
|
70
|
-
return (_jsx(MediaImage, { src: src, alt:
|
|
85
|
+
return (_jsx(MediaImage, { src: src, alt: accessibleTitle, size: "thumbnail", objectFit: "contain",
|
|
71
86
|
// backed by the catalogued `size` enum; no consumer-provided overrides.
|
|
72
87
|
className: `block w-auto ${heightCls}` }));
|
|
73
88
|
}
|
|
74
89
|
const fill = resolveFill(tone, gradientId);
|
|
75
90
|
const isGradient = tone === "brand";
|
|
76
|
-
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 236 245", role: "img", "aria-label":
|
|
91
|
+
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 236 245", role: "img", "aria-label": accessibleTitle, "data-testid": "site-mark",
|
|
77
92
|
// backed by the catalogued `size` enum; no consumer-provided overrides.
|
|
78
|
-
className: `block w-auto ${heightCls}`, children: [_jsx("title", { children:
|
|
93
|
+
className: `block w-auto ${heightCls}`, children: [_jsx("title", { children: accessibleTitle }), isGradient && (_jsx("defs", { children: _jsxs("linearGradient", { id: gradientId, x1: "0%", y1: "0%", x2: "100%", y2: "100%", children: [_jsx("stop", { offset: "0%", style: { stopColor: "var(--appkit-logo-stop-from)" } }), _jsx("stop", { offset: "55%", style: { stopColor: "var(--appkit-logo-stop-mid)" } }), _jsx("stop", { offset: "100%", style: { stopColor: "var(--appkit-logo-stop-to)" } })] }) })), MARK_PATHS.map((d, i) => (_jsx("path", { d: d, fill: fill, transform: MARK_TRANSFORMS[i] }, i)))] }));
|
|
79
94
|
}
|
|
@@ -20,3 +20,23 @@ export declare function isSameMonth(a: Date | number, b: Date | number): boolean
|
|
|
20
20
|
export declare function currentYear(): string;
|
|
21
21
|
export declare function nowISO(): string;
|
|
22
22
|
export declare function formatCustomDate(date: Date | string, format?: "short" | "medium" | "long" | "full"): string;
|
|
23
|
+
/**
|
|
24
|
+
* Render a `YYYY-MM` period as "October 2026".
|
|
25
|
+
*
|
|
26
|
+
* Lifted from the consumer's `src/lib/format/inr.ts` (A7), where five
|
|
27
|
+
* ad-booking surfaces shared it.
|
|
28
|
+
*
|
|
29
|
+
* 🛑 `timeZone: "UTC"` is load-bearing and is NOT a default worth overriding.
|
|
30
|
+
* The period is a calendar month with no time in it, so constructing it as UTC
|
|
31
|
+
* midnight and formatting in the viewer's zone renders the PREVIOUS month for
|
|
32
|
+
* anyone west of UTC. India is UTC+5:30, so the bug is invisible to everyone
|
|
33
|
+
* testing it there — which is why the original carried this same note.
|
|
34
|
+
*
|
|
35
|
+
* 🛑 The locale was a hardcoded `"en-IN"` in the consumer copy. It now comes
|
|
36
|
+
* from the market profile, which is the one thing about that helper that was
|
|
37
|
+
* genuinely consumer-specific.
|
|
38
|
+
*
|
|
39
|
+
* Returns the input unchanged when it is not a parseable `YYYY-MM`, rather than
|
|
40
|
+
* rendering "Invalid Date" into a billing table.
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatMonthLabel(period: string, locale?: string): string;
|