@mohasinac/appkit 4.69.1 → 4.70.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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "stampVersion": 1,
3
- "version": "4.69.1",
4
- "head": "1a1259a8058e4a957e71fef93fe49c34708f77bb",
3
+ "version": "4.70.0",
4
+ "head": "2818ecbd04176961cc2184dc89a3bf319efb92d4",
5
5
  "branch": "main",
6
6
  "dirty": false,
7
- "builtAt": "2026-10-27T13:28:31.858Z"
7
+ "builtAt": "2026-10-28T02:31:30.990Z"
8
8
  }
@@ -1,5 +1,3 @@
1
- export { AboutView } from "./components/AboutView";
2
- export type { AboutViewProps, AboutHowItem, AboutValueItem, AboutMilestone, AboutTeamMember, } from "./components/AboutView";
3
1
  export type { AboutContentDocument } from "./schemas/firestore";
4
2
  export { TrackOrderView } from "./components/TrackOrderView";
5
3
  export type { TrackOrderViewProps } from "./components/TrackOrderView";
@@ -1,4 +1,3 @@
1
- export { AboutView } from "./components/AboutView.js";
2
1
  export { TrackOrderView } from "./components/TrackOrderView.js";
3
2
  /*
4
3
  * The shared prose-page banner. Exported because it is the one thing all
@@ -442,7 +442,6 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
442
442
  String(ROUTES.PUBLIC.ITEM_REQUESTS),
443
443
  String(ROUTES.PUBLIC.LOTTERIES),
444
444
  String(ROUTES.PUBLIC.PARTS),
445
- String(ROUTES.PUBLIC.PROMOTIONS),
446
445
  String(ROUTES.PUBLIC.REPORT_ENTITY),
447
446
  String(ROUTES.PUBLIC.SCAMS),
448
447
  /* No `ROUTES.PUBLIC.SELL` constant exists, so `/sell` — the public
@@ -65,7 +65,7 @@ export function FAQSection({ title, subtitle, items, tabs: tabsProp, showCategor
65
65
  return items;
66
66
  return items.filter((i) => (i.category ?? "general") === activeTab);
67
67
  }, [items, showTabs, activeTab]);
68
- return (_jsx(Section, { paddingX: "x-md-2xl", className: className, surface: "subtle", paddingY: "y-4xl", children: _jsxs(Div, { className: "w-full max-w-4xl mx-auto", children: [_jsxs(Div, { className: "text-left mb-10", children: [_jsx(Heading, { level: 2, variant: "none", gradient: "brand", className: "mb-3", mdSize: "4xl", size: "3xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { className: `max-w-xl ${themed.textSecondary}`, size: "base", children: subtitle }))] }), showTabs && (_jsx(Tabs, { value: activeTab, onChange: setActiveTab, className: "mb-8", children: _jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "all", children: "All" }), derivedTabs.map((tab) => (_jsx(TabsTrigger, { value: tab.value, children: tab.label }, tab.value)))] }) })), _jsxs(Stack, { gap: "none", rounded: "xl", className: `${themed.bgPrimary} ${__O.hidden} border ${themed.border}`, children: [visibleItems.length === 0 && (_jsx(Text, { paddingY: "3xl", align: "start", className: themed.textSecondary, children: "No FAQs in this category yet." })), visibleItems.map((faq, index) => {
68
+ return (_jsx(Section, { paddingX: "x-md-2xl", className: className, surface: "subtle", paddingY: "y-4xl", children: _jsxs(Div, { className: "w-full max-w-4xl mx-auto", children: [_jsxs(Div, { className: "text-left mb-10", children: [_jsx(Heading, { level: 2, variant: "none", gradient: "brand", className: "mb-3", mdSize: "4xl", size: "3xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { className: `max-w-xl ${themed.textSecondary}`, size: "base", children: subtitle }))] }), showTabs && (_jsx(Tabs, { value: activeTab, onChange: setActiveTab, className: "mb-8", children: _jsxs(TabsList, { wrap: true, children: [_jsx(TabsTrigger, { value: "all", children: "All" }), derivedTabs.map((tab) => (_jsx(TabsTrigger, { value: tab.value, children: tab.label }, tab.value)))] }) })), _jsxs(Stack, { gap: "none", rounded: "xl", className: `${themed.bgPrimary} ${__O.hidden} border ${themed.border}`, children: [visibleItems.length === 0 && (_jsx(Text, { paddingY: "3xl", align: "start", className: themed.textSecondary, children: "No FAQs in this category yet." })), visibleItems.map((faq, index) => {
69
69
  const isOpen = openIds.has(faq.id);
70
70
  return (_jsxs(Div, { className: `transition-colors duration-200 ${index < visibleItems.length - 1 ? "border-b border-[var(--appkit-color-border)]" : ""} ${isOpen ? "bg-[var(--appkit-color-surface-elevated)]" : ""}`, children: [_jsxs(Button, { variant: "ghost", rounded: "none", className: `w-full text-left px-[1.25rem] py-[1rem] ${flex.between} gap-[1rem] hover:${themed.bgTertiary} transition-colors`, onClick: () => toggle(faq.id), "aria-expanded": isOpen, children: [_jsx(Span, { size: "sm", weight: "medium", className: `flex-1 leading-snug ${isOpen ? "text-primary" : themed.textPrimary}`, children: faq.question }), _jsx(ChevronDown, { className: `w-4 h-4 flex-shrink-0 transition-transform duration-250 ${isOpen ? "rotate-180 text-primary" : themed.textSecondary}` })] }), _jsx(Div, { layout: "grid", className: `transition-all duration-300 ease-out ${isOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"}`, children: _jsx(Div, { className: `${__O.hidden}`, children: _jsx(Div, { paddingY: "b-md-lg", padding: "x-md", children: isHtml(faq.answer) ? (_jsx(RichText, { html: faq.answer, proseClass: "prose prose-sm max-w-none dark:prose-invert" })) : (_jsx(Text, { className: `leading-relaxed ${themed.textSecondary}`, size: "sm", children: faq.answer })) }) }) })] }, faq.id));
71
71
  })] }), viewMoreHref && (_jsx(Div, { className: "text-left mt-10", children: _jsxs(TextLink, { rounded: "full", paddingX: "xl", paddingY: "sm", href: viewMoreHref, layout: "inline-flex", align: "center", className: "gap-[0.5rem] bg-primary/10 text-primary hover:bg-primary/20 dark:bg-primary/15 transition-colors", size: "sm", weight: "medium", children: [viewMoreLabel, hasMore && moreCount > 0 && (_jsxs(Span, { color: "inverse", size: "xs", className: "bg-primary", rounded: "full", padding: "pill-xs", children: ["+", moreCount] }))] }) }))] }) }));
@@ -22,6 +22,7 @@ import { gateCarouselSlides } from "../lib/slide-gating.js";
22
22
  import { gateHomepageSections } from "../lib/section-gating.js";
23
23
  import { closedSurfaceContext, isClosedHref } from "../../../_internal/shared/listing-types/route-gating.js";
24
24
  import { getSurfaceCounts } from "../../../_internal/server/features/products/surface-counts.js";
25
+ import { ROUTES } from "../../../next/routing/route-map.js";
25
26
  import { SECTION_COPY } from "../constants/section-copy.js";
26
27
  import { homepageSectionsRepository } from "../repository/homepage-sections.repository.js";
27
28
  import { isListingTypeEnabled, isCategoryTypeEnabled, isBrowsePageEnabled, } from "../../../_internal/shared/listing-types/feature-flags.js";
@@ -75,6 +76,36 @@ const HOMEPAGE_SECTION_BROWSE_PAGE = {
75
76
  brands: "brands",
76
77
  stores: "stores",
77
78
  };
79
+ /**
80
+ * The FOURTH axis — a section that funnels into a route closed by
81
+ * `closedPaths` or `disabledRoutes`, neither of which the three maps above
82
+ * can see.
83
+ *
84
+ * 🛑 THIS ONE EXISTS BECAUSE A SEED LOAD RE-ENABLED A SECTION. The blog rail
85
+ * was switched off per-document, `appkit-seed load` writes `set(merge: true)`
86
+ * over `enabled`, and the seed still said `true` — so the rail came back and
87
+ * the homepage shipped FIVE dead links (`/blog` plus four post URLs) into a
88
+ * section closed via `disabledRoutes`.
89
+ *
90
+ * A per-document `enabled: false` is a value a reseed can overwrite. A gate
91
+ * keyed on whether the destination is OPEN cannot be: the section stays dark
92
+ * for exactly as long as the route is shut, however the flag got flipped.
93
+ * That is the difference between fixing this instance and fixing the class.
94
+ */
95
+ /*
96
+ * 🛑 `coupons` is NOT here. Its "View all offers" button points at
97
+ * `/promotions`, which is a real offers page with a live coupon behind it —
98
+ * so the fix was to REOPEN that route, not to hide a working rail. Closing a
99
+ * page and then hiding everything that links to it is the wrong direction
100
+ * when the page is one a shop wants.
101
+ */
102
+ const HOMEPAGE_SECTION_ROUTE = {
103
+ "blog-articles": String(ROUTES.BLOG.LIST),
104
+ events: String(ROUTES.PUBLIC.EVENTS),
105
+ "event-raffles": String(ROUTES.PUBLIC.EVENTS),
106
+ reviews: String(ROUTES.PUBLIC.REVIEWS),
107
+ "featured-bundles": String(ROUTES.PUBLIC.BUNDLES),
108
+ };
78
109
  import { getFeaturedProducts, getFeaturedAuctions, getFeaturedPreOrders } from "../../products/actions/product-actions.js";
79
110
  import { listTopLevelCategories, listBrandCategories } from "../../categories/actions/category-actions.js";
80
111
  import { listFeaturedBundles } from "../../../_internal/server/features/bundles/data.js";
@@ -189,6 +220,9 @@ export async function MarketplaceHomepageView({ adSlots, newsletterFormSlot, onB
189
220
  const gatedBrowse = HOMEPAGE_SECTION_BROWSE_PAGE[type];
190
221
  if (gatedBrowse && !isBrowsePageEnabled(gatedBrowse, siteSettings))
191
222
  return false;
223
+ const gatedRoute = HOMEPAGE_SECTION_ROUTE[type];
224
+ if (gatedRoute && isClosedHref(gatedRoute, closedSurfaces))
225
+ return false;
192
226
  return true;
193
227
  });
194
228
  /*
package/dist/index.d.ts CHANGED
@@ -753,7 +753,6 @@ export { validateFileSize } from "./providers/storage-firebase/client";
753
753
  export { validateFileType } from "./providers/storage-firebase/client";
754
754
  export { validateImage } from "./providers/storage-firebase/client";
755
755
  export type { StorageHelpers } from "./providers/storage-firebase/client";
756
- export { AboutView } from "./features/about/components/AboutView";
757
756
  export { FAQPageView } from "./features/about/components/FAQPageView";
758
757
  export { FeesView } from "./features/about/components/FeesView";
759
758
  export { HelpPageView } from "./features/about/components/HelpPageView";
@@ -774,7 +773,6 @@ export { TrackOrderView } from "./features/about/components/TrackOrderView";
774
773
  export type { AboutHowItem } from "./features/about/schemas/firestore";
775
774
  export type { AboutMilestone } from "./features/about/schemas/firestore";
776
775
  export type { AboutValueItem } from "./features/about/schemas/firestore";
777
- export type { AboutViewProps } from "./features/about/components/AboutView";
778
776
  export type { AboutTeamMember } from "./features/about/schemas/firestore";
779
777
  export type { AboutContentDocument } from "./features/about/schemas/firestore";
780
778
  export type { FAQPageViewProps } from "./features/about/components/FAQPageView";
package/dist/index.js CHANGED
@@ -1580,10 +1580,6 @@ export { validateFileType } from "./providers/storage-firebase/client.js";
1580
1580
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
1581
1581
  // validateImage - Helper for validate image.
1582
1582
  export { validateImage } from "./providers/storage-firebase/client.js";
1583
- // ./features/about/index
1584
- // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
1585
- // AboutView - Component for about view.
1586
- export { AboutView } from "./features/about/components/AboutView.js";
1587
1583
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
1588
1584
  // FAQPageView - Component for faqpage view.
1589
1585
  export { FAQPageView } from "./features/about/components/FAQPageView.js";
package/dist/styles.css CHANGED
@@ -7444,6 +7444,26 @@
7444
7444
  color: var(--appkit-tabs-ink-active);
7445
7445
  }
7446
7446
 
7447
+ /*
7448
+ * Wrapping tab bar — `<TabsList wrap>`.
7449
+ *
7450
+ * 🛑 For CONTENT tabs inside a page section, never for navigation. The
7451
+ * default list scrolls horizontally and collapses into a dropdown once it
7452
+ * overflows, which is right for a nav bar on a phone and wrong in the middle
7453
+ * of a homepage section: there it renders as a stray form control, and the
7454
+ * visitor cannot see what the section contains without opening it.
7455
+ *
7456
+ * Wrapping keeps every tab visible on a second line. `overflow: visible` is
7457
+ * required alongside `flex-wrap` — the base rule's `overflow-x: auto` would
7458
+ * otherwise still clip the wrapped rows and reintroduce a scrollbar.
7459
+ */
7460
+ .appkit-tabs-list--wrap {
7461
+ flex-wrap: wrap;
7462
+ overflow-x: visible;
7463
+ overflow-y: visible;
7464
+ scrollbar-width: auto;
7465
+ }
7466
+
7447
7467
  /* Radio — toggle and classic radio group variants */
7448
7468
 
7449
7469
  .appkit-radio-group {
@@ -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 collapsed = useRowCollapse(shellRef, rowRef, signature);
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.69.1",
3
+ "version": "4.70.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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;
@@ -1,14 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Anchor } from "../../../ui/components/Anchor.js";
3
- import { Avatar } from "../../../ui/components/Avatar.js";
4
- import { Badge } from "../../../ui/components/Badge.js";
5
- import { Card } from "../../../ui/components/Card.js";
6
- import { Div } from "../../../ui/components/Div.js";
7
- import { Heading, Span, Text } from "../../../ui/components/Typography.js";
8
- import { Row, Stack } from "../../../ui/components/Layout.js";
9
- import { Section } from "../../../ui/components/Semantic.js";
10
- import { TextLink } from "../../../ui/components/TextLink.js";
11
- import { ROUTES } from "../../../constants/index.js";
12
- export function AboutView({ labels = {}, howItems = [], valueItems = [], milestones = [], teamMembers = [], ctaBannerClass = "", renderCtaButtons, }) {
13
- return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "y-4xl", children: _jsxs(Div, { paddingX: "x-page", className: "max-w-4xl mx-auto text-center", children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-6", mdSize: "5xl", size: "4xl", weight: "bold", children: labels.title }), _jsx(Text, { color: "inverse-muted", variant: "none", className: "max-w-2xl mx-auto", size: "xl", children: labels.subtitle })] }) }), _jsxs(Stack, { gap: "section", paddingX: "x-page", className: "max-w-5xl mx-auto", padding: "y-4xl", children: [_jsxs(Section, { className: "text-center max-w-3xl mx-auto", children: [_jsx(Heading, { level: 2, className: "mb-4", children: labels.missionTitle }), _jsx(Text, { size: "lg", variant: "secondary", className: "leading-relaxed", children: labels.missionText })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-12", align: "center", children: labels.howItWorksTitle }), _jsx(Div, { className: "grid md:grid-cols-3", gap: "6", children: howItems.map(({ title, text, icon, tone }) => (_jsxs(Card, { variant: `gradient-${tone ?? "indigo"}`, padding: "md", spacing: "sm", children: [_jsx(Div, { children: _jsx(Span, { size: "4xl", children: icon }) }), _jsx(Heading, { level: 3, color: "inverse", variant: "none", children: title }), _jsx(Text, { size: "sm", color: "inverse-muted", variant: "none", className: "leading-relaxed", children: text })] }, title))) })] }), _jsxs(Section, { children: [_jsx(Heading, { level: 2, className: labels.valuesSubtitle ? "mb-3" : "mb-12", align: "center", children: labels.valuesTitle }), labels.valuesSubtitle && (_jsx(Text, { size: "base", variant: "secondary", align: "center", className: "mb-12 max-w-2xl mx-auto leading-relaxed", children: labels.valuesSubtitle })), _jsx(Div, { className: "grid md:grid-cols-3", gap: "5", children: valueItems.map(({ title, text, icon, detail }) => (_jsxs(Stack, { gap: "3", surface: "default", padding: "lg", rounded: "xl", border: "default", children: [_jsx(Div, { children: _jsx(Span, { size: "3xl", children: icon }) }), _jsx(Heading, { level: 3, children: title }), _jsx(Text, { size: "sm", variant: "secondary", className: "leading-relaxed", children: text }), detail && (_jsx(Text, { size: "sm", color: "muted", className: "leading-relaxed", children: detail }))] }, title))) }), labels.valuesLinkLabel && (_jsx(Div, { className: "mt-8 text-center", children: _jsx(TextLink, { href: String(ROUTES.PUBLIC.ETHICS), size: "sm", children: labels.valuesLinkLabel }) }))] }), _jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-10", align: "center", children: labels.milestonesTitle }), _jsx(Stack, { className: "relative border-l-2 border-primary/30 max-w-2xl mx-auto pl-[2rem]", gap: "xl", children: milestones.map(({ year, text }) => (_jsxs(Div, { className: "relative", children: [_jsx(Div, { className: "absolute -left-10 top-1 w-4 h-4 bg-primary border-2 border-white", rounded: "full" }), _jsx(Span, { size: "xs", weight: "bold", className: "text-primary tracking-wide", transform: "uppercase", children: year }), _jsx(Text, { className: "mt-1", children: text })] }, year))) })] }), teamMembers.length > 0 && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-2", align: "center", children: labels.teamTitle }), labels.teamSubtitle && (_jsx(Text, { size: "lg", variant: "secondary", align: "center", className: "mb-12 max-w-2xl mx-auto", children: labels.teamSubtitle })), _jsx(Div, { className: "grid md:grid-cols-3", gap: "6", children: teamMembers.map((member) => (_jsxs(Stack, { gap: "3", surface: "default", padding: "lg", rounded: "xl", border: "default", align: "center", className: "text-center", children: [_jsx(Avatar, { src: member.photoUrl, name: member.name, alt: member.name, size: "lg" }), _jsx(Heading, { level: 3, children: member.name }), _jsxs(Row, { gap: "xs", wrap: true, justify: "center", children: [_jsx(Span, { size: "xs", weight: "bold", className: "text-primary tracking-wide", transform: "uppercase", children: member.role }), member.isFounder && _jsx(Badge, { variant: "primary", size: "xs", children: "Founder" }), member.isDeveloper && _jsx(Badge, { variant: "secondary", size: "xs", children: "Developer" })] }), _jsx(Text, { size: "sm", variant: "secondary", className: "leading-relaxed", children: member.bio }), member.githubUrl && (_jsx(Anchor, { href: member.githubUrl, tone: "muted", size: "sm", underline: "hover", children: "GitHub \u2197" }))] }, member.name))) })] })), _jsxs(Section, { color: "inverse", tone: "accent-banner", className: `text-center ${ctaBannerClass}`, padding: "xl", rounded: "2xl", children: [_jsx(Heading, { color: "inverse", level: 2, variant: "none", className: "mb-8", size: "3xl", weight: "bold", children: labels.ctaTitle }), renderCtaButtons ? (renderCtaButtons()) : (_jsx(Row, { justify: "center", gap: "3", wrap: true, children: _jsxs(Span, { color: "inverse-muted", size: "sm", children: [labels.ctaSell, " \u00B7 ", labels.ctaShop] }) }))] })] })] }));
14
- }