@mohasinac/appkit 4.69.1 → 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/about/index.d.ts +0 -2
- package/dist/features/about/index.js +0 -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/admin/schemas/firestore.js +0 -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/FAQSection.js +1 -1
- package/dist/features/homepage/components/MarketplaceHomepageView.js +34 -0
- 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 -3
- package/dist/index.js +0 -6
- 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/styles.css +20 -0
- 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/ui/components/Tabs.d.ts +9 -1
- package/dist/ui/components/Tabs.js +20 -3
- package/dist/ui/components/Tabs.style.css +20 -0
- package/dist/ui/rich-text/RichText.d.ts +0 -12
- package/dist/ui/rich-text/RichText.js +52 -1
- 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
- package/dist/features/about/components/AboutView.d.ts +0 -30
- package/dist/features/about/components/AboutView.js +0 -14
|
@@ -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
|
}
|
|
@@ -7,6 +7,14 @@ export interface TabsProps {
|
|
|
7
7
|
children?: React.ReactNode;
|
|
8
8
|
}
|
|
9
9
|
export interface TabsListProps {
|
|
10
|
+
/**
|
|
11
|
+
* Never collapse into a dropdown; let the tabs wrap onto a second line.
|
|
12
|
+
*
|
|
13
|
+
* For CONTENT tabs inside a section, where a dropdown reads as a stray form
|
|
14
|
+
* control. Navigation tab bars should leave this `false` — see the note in
|
|
15
|
+
* `TabsList`.
|
|
16
|
+
*/
|
|
17
|
+
wrap?: boolean;
|
|
10
18
|
className?: string;
|
|
11
19
|
children?: React.ReactNode;
|
|
12
20
|
}
|
|
@@ -30,7 +38,7 @@ export interface TabsContentProps {
|
|
|
30
38
|
children?: React.ReactNode;
|
|
31
39
|
}
|
|
32
40
|
export declare function Tabs({ defaultValue, value, onChange, className, children, }: TabsProps): React.JSX.Element;
|
|
33
|
-
export declare function TabsList({ className, children }: TabsListProps): React.JSX.Element;
|
|
41
|
+
export declare function TabsList({ className, children, wrap }: TabsListProps): React.JSX.Element;
|
|
34
42
|
export declare function TabsTrigger({ value, disabled, className, children, badge, }: TabsTriggerProps): React.JSX.Element;
|
|
35
43
|
export declare function TabsContent({ value, className, children, }: TabsContentProps): React.JSX.Element | null;
|
|
36
44
|
export default Tabs;
|
|
@@ -27,7 +27,7 @@ export function Tabs({ defaultValue = "", value, onChange, className = "", child
|
|
|
27
27
|
}), [active, onChange, value]);
|
|
28
28
|
return (_jsx(TabsContext.Provider, { value: contextValue, children: _jsx("div", { className: className, "data-section": "tabs-div-614", children: children }) }));
|
|
29
29
|
}
|
|
30
|
-
export function TabsList({ className = "", children }) {
|
|
30
|
+
export function TabsList({ className = "", children, wrap = false }) {
|
|
31
31
|
const { active, setActive } = useContext(TabsContext);
|
|
32
32
|
const shellRef = useRef(null);
|
|
33
33
|
const rowRef = useRef(null);
|
|
@@ -47,7 +47,24 @@ export function TabsList({ className = "", children }) {
|
|
|
47
47
|
onSelect: () => setActive(item.props.value),
|
|
48
48
|
}));
|
|
49
49
|
const signature = overflowItems.map((i) => `${i.id}:${i.label}:${i.count ?? ""}`).join("|");
|
|
50
|
-
const
|
|
50
|
+
const measured = useRowCollapse(shellRef, rowRef, signature);
|
|
51
|
+
/*
|
|
52
|
+
* 🛑 `wrap` OPTS OUT OF COLLAPSING ENTIRELY, and it exists because
|
|
53
|
+
* collapsing is only right for NAVIGATION.
|
|
54
|
+
*
|
|
55
|
+
* Owner, 2026-10-27: *"it makes sense for forms or navigations, but no
|
|
56
|
+
* sense for a homepage section's tabs to be coming down."* That is the
|
|
57
|
+
* correct distinction. A nav bar that collapses hides destinations behind
|
|
58
|
+
* one more tap, which is a fair trade on a phone. A content filter inside a
|
|
59
|
+
* section — the homepage FAQ's category tabs — collapses into a dropdown
|
|
60
|
+
* that looks like a form control sitting in the middle of a page, and the
|
|
61
|
+
* visitor cannot see at a glance what the section even contains.
|
|
62
|
+
*
|
|
63
|
+
* Wrapping is the right failure mode here: the tabs take a second line and
|
|
64
|
+
* stay visible. Default stays `false`, so every existing nav tab bar is
|
|
65
|
+
* untouched.
|
|
66
|
+
*/
|
|
67
|
+
const collapsed = wrap ? false : measured;
|
|
51
68
|
/*
|
|
52
69
|
* Collapsed renders `OverflowRow` in its already-collapsed state rather than
|
|
53
70
|
* the old `TabsNavSelect`, which was a native `<select>` — so the collapsed
|
|
@@ -62,7 +79,7 @@ export function TabsList({ className = "", children }) {
|
|
|
62
79
|
if (collapsed) {
|
|
63
80
|
return (_jsx("div", { ref: shellRef, className: className, children: _jsx(OverflowRow, { items: overflowItems, ariaLabel: "Tabs", activeId: active, alwaysCollapsed: true, searchPlaceholder: "Search tabs\u2026" }) }));
|
|
64
81
|
}
|
|
65
|
-
return (_jsx("div", { ref: shellRef, children: _jsx(TabBarShell, { className: className, ariaLabel: "Tabs", activeKey: active, listRef: rowRef, children: children }) }));
|
|
82
|
+
return (_jsx("div", { ref: shellRef, children: _jsx(TabBarShell, { className: [className, wrap ? "appkit-tabs-list--wrap" : ""].filter(Boolean).join(" "), ariaLabel: "Tabs", activeKey: active, listRef: rowRef, children: children }) }));
|
|
66
83
|
}
|
|
67
84
|
export function TabsTrigger({ value, disabled, className = "", children, badge, }) {
|
|
68
85
|
const { active, setActive } = useContext(TabsContext);
|
|
@@ -290,3 +290,23 @@
|
|
|
290
290
|
border-color: transparent;
|
|
291
291
|
color: var(--appkit-tabs-ink-active);
|
|
292
292
|
}
|
|
293
|
+
|
|
294
|
+
/*
|
|
295
|
+
* Wrapping tab bar — `<TabsList wrap>`.
|
|
296
|
+
*
|
|
297
|
+
* 🛑 For CONTENT tabs inside a page section, never for navigation. The
|
|
298
|
+
* default list scrolls horizontally and collapses into a dropdown once it
|
|
299
|
+
* overflows, which is right for a nav bar on a phone and wrong in the middle
|
|
300
|
+
* of a homepage section: there it renders as a stray form control, and the
|
|
301
|
+
* visitor cannot see what the section contains without opening it.
|
|
302
|
+
*
|
|
303
|
+
* Wrapping keeps every tab visible on a second line. `overflow: visible` is
|
|
304
|
+
* required alongside `flex-wrap` — the base rule's `overflow-x: auto` would
|
|
305
|
+
* otherwise still clip the wrapped rows and reintroduce a scrollbar.
|
|
306
|
+
*/
|
|
307
|
+
.appkit-tabs-list--wrap {
|
|
308
|
+
flex-wrap: wrap;
|
|
309
|
+
overflow-x: visible;
|
|
310
|
+
overflow-y: visible;
|
|
311
|
+
scrollbar-width: auto;
|
|
312
|
+
}
|
|
@@ -39,16 +39,4 @@ export interface RichTextProps {
|
|
|
39
39
|
*/
|
|
40
40
|
charts?: boolean;
|
|
41
41
|
}
|
|
42
|
-
/**
|
|
43
|
-
* RichText renders sanitised HTML from a CMS / ProseMirror output.
|
|
44
|
-
*
|
|
45
|
-
* Security: client-side DOM-based sanitiser strips disallowed tags/attrs.
|
|
46
|
-
* For defence-in-depth, also sanitise on the server before storing.
|
|
47
|
-
*
|
|
48
|
-
* Usage:
|
|
49
|
-
* ```tsx
|
|
50
|
-
* <RichText html={blogPost.body} className="max-w-2xl" />
|
|
51
|
-
* <RichText html={desc} copyableCode />
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
42
|
export declare function RichText({ html, className, proseClass, copyableCode, highlightCode, charts, }: RichTextProps): import("react").JSX.Element;
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useMemo } from "react";
|
|
4
4
|
import { Div } from "../components/Div.js";
|
|
5
5
|
import { normalizeRichTextHtml } from "../../utils/string.formatter.js";
|
|
6
|
+
import { useSiteSettings } from "../../core/hooks/useSiteSettings.js";
|
|
6
7
|
import { splitChartBlocks } from "./chart-blocks.js";
|
|
7
8
|
import { ChartFromJson } from "../components/Chart.js";
|
|
8
9
|
// --- Allowed HTML tags (safe subset — no script/object; iframe allowed for YouTube only) -------
|
|
@@ -182,9 +183,59 @@ function attachCopyButtons(container) {
|
|
|
182
183
|
* <RichText html={desc} copyableCode />
|
|
183
184
|
* ```
|
|
184
185
|
*/
|
|
186
|
+
/**
|
|
187
|
+
* Unwrap every `<a>` whose destination the site has closed, keeping its text.
|
|
188
|
+
*
|
|
189
|
+
* 🛑 THIS IS WHERE THE LAST DEAD LINKS WERE, and why six page-level sweeps
|
|
190
|
+
* kept reporting "clean". Stored rich text is authored content that no page
|
|
191
|
+
* template owns: a crawl of the live site on 2026-10-27 found `/scams`,
|
|
192
|
+
* `/tools/authenticity` and `/blog` linked from the HOMEPAGE, and every one
|
|
193
|
+
* of them was inside an FAQ ANSWER. Grepping a page for marketplace words
|
|
194
|
+
* cannot see an href, and gating a section cannot see inside its prose.
|
|
195
|
+
*
|
|
196
|
+
* `RichText` is the single renderer for every stored HTML field on the site —
|
|
197
|
+
* FAQ answers, legal pages, blog bodies, product descriptions — so one filter
|
|
198
|
+
* here covers all of them.
|
|
199
|
+
*
|
|
200
|
+
* 🛑 UNWRAPPED, NOT DELETED. The sentence still reads: "the scam registry is
|
|
201
|
+
* the place to report it" keeps its words and loses its link. Removing the
|
|
202
|
+
* whole anchor would leave a gap mid-sentence, and removing the paragraph
|
|
203
|
+
* would silently delete an admin's copy.
|
|
204
|
+
*
|
|
205
|
+
* Prefix-matched on a `/`-delimited boundary, the same rule as every other
|
|
206
|
+
* gate — `/store` must not match `/stores`.
|
|
207
|
+
*/
|
|
208
|
+
function unwrapClosedLinks(html, closed) {
|
|
209
|
+
if (closed.length === 0 || typeof document === "undefined")
|
|
210
|
+
return html;
|
|
211
|
+
const container = document.createElement("div");
|
|
212
|
+
container.innerHTML = html;
|
|
213
|
+
let changed = false;
|
|
214
|
+
for (const a of Array.from(container.querySelectorAll("a[href]"))) {
|
|
215
|
+
const raw = a.getAttribute("href") ?? "";
|
|
216
|
+
if (!raw.startsWith("/"))
|
|
217
|
+
continue;
|
|
218
|
+
const path = raw.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
|
|
219
|
+
// `/en/blog` -> `/blog`: an admin pasting out of the address bar stores
|
|
220
|
+
// the locale-prefixed form, which a raw comparison would miss.
|
|
221
|
+
const bare = path.replace(/^\/[a-z]{2}(?=\/)/, "");
|
|
222
|
+
const hit = closed.some((p) => bare === p || bare.startsWith(`${p}/`) || path === p || path.startsWith(`${p}/`));
|
|
223
|
+
if (!hit)
|
|
224
|
+
continue;
|
|
225
|
+
a.replaceWith(...Array.from(a.childNodes));
|
|
226
|
+
changed = true;
|
|
227
|
+
}
|
|
228
|
+
return changed ? container.innerHTML : html;
|
|
229
|
+
}
|
|
185
230
|
export function RichText({ html, className = "", proseClass = "prose max-w-none dark:prose-invert", copyableCode = false, highlightCode, charts = true, }) {
|
|
231
|
+
/*
|
|
232
|
+
* Fails OPEN: no settings yet, or a failed fetch, leaves every link intact.
|
|
233
|
+
* A transient error must not strip working links out of an admin's copy.
|
|
234
|
+
*/
|
|
235
|
+
const { data: settings } = useSiteSettings();
|
|
236
|
+
const closedPrefixes = settings?.hiddenSurfacePrefixes ?? [];
|
|
186
237
|
const safe = useMemo(() => {
|
|
187
|
-
const sanitized = sanitiseHtml(normalizeRichTextHtml(html));
|
|
238
|
+
const sanitized = unwrapClosedLinks(sanitiseHtml(normalizeRichTextHtml(html)), closedPrefixes);
|
|
188
239
|
if (!highlightCode || typeof document === "undefined")
|
|
189
240
|
return sanitized;
|
|
190
241
|
const container = document.createElement("div");
|
|
@@ -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;
|
|
@@ -192,3 +192,28 @@ export function formatCustomDate(date, format = "medium") {
|
|
|
192
192
|
const options = formatOptions[format] || formatOptions.medium;
|
|
193
193
|
return dateObj.toLocaleDateString(getDefaultLocale(), options);
|
|
194
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Render a `YYYY-MM` period as "October 2026".
|
|
197
|
+
*
|
|
198
|
+
* Lifted from the consumer's `src/lib/format/inr.ts` (A7), where five
|
|
199
|
+
* ad-booking surfaces shared it.
|
|
200
|
+
*
|
|
201
|
+
* 🛑 `timeZone: "UTC"` is load-bearing and is NOT a default worth overriding.
|
|
202
|
+
* The period is a calendar month with no time in it, so constructing it as UTC
|
|
203
|
+
* midnight and formatting in the viewer's zone renders the PREVIOUS month for
|
|
204
|
+
* anyone west of UTC. India is UTC+5:30, so the bug is invisible to everyone
|
|
205
|
+
* testing it there — which is why the original carried this same note.
|
|
206
|
+
*
|
|
207
|
+
* 🛑 The locale was a hardcoded `"en-IN"` in the consumer copy. It now comes
|
|
208
|
+
* from the market profile, which is the one thing about that helper that was
|
|
209
|
+
* genuinely consumer-specific.
|
|
210
|
+
*
|
|
211
|
+
* Returns the input unchanged when it is not a parseable `YYYY-MM`, rather than
|
|
212
|
+
* rendering "Invalid Date" into a billing table.
|
|
213
|
+
*/
|
|
214
|
+
export function formatMonthLabel(period, locale) {
|
|
215
|
+
const [y, m] = period.split("-").map(Number);
|
|
216
|
+
if (!Number.isFinite(y) || !Number.isFinite(m) || m < 1 || m > 12)
|
|
217
|
+
return period;
|
|
218
|
+
return new Date(Date.UTC(y, m - 1, 1)).toLocaleDateString(locale ?? getDefaultLocale(), { month: "long", year: "numeric", timeZone: "UTC" });
|
|
219
|
+
}
|
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Number Formatting Utilities
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Money for PROSE — notification bodies, error messages, log lines, chips.
|
|
6
|
+
*
|
|
7
|
+
* ## Why this exists beside `formatCurrency`
|
|
8
|
+
*
|
|
9
|
+
* 71 call sites interpolated a hardcoded symbol next to a bare number —
|
|
10
|
+
* `` `Minimum offer is ₹${bounds.min}` `` — which is wrong three ways at once:
|
|
11
|
+
* the symbol is baked in, the grouping is absent (`₹1000`, not `₹1,000`), and
|
|
12
|
+
* ~20 more files reached for `toLocaleString("en-IN")` with the locale
|
|
13
|
+
* hardcoded too. All three read from the market profile now, which the consumer
|
|
14
|
+
* already configures via `configureMarketDefaults()`.
|
|
15
|
+
*
|
|
16
|
+
* ## 🛑 Why not just use `formatCurrency`
|
|
17
|
+
*
|
|
18
|
+
* `Intl.NumberFormat` with `style: "currency"` forces the currency's standard
|
|
19
|
+
* fraction digits, so every whole amount becomes `₹1,000.00`. Rewriting 71
|
|
20
|
+
* strings to add `.00` to prices, offers and refunds that are whole amounts is
|
|
21
|
+
* a user-visible copy change dressed up as a refactor. This shows the
|
|
22
|
+
* fractional part only when there is one: `₹1,000` and `₹1,000.50`.
|
|
23
|
+
*
|
|
24
|
+
* `formatCurrency` keeps its job — the ISO-correct, locale-placed rendering for
|
|
25
|
+
* tabular and formal surfaces (invoices, statements). Use that where the form
|
|
26
|
+
* matters and this where the sentence does.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatMoney(amount: number, locale?: string): string;
|
|
4
29
|
export declare function formatCurrency(amount: number, currency?: string, locale?: string): string;
|
|
5
30
|
export declare function formatNumber(num: number, locale?: string, options?: {
|
|
6
31
|
decimals?: number;
|
|
@@ -13,3 +38,25 @@ export declare function formatCompactNumber(num: number): string;
|
|
|
13
38
|
export declare function formatDecimal(num: number, decimals?: number): string;
|
|
14
39
|
export declare function formatOrdinal(num: number): string;
|
|
15
40
|
export declare function parseFormattedNumber(str: string): number;
|
|
41
|
+
/**
|
|
42
|
+
* Whole currency units — no fractional digits — with locale grouping.
|
|
43
|
+
*
|
|
44
|
+
* `₹1,500` for 1499.50. Lifted from the consumer's `src/lib/format/inr.ts`
|
|
45
|
+
* (A5b/A7), where six `/user/*` and ad-booking pages had carried byte-identical
|
|
46
|
+
* private copies before it was consolidated there.
|
|
47
|
+
*
|
|
48
|
+
* ## 🛑 This is NOT `formatMoney`, and must not be collapsed into it
|
|
49
|
+
*
|
|
50
|
+
* `maximumFractionDigits: 0` is the whole point: this ROUNDS, so 1499.50
|
|
51
|
+
* renders `₹1,500`. `formatMoney` keeps the fractional part when there is one.
|
|
52
|
+
* Both are
|
|
53
|
+
* correct for their own surfaces — this one is for figures where the fraction
|
|
54
|
+
* is noise (a monthly ad spend, an order total in a summary row), and the
|
|
55
|
+
* surfaces using it show booking and order totals, so a change in rounding is
|
|
56
|
+
* a change in a number a buyer reads.
|
|
57
|
+
*
|
|
58
|
+
* Converging the two is a product decision about displayed money, not a
|
|
59
|
+
* de-duplication. The consumer's copy carried that warning for exactly this
|
|
60
|
+
* reason and it travels with the function.
|
|
61
|
+
*/
|
|
62
|
+
export declare function formatWholeMoney(amount: number, locale?: string): string;
|
|
@@ -1,7 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Number Formatting Utilities
|
|
3
3
|
*/
|
|
4
|
-
import { getDefaultCurrency, getDefaultLocale, } from "../core/baseline-resolver.js";
|
|
4
|
+
import { getDefaultCurrency, getDefaultCurrencySymbol, getDefaultLocale, } from "../core/baseline-resolver.js";
|
|
5
|
+
/**
|
|
6
|
+
* Money for PROSE — notification bodies, error messages, log lines, chips.
|
|
7
|
+
*
|
|
8
|
+
* ## Why this exists beside `formatCurrency`
|
|
9
|
+
*
|
|
10
|
+
* 71 call sites interpolated a hardcoded symbol next to a bare number —
|
|
11
|
+
* `` `Minimum offer is ₹${bounds.min}` `` — which is wrong three ways at once:
|
|
12
|
+
* the symbol is baked in, the grouping is absent (`₹1000`, not `₹1,000`), and
|
|
13
|
+
* ~20 more files reached for `toLocaleString("en-IN")` with the locale
|
|
14
|
+
* hardcoded too. All three read from the market profile now, which the consumer
|
|
15
|
+
* already configures via `configureMarketDefaults()`.
|
|
16
|
+
*
|
|
17
|
+
* ## 🛑 Why not just use `formatCurrency`
|
|
18
|
+
*
|
|
19
|
+
* `Intl.NumberFormat` with `style: "currency"` forces the currency's standard
|
|
20
|
+
* fraction digits, so every whole amount becomes `₹1,000.00`. Rewriting 71
|
|
21
|
+
* strings to add `.00` to prices, offers and refunds that are whole amounts is
|
|
22
|
+
* a user-visible copy change dressed up as a refactor. This shows the
|
|
23
|
+
* fractional part only when there is one: `₹1,000` and `₹1,000.50`.
|
|
24
|
+
*
|
|
25
|
+
* `formatCurrency` keeps its job — the ISO-correct, locale-placed rendering for
|
|
26
|
+
* tabular and formal surfaces (invoices, statements). Use that where the form
|
|
27
|
+
* matters and this where the sentence does.
|
|
28
|
+
*/
|
|
29
|
+
export function formatMoney(amount, locale) {
|
|
30
|
+
const resolvedLocale = locale ?? getDefaultLocale();
|
|
31
|
+
/*
|
|
32
|
+
* 🛑 0 decimals if integral, otherwise exactly 2 — NOT `min: 0, max: 2`.
|
|
33
|
+
*
|
|
34
|
+
* That first draft was wrong and `published-values.ts` caught it: with a
|
|
35
|
+
* floor of 0, `1499.5` renders "₹1,499.5", a money figure with one decimal
|
|
36
|
+
* place. The rule money actually wants is "no trailing .00 on a whole
|
|
37
|
+
* amount, but always both places when there is a fractional part" — exactly
|
|
38
|
+
* what the `formatRupees` this replaced encoded as
|
|
39
|
+
* `minimumFractionDigits: Number.isInteger(amount) ? 0 : 2`, and what its own
|
|
40
|
+
* docstring stated as `₹10`, `₹1,499.50`.
|
|
41
|
+
*/
|
|
42
|
+
const fractionDigits = Number.isInteger(amount) ? 0 : 2;
|
|
43
|
+
const grouped = new Intl.NumberFormat(resolvedLocale, {
|
|
44
|
+
minimumFractionDigits: fractionDigits,
|
|
45
|
+
maximumFractionDigits: fractionDigits,
|
|
46
|
+
}).format(amount);
|
|
47
|
+
/*
|
|
48
|
+
* Symbol then number, not `Intl`'s currency placement. These strings are
|
|
49
|
+
* assembled into sentences that already read "₹500" in copy the team wrote,
|
|
50
|
+
* and a locale whose convention is "1 000,00 ₹" would reorder mid-sentence.
|
|
51
|
+
* A market that needs the symbol elsewhere overrides `currencySymbol`.
|
|
52
|
+
*/
|
|
53
|
+
return `${getDefaultCurrencySymbol()}${grouped}`;
|
|
54
|
+
}
|
|
5
55
|
export function formatCurrency(amount, currency, locale) {
|
|
6
56
|
const resolvedCurrency = currency ?? getDefaultCurrency();
|
|
7
57
|
const resolvedLocale = locale ?? getDefaultLocale();
|
|
@@ -107,3 +157,31 @@ export function parseFormattedNumber(str) {
|
|
|
107
157
|
const num = parseFloat(result);
|
|
108
158
|
return isNegative ? -Math.abs(num) : num;
|
|
109
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Whole currency units — no fractional digits — with locale grouping.
|
|
162
|
+
*
|
|
163
|
+
* `₹1,500` for 1499.50. Lifted from the consumer's `src/lib/format/inr.ts`
|
|
164
|
+
* (A5b/A7), where six `/user/*` and ad-booking pages had carried byte-identical
|
|
165
|
+
* private copies before it was consolidated there.
|
|
166
|
+
*
|
|
167
|
+
* ## 🛑 This is NOT `formatMoney`, and must not be collapsed into it
|
|
168
|
+
*
|
|
169
|
+
* `maximumFractionDigits: 0` is the whole point: this ROUNDS, so 1499.50
|
|
170
|
+
* renders `₹1,500`. `formatMoney` keeps the fractional part when there is one.
|
|
171
|
+
* Both are
|
|
172
|
+
* correct for their own surfaces — this one is for figures where the fraction
|
|
173
|
+
* is noise (a monthly ad spend, an order total in a summary row), and the
|
|
174
|
+
* surfaces using it show booking and order totals, so a change in rounding is
|
|
175
|
+
* a change in a number a buyer reads.
|
|
176
|
+
*
|
|
177
|
+
* Converging the two is a product decision about displayed money, not a
|
|
178
|
+
* de-duplication. The consumer's copy carried that warning for exactly this
|
|
179
|
+
* reason and it travels with the function.
|
|
180
|
+
*/
|
|
181
|
+
export function formatWholeMoney(amount, locale) {
|
|
182
|
+
return new Intl.NumberFormat(locale ?? getDefaultLocale(), {
|
|
183
|
+
style: "currency",
|
|
184
|
+
currency: getDefaultCurrency(),
|
|
185
|
+
maximumFractionDigits: 0,
|
|
186
|
+
}).format(amount);
|
|
187
|
+
}
|
|
@@ -6,4 +6,3 @@ export declare function normalizePhone(phone: string): string;
|
|
|
6
6
|
export declare function formatPhone(phone: string, countryCode?: string): string;
|
|
7
7
|
export declare function extractCountryCode(phone: string): string | null;
|
|
8
8
|
export declare function isValidMobile(phone: string, phonePrefix?: string): boolean;
|
|
9
|
-
export declare function isValidIndianPincode(pincode: string): boolean;
|
|
@@ -36,6 +36,20 @@ export function isValidMobile(phone, phonePrefix) {
|
|
|
36
36
|
// Generic: 10-15 digit number
|
|
37
37
|
return cleaned.length >= 10 && cleaned.length <= 15;
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
/*
|
|
40
|
+
* 🛑 `isValidIndianPincode` was REMOVED in A6c. It hardcoded `/^\d{6}$/` — one
|
|
41
|
+
* country's postcode shape in a library meant to be jurisdiction-neutral — and
|
|
42
|
+
* had **zero callers**: the only reference anywhere was its own re-export from
|
|
43
|
+
* `index.ts`, so it was dead code on appkit's public surface.
|
|
44
|
+
*
|
|
45
|
+
* `constants/geo/countries.ts` is the live answer and a better one: 26 countries
|
|
46
|
+
* as DATA (`postalPattern`, `postalLabel`, `hasStates`, `dialCode`) behind
|
|
47
|
+
* `isValidPostalCode` / `postalPatternFor` / `postalLabelFor`, with 17 reader
|
|
48
|
+
* files. Its own header records the bug this function's shape causes —
|
|
49
|
+
* "a Canadian address was being held to `/^\d{6}$/`".
|
|
50
|
+
*
|
|
51
|
+
* That table is also why A6's proposed `IAddressValidationProvider` was not
|
|
52
|
+
* built: the AK2 row describing appkit as doing "pincode-only India validation"
|
|
53
|
+
* predates `countries.ts` and is stale. Postcode rules are a 26-row table, not
|
|
54
|
+
* 26 provider implementations.
|
|
55
|
+
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mohasinac/appkit",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.71.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -67,7 +67,12 @@
|
|
|
67
67
|
"types": "./dist/contracts/registry.d.ts",
|
|
68
68
|
"import": "./dist/contracts/registry.js"
|
|
69
69
|
},
|
|
70
|
-
"./
|
|
70
|
+
"./cli": {
|
|
71
|
+
"types": "./dist/cli/index.d.ts",
|
|
72
|
+
"import": "./dist/cli/index.js",
|
|
73
|
+
"require": "./dist/cli/index.js"
|
|
74
|
+
},
|
|
75
|
+
"./core/baseline-resolver": {
|
|
71
76
|
"types": "./dist/core/baseline-resolver.d.ts",
|
|
72
77
|
"import": "./dist/core/baseline-resolver.js"
|
|
73
78
|
},
|
|
@@ -83,7 +88,15 @@
|
|
|
83
88
|
"types": "./dist/providers/email-resend/index.d.ts",
|
|
84
89
|
"import": "./dist/providers/email-resend/index.js"
|
|
85
90
|
},
|
|
86
|
-
"./providers/
|
|
91
|
+
"./providers/tax-india-gst": {
|
|
92
|
+
"types": "./dist/providers/tax-india-gst/index.d.ts",
|
|
93
|
+
"import": "./dist/providers/tax-india-gst/index.js"
|
|
94
|
+
},
|
|
95
|
+
"./providers/shipping-shiprocket": {
|
|
96
|
+
"types": "./dist/providers/shipping-shiprocket/index.d.ts",
|
|
97
|
+
"import": "./dist/providers/shipping-shiprocket/index.js"
|
|
98
|
+
},
|
|
99
|
+
"./providers/storage-firebase": {
|
|
87
100
|
"types": "./dist/providers/storage-firebase/index.d.ts",
|
|
88
101
|
"import": "./dist/providers/storage-firebase/index.js"
|
|
89
102
|
},
|
|
@@ -317,6 +330,7 @@
|
|
|
317
330
|
},
|
|
318
331
|
"files": [
|
|
319
332
|
"dist",
|
|
333
|
+
"PROVIDERS.md",
|
|
320
334
|
"tsconfig.base.json",
|
|
321
335
|
"scripts/sieve-audit.mjs",
|
|
322
336
|
"scripts/audit-violations.mjs",
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { AboutHowItem, AboutValueItem, AboutMilestone, AboutTeamMember } from "../schemas/firestore";
|
|
3
|
-
export type { AboutHowItem, AboutValueItem, AboutMilestone, AboutTeamMember } from "../schemas/firestore";
|
|
4
|
-
export interface AboutViewProps {
|
|
5
|
-
labels?: {
|
|
6
|
-
title?: string;
|
|
7
|
-
subtitle?: string;
|
|
8
|
-
missionTitle?: string;
|
|
9
|
-
missionText?: string;
|
|
10
|
-
howItWorksTitle?: string;
|
|
11
|
-
valuesTitle?: string;
|
|
12
|
-
valuesSubtitle?: string;
|
|
13
|
-
/** When set, renders a link from the values section through to `/ethics`. */
|
|
14
|
-
valuesLinkLabel?: string;
|
|
15
|
-
milestonesTitle?: string;
|
|
16
|
-
teamTitle?: string;
|
|
17
|
-
teamSubtitle?: string;
|
|
18
|
-
ctaTitle?: string;
|
|
19
|
-
ctaSell?: string;
|
|
20
|
-
ctaShop?: string;
|
|
21
|
-
};
|
|
22
|
-
howItems?: AboutHowItem[];
|
|
23
|
-
valueItems?: AboutValueItem[];
|
|
24
|
-
milestones?: AboutMilestone[];
|
|
25
|
-
teamMembers?: AboutTeamMember[];
|
|
26
|
-
ctaBannerClass?: string;
|
|
27
|
-
/** Render the CTA action buttons */
|
|
28
|
-
renderCtaButtons?: () => React.ReactNode;
|
|
29
|
-
}
|
|
30
|
-
export declare function AboutView({ labels, howItems, valueItems, milestones, teamMembers, ctaBannerClass, renderCtaButtons, }: AboutViewProps): React.JSX.Element;
|