@mohasinac/appkit 4.30.16 → 4.30.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +27 -2
- package/dist/_internal/shared/types/action-error-reporter.d.ts +40 -0
- package/dist/_internal/shared/types/action-error-reporter.js +27 -0
- package/dist/_internal/shared/types/action-result.d.ts +10 -0
- package/dist/_internal/shared/types/action-result.js +26 -0
- package/dist/client/api/surface-error.d.ts +6 -4
- package/dist/client/api/surface-error.js +19 -8
- package/dist/contracts/realtime-session.d.ts +27 -0
- package/dist/contracts/realtime-session.js +118 -0
- package/dist/core/unit-of-work.js +21 -13
- package/dist/errors/base-error.js +14 -1
- package/dist/errors/database-error.js +11 -1
- package/dist/errors/describe-cause.d.ts +5 -0
- package/dist/errors/describe-cause.js +76 -0
- package/dist/errors/error-display-map.d.ts +32 -1
- package/dist/errors/error-display-map.js +46 -0
- package/dist/errors/error-mapping.d.ts +47 -7
- package/dist/errors/error-mapping.js +46 -5
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +4 -0
- package/dist/features/about/components/PublicProfileView.js +4 -4
- package/dist/features/account/components/AddressBook.js +2 -2
- package/dist/features/account/components/AddressesIndexListing.js +2 -2
- package/dist/features/admin/components/AdminAnalyticsView.js +2 -2
- package/dist/features/admin/components/AdminCouponsView.js +2 -2
- package/dist/features/admin/components/AdminGuideHubView.js +2 -2
- package/dist/features/admin/components/AdminMediaView.js +2 -2
- package/dist/features/admin/components/AdminViewCards.js +2 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +1 -1
- package/dist/features/auctions/components/MarketplaceAuctionGrid.d.ts +1 -2
- package/dist/features/auctions/components/MarketplaceAuctionGrid.js +8 -7
- package/dist/features/auctions/components/PlaceBidFormClient.js +32 -15
- package/dist/features/before-after/components/BeforeAfterGallery.js +2 -2
- package/dist/features/blog/components/BlogIndexListing.js +3 -3
- package/dist/features/blog/components/BlogListView.js +3 -3
- package/dist/features/blog/components/BlogPostView.js +2 -2
- package/dist/features/categories/components/BundleCollage.js +2 -2
- package/dist/features/categories/components/CategoriesIndexListing.js +3 -3
- package/dist/features/categories/components/CategoryBundlesListing.js +2 -2
- package/dist/features/categories/components/CategoryGrid.js +2 -2
- package/dist/features/categories/components/CategoryProductsListing.js +2 -2
- package/dist/features/categories/components/CategoryStoresListing.js +2 -2
- package/dist/features/collections/components/CollectionCard.js +2 -2
- package/dist/features/events/components/EventRafflesSection.js +2 -2
- package/dist/features/events/components/EventsIndexListing.js +2 -2
- package/dist/features/events/components/EventsListView.js +3 -3
- package/dist/features/homepage/components/FeaturedBundlesSection.js +2 -2
- package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
- package/dist/features/homepage/components/HomepageSkeleton.js +1 -1
- package/dist/features/homepage/components/SecurityHighlightsSection.js +1 -1
- package/dist/features/homepage/components/SocialFeedSection.js +2 -2
- package/dist/features/homepage/components/StatsCounterSection.js +1 -1
- package/dist/features/homepage/components/TrustBadges.js +2 -2
- package/dist/features/homepage/components/TrustFeaturesSection.js +1 -1
- package/dist/features/messages/hooks/useConversation.js +39 -15
- package/dist/features/messages/hooks/useConversations.js +36 -14
- package/dist/features/messages/repository/conversations.repository.d.ts +23 -0
- package/dist/features/messages/repository/conversations.repository.js +53 -0
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -3
- package/dist/features/products/components/AuctionsIndexListing.js +2 -3
- package/dist/features/products/components/PrizeDrawCollage.js +2 -2
- package/dist/features/products/components/PrizeDrawsIndexListing.js +2 -3
- package/dist/features/products/components/PrizeDrawsSection.js +2 -2
- package/dist/features/products/components/ProductGrid.js +2 -7
- package/dist/features/products/components/ProductsIndexListing.js +3 -3
- package/dist/features/products/components/RelatedProducts.js +2 -2
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/reviews/components/ReviewDetailPageView.js +2 -2
- package/dist/features/reviews/components/ReviewsListingPanel.js +2 -2
- package/dist/features/seller/components/SellerCouponsView.js +2 -2
- package/dist/features/seller/components/SellerDashboardView.js +3 -3
- package/dist/features/server-errors/schemas/firestore.d.ts +9 -0
- package/dist/features/stores/components/StoreAuctionsListing.js +2 -3
- package/dist/features/stores/components/StoreClassifiedsListing.js +2 -3
- package/dist/features/stores/components/StoreDigitalCodesListing.js +2 -3
- package/dist/features/stores/components/StoreGuideHubView.js +2 -2
- package/dist/features/stores/components/StoreLiveItemsListing.js +2 -3
- package/dist/features/stores/components/StorePreOrdersListing.js +2 -3
- package/dist/features/stores/components/StoreProductsListing.js +2 -2
- package/dist/features/stores/components/StoresIndexListing.js +3 -3
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +54 -1
- package/dist/next/api/routeHandler.js +27 -7
- package/dist/providers/db-firebase/base.repository.js +34 -18
- package/dist/react/hooks/useRealtimeEvent.js +22 -4
- package/dist/server.d.ts +2 -0
- package/dist/server.js +5 -0
- package/dist/styles.css +84 -20
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/tokens.css +7 -2
- package/dist/ui/components/Layout.d.ts +31 -18
- package/dist/ui/components/Layout.js +30 -19
- package/dist/ui/components/Layout.style.css +76 -17
- package/dist/ui/components/ListingLayout.d.ts +2 -0
- package/dist/ui/components/ListingLayout.js +16 -2
- package/dist/ui/components/StickyToolbar.js +3 -48
- package/dist/ui/components/toolbar-collapse.d.ts +44 -0
- package/dist/ui/components/toolbar-collapse.js +93 -0
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { BeforeAfterSlider } from "./BeforeAfterSlider";
|
|
3
|
-
import { Div, Stack, Text } from "../../../ui";
|
|
3
|
+
import { Div, Grid, Stack, Text } from "../../../ui";
|
|
4
4
|
export function BeforeAfterGallery({ items, className = "", }) {
|
|
5
5
|
if (items.length === 0)
|
|
6
6
|
return null;
|
|
7
|
-
return (_jsx(
|
|
7
|
+
return (_jsx(Grid, { cols: "cards", gap: "lg", className: className, children: items.map((item) => (_jsxs(Stack, { gap: "sm", children: [_jsx(BeforeAfterSlider, { item: item }), (item.title || item.description) && (_jsxs(Div, { className: "text-center", children: [item.title && (_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: item.title })), item.description && (_jsx(Text, { className: "mt-0.5", color: "muted", size: "xs", children: item.description }))] }))] }, item.id))) }));
|
|
8
8
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useCallback, useMemo } from "react";
|
|
4
4
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
5
5
|
import { useBlogPosts } from "../hooks/useBlog";
|
|
6
|
-
import { Div, ListingFilterDrawer, ListingToolbar, Pagination, Row, Stack, Text, StickyToolbar } from "../../../ui";
|
|
6
|
+
import { Div, Grid, ListingFilterDrawer, ListingToolbar, Pagination, Row, Stack, Text, StickyToolbar } from "../../../ui";
|
|
7
7
|
import { ROUTES } from "../../../next";
|
|
8
8
|
import { BlogCard } from "./BlogListView";
|
|
9
9
|
import { BlogFilters, BLOG_PUBLIC_SORT_OPTIONS } from "./BlogFilters";
|
|
@@ -19,7 +19,7 @@ const __O = {
|
|
|
19
19
|
function renderBlogGrid(props) {
|
|
20
20
|
const { isLoading, posts, view } = props;
|
|
21
21
|
if (isLoading) {
|
|
22
|
-
return (_jsx(
|
|
22
|
+
return (_jsx(Grid, { cols: "cards", gap: "lg", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-video", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p5}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-1/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-2/3", surface: "subtle", rounded: "default" })] })] }, i))) }));
|
|
23
23
|
}
|
|
24
24
|
if (posts.length === 0) {
|
|
25
25
|
return _jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "No posts found." });
|
|
@@ -27,7 +27,7 @@ function renderBlogGrid(props) {
|
|
|
27
27
|
if (view === "list") {
|
|
28
28
|
return (_jsx(Stack, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", border: "subtle", rounded: "xl", children: posts.map((post) => _jsx(BlogCard, { post: post, href: String(ROUTES.BLOG.ARTICLE(post.slug)) }, post.id)) }));
|
|
29
29
|
}
|
|
30
|
-
return (_jsx(
|
|
30
|
+
return (_jsx(Grid, { cols: "cards", gap: "lg", children: posts.map((post) => _jsx(BlogCard, { post: post, href: String(ROUTES.BLOG.ARTICLE(post.slug)) }, post.id)) }));
|
|
31
31
|
}
|
|
32
32
|
function renderBlogFilterDrawer(props) {
|
|
33
33
|
const { filterOpen, setFilterOpen, activeFilterCount, clearFilters, applyFilters, pendingTable } = props;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import Link from "next/link";
|
|
4
|
-
import { Article, Button, Div, Heading, Pagination, Row, Span, Stack, Text } from "../../../ui";
|
|
4
|
+
import { Article, Button, Div, Grid, Heading, Pagination, Row, Span, Stack, Text } from "../../../ui";
|
|
5
5
|
import { MediaImage } from "../../media/MediaImage";
|
|
6
6
|
import { getDefaultLocale } from "../../../core/baseline-resolver";
|
|
7
7
|
import { getMediaUrl } from "../../media/types/index";
|
|
@@ -37,7 +37,7 @@ export function BlogCategoryTabs({ categories, active, onSelect, labels = {}, })
|
|
|
37
37
|
}
|
|
38
38
|
export function BlogListView({ posts, isLoading, totalPages = 1, currentPage = 1, total = 0, onPageChange, onPostClick, emptyLabel = "No posts found", slots, }) {
|
|
39
39
|
if (isLoading) {
|
|
40
|
-
return (_jsx(
|
|
40
|
+
return (_jsx(Grid, { cols: "cards", gap: "lg", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: "animate-pulse overflow-hidden border border-neutral-200 bg-[var(--appkit-color-surface)]", rounded: "xl", children: [_jsx(Div, { className: "aspect-video bg-neutral-200" }), _jsxs(Stack, { className: `${__P.p5}`, gap: "sm", children: [_jsx(Div, { className: "h-4 w-16 bg-neutral-200", rounded: "default" }), _jsx(Div, { className: "h-5 w-full bg-neutral-200", rounded: "default" }), _jsx(Div, { className: "h-4 w-3/4 bg-neutral-200", rounded: "default" })] })] }, i))) }));
|
|
41
41
|
}
|
|
42
42
|
if (posts.length === 0) {
|
|
43
43
|
if (slots?.renderEmptyState) {
|
|
@@ -47,7 +47,7 @@ export function BlogListView({ posts, isLoading, totalPages = 1, currentPage = 1
|
|
|
47
47
|
}
|
|
48
48
|
return (_jsxs(Stack, { gap: "xl", children: [slots?.renderHeader
|
|
49
49
|
? slots.renderHeader({ total })
|
|
50
|
-
: null, _jsx(
|
|
50
|
+
: null, _jsx(Grid, { cols: "cards", gap: "lg", children: posts.map((post, i) => slots?.renderCard ? (_jsx(React.Fragment, { children: slots.renderCard(post, i) }, post.id)) : (_jsx(BlogCard, { post: post, onClick: onPostClick }, post.id))) }), slots?.renderFooter ? slots.renderFooter({
|
|
51
51
|
page: currentPage,
|
|
52
52
|
totalPages,
|
|
53
53
|
}) : totalPages > 1 && onPageChange ? (_jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: onPageChange }) })) : null] }));
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
|
-
import { Div, Heading, RichText, Row, Scrim, Span, Stack, Text } from "../../../ui";
|
|
5
|
+
import { Div, Grid, Heading, RichText, Row, Scrim, Span, Stack, Text } from "../../../ui";
|
|
6
6
|
import { Iframe } from "../../../ui/components/Iframe";
|
|
7
7
|
import { MediaImage } from "../../media/MediaImage";
|
|
8
8
|
import { useBlogPost } from "../hooks/useBlog";
|
|
@@ -31,7 +31,7 @@ function renderBlogPostHeader(post, date, labels) {
|
|
|
31
31
|
function renderBlogPostRelated(related, title, renderRelatedCard) {
|
|
32
32
|
if (related.length === 0)
|
|
33
33
|
return null;
|
|
34
|
-
return (_jsxs(Div, { children: [_jsx(Heading, { level: 2, className: "mb-6", size: "xl", weight: "semibold", children: title }), _jsx(
|
|
34
|
+
return (_jsxs(Div, { children: [_jsx(Heading, { level: 2, className: "mb-6", size: "xl", weight: "semibold", children: title }), _jsx(Grid, { cols: "cards", gap: "lg", children: related.map((rel, i) => renderRelatedCard ? (_jsx(React.Fragment, { children: renderRelatedCard(rel, i) }, rel.id)) : (_jsx(BlogCard, { post: rel, href: String(ROUTES.BLOG.ARTICLE(rel.slug)) }, rel.id))) })] }));
|
|
35
35
|
}
|
|
36
36
|
export function BlogPostView({ slug, initialData, labels = {}, renderImage, renderContent, renderBackButton, renderLoading, renderError, renderRelatedCard, renderAuthorBio, className = "", }) {
|
|
37
37
|
const { post, related, relatedByTags, relatedByAuthor, isLoading, error } = useBlogPost(slug, {
|
|
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
10
10
|
*/
|
|
11
11
|
import { useState } from "react";
|
|
12
12
|
import Link from "next/link";
|
|
13
|
-
import { Button, Div, Row, Text } from "../../../ui";
|
|
13
|
+
import { Button, Div, Grid, Row, Text } from "../../../ui";
|
|
14
14
|
import { MediaImage } from "../../media/MediaImage";
|
|
15
15
|
import { ImageLightbox } from "../../../ui/components/ImageLightbox";
|
|
16
16
|
import { ROUTES } from "../../../next/routing/route-map";
|
|
@@ -40,7 +40,7 @@ export function BundleCollage({ members, onItemClick }) {
|
|
|
40
40
|
if (!members.length)
|
|
41
41
|
return null;
|
|
42
42
|
const galleryImages = toGalleryImages(members);
|
|
43
|
-
return (_jsxs(_Fragment, { children: [_jsx(
|
|
43
|
+
return (_jsxs(_Fragment, { children: [_jsx(Grid, { cols: "categoryCards", gap: "3", children: members.map((p, idx) => {
|
|
44
44
|
const cover = p.mainImage ?? p.images?.[0];
|
|
45
45
|
const href = String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#");
|
|
46
46
|
return (_jsxs(Div, { layout: "flex-col", className: "group relative overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] transition-transform hover:scale-[1.01]", rounded: "lg", children: [_jsx(Button, { variant: "ghost", type: "button", onClick: makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex), paddingX: "none", paddingY: "none", rounded: "none", className: "relative aspect-square w-full overflow-hidden focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)]", "aria-label": `View ${p.title} in lightbox`, children: _jsxs(Div, { className: "relative aspect-square w-full", children: [cover ? (_jsx(MediaImage, { src: cover, alt: p.title, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", surface: "muted", className: "absolute inset-0", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), _jsxs(Div, { textWeight: "semibold", textSize: "xs", surface: "overlay-xl", padding: "chip-2xs", className: "absolute left-2 top-2 text-white", rounded: "default", children: ["#", idx + 1] })] }) }), _jsxs(Div, { padding: "xs", children: [_jsx(Link, { href: href, className: "line-clamp-2 text-[length:var(--appkit-text-sm)] font-medium hover:underline", children: p.title }), _jsx(Text, { className: "mt-0.5 text-[var(--appkit-color-text-muted)]", size: "xs", children: formatCurrency(p.price ?? 0, p.currency ?? "INR") })] })] }, p.id));
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useCallback, useMemo } from "react";
|
|
4
4
|
import { useCategoriesFiltered } from "../hooks/useCategories";
|
|
5
5
|
import { ROUTES } from "../../../next";
|
|
6
|
-
import { Button, Div, ListingFilterDrawer, ListingToolbar, Pagination, Row, Stack, Text, StickyToolbar } from "../../../ui";
|
|
6
|
+
import { Button, Div, Grid, ListingFilterDrawer, ListingToolbar, Pagination, Row, Stack, Text, StickyToolbar } from "../../../ui";
|
|
7
7
|
import { CategoryCard } from "./CategoryGrid";
|
|
8
8
|
import { CategoryFilters } from "./CategoryFilters";
|
|
9
9
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
@@ -21,7 +21,7 @@ function renderCategoryGrid(props) {
|
|
|
21
21
|
const { isLoading, categories, view, activeSearch, activeTab, brandsOnly } = props;
|
|
22
22
|
const isBrandView = activeTab === "brands" || brandsOnly;
|
|
23
23
|
if (isLoading) {
|
|
24
|
-
return (_jsx(
|
|
24
|
+
return (_jsx(Grid, { cols: "categoryCards", gap: "md", children: Array.from({ length: 10 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-[4/3]", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}.5`, gap: "sm", children: [_jsx(Div, { className: "h-3.5 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-full", surface: "subtle", rounded: "default" })] })] }, i))) }));
|
|
25
25
|
}
|
|
26
26
|
if (categories.length === 0) {
|
|
27
27
|
return (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: activeSearch ? `No ${isBrandView ? "brands" : "categories"} matching "${activeSearch}"` : isBrandView ? "No brands found" : "No categories found" }));
|
|
@@ -29,7 +29,7 @@ function renderCategoryGrid(props) {
|
|
|
29
29
|
if (view === "list") {
|
|
30
30
|
return (_jsx(Stack, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", border: "subtle", rounded: "xl", children: categories.map((category) => _jsx(CategoryCard, { category: category, href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug)) }, category.id)) }));
|
|
31
31
|
}
|
|
32
|
-
return (_jsx(
|
|
32
|
+
return (_jsx(Grid, { cols: "categoryCards", gap: "md", children: categories.map((category) => _jsx(CategoryCard, { category: category, href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug)) }, category.id)) }));
|
|
33
33
|
}
|
|
34
34
|
function renderCategoryFilterDrawer(props) {
|
|
35
35
|
const { filterOpen, setFilterOpen, activeFilterCount, clearFilters, applyFilters, pendingTable } = props;
|
|
@@ -10,7 +10,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
10
10
|
*/
|
|
11
11
|
import { Row, Stack, sortBy } from "@mohasinac/appkit/client";
|
|
12
12
|
import { useMemo, useCallback, useState } from "react";
|
|
13
|
-
import { Div, Label, Span, Text, Toggle, StickyToolbar } from "../../../ui";
|
|
13
|
+
import { Div, Grid, Label, Span, Text, Toggle, StickyToolbar } from "../../../ui";
|
|
14
14
|
import { ListingToolbar, Pagination, FilterDrawer } from "../../../ui";
|
|
15
15
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
16
16
|
import { BundleBuyNowCta } from "./BundleBuyNowCta";
|
|
@@ -93,5 +93,5 @@ export function CategoryBundlesListing({ initialBundles, brandName, onBuyNow, })
|
|
|
93
93
|
// Apply → close. <FilterDrawer> below stays available for parity
|
|
94
94
|
// with every other listing page's filter affordance.
|
|
95
95
|
{ label: "Show out-of-stock", active: showOutOfStock, onChange: (next) => table.setMany({ showOutOfStock: next ? "true" : "", page: "1" }) },
|
|
96
|
-
] }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: safePage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: pageItems.length === 0 ? (_jsxs(Text, { paddingY: "4xl", color: "muted", size: "sm", align: "start", children: ["No bundles match your search", brandName ? ` for ${brandName}` : "", "."] })) : (_jsx(
|
|
96
|
+
] }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: safePage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: pageItems.length === 0 ? (_jsxs(Text, { paddingY: "4xl", color: "muted", size: "sm", align: "start", children: ["No bundles match your search", brandName ? ` for ${brandName}` : "", "."] })) : (_jsx(Grid, { cols: "cards", gap: "lg", children: pageItems.map((bundle) => (_jsxs(Stack, { children: [_jsx(MarketplaceBundleCard, { bundle: bundle }), onBuyNow && (_jsx(Div, { border: "default", surface: "default", className: "-mt-px border-t border-t-0 border-[var(--appkit-color-border)] pt-[0.5rem] pb-[0.75rem]", padding: "x-sm", roundedBottom: "lg", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: bundle.bundleStockStatus === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }, bundle.id))) })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: () => setPendingShowOutOfStock(false), activeCount: filterActiveCount, hideTrigger: true, children: _jsx(Stack, { className: `${__P.p4}`, gap: "md", children: _jsxs(Label, { layout: "flex", gap: "lg", className: "justify-[space-between] cursor-pointer", children: [_jsx(Span, { size: "sm", weight: "medium", color: "muted", children: "Show out-of-stock bundles" }), _jsx(Toggle, { checked: pendingShowOutOfStock, onChange: setPendingShowOutOfStock, size: "sm" })] }) }) })] }));
|
|
97
97
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { ArrowRight } from "lucide-react";
|
|
4
|
-
import { Div, Row, Span, Stack, Text } from "../../../ui";
|
|
4
|
+
import { Div, Grid, Row, Span, Stack, Text } from "../../../ui";
|
|
5
5
|
import { DynamicBgDiv } from "../../../ui/components/DynamicBgDiv";
|
|
6
6
|
import { MediaImage } from "../../media/MediaImage";
|
|
7
7
|
const __P = {
|
|
@@ -30,5 +30,5 @@ export function CategoryGrid({ categories, getHref, onCategoryClick, emptyLabel
|
|
|
30
30
|
if (categories.length === 0) {
|
|
31
31
|
return (_jsx(Text, { paddingY: "3xl", className: "text-[var(--appkit-color-text-muted)]", size: "sm", align: "start", children: emptyLabel }));
|
|
32
32
|
}
|
|
33
|
-
return (_jsx(
|
|
33
|
+
return (_jsx(Grid, { cols: "categoryCards", gap: "md", className: className, children: categories.map((cat) => (_jsx(CategoryCard, { category: cat, href: getHref ? getHref(cat) : undefined, onClick: !getHref ? onCategoryClick : undefined }, cat.id))) }));
|
|
34
34
|
}
|
|
@@ -4,7 +4,7 @@ import { useState, useCallback } from "react";
|
|
|
4
4
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
5
5
|
import { usePendingTable } from "../../../react/hooks/usePendingTable";
|
|
6
6
|
import { useProducts } from "../../products/hooks/useProducts";
|
|
7
|
-
import { Div, FilterDrawer, ListingToolbar, LoginRequiredModal, Pagination, Row, Stack, useToast, StickyToolbar } from "../../../ui";
|
|
7
|
+
import { Div, Grid, FilterDrawer, ListingToolbar, LoginRequiredModal, Pagination, Row, Stack, useToast, StickyToolbar } from "../../../ui";
|
|
8
8
|
import { useAuthGate } from "../../../react/hooks/useAuthGate";
|
|
9
9
|
import { ACTION_ID } from "../../products/constants/action-defs";
|
|
10
10
|
import { ROUTES } from "../../../next";
|
|
@@ -26,7 +26,7 @@ const FILTER_KEYS = ["condition", "brand", "minPrice", "maxPrice"];
|
|
|
26
26
|
function renderCategoryProductGrid(props) {
|
|
27
27
|
const { isLoading, products, view, getProductHref, onWishlistToggle, wishlistedIds, onAddToCart } = props;
|
|
28
28
|
if (isLoading) {
|
|
29
|
-
return (_jsx(
|
|
29
|
+
return (_jsx(Grid, { cols: "cards", gap: "lg", children: Array.from({ length: 8 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-1/3", surface: "subtle", rounded: "default" })] })] }, i))) }));
|
|
30
30
|
}
|
|
31
31
|
return (_jsx(ProductGrid, { products: products, getProductHref: getProductHref, view: view, emptyLabel: "No products found in this category.", onWishlistToggle: onWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: onAddToCart }));
|
|
32
32
|
}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { InteractiveStoreCard } from "../../stores/components/InteractiveStoreCard";
|
|
4
4
|
import { ROUTES } from "../../../next";
|
|
5
|
-
import {
|
|
5
|
+
import { Grid, Stack, Text } from "../../../ui";
|
|
6
6
|
export function CategoryStoresListing({ stores }) {
|
|
7
7
|
if (stores.length === 0) {
|
|
8
8
|
return (_jsx(Stack, { justify: "center", className: "text-left", padding: "y-4xl", align: "start", children: _jsx(Text, { size: "sm", color: "muted", children: "No stores found in this category." }) }));
|
|
9
9
|
}
|
|
10
|
-
return (_jsx(
|
|
10
|
+
return (_jsx(Grid, { cols: "cards", gap: "lg", children: stores.map((store) => (_jsx(InteractiveStoreCard, { store: store, href: String(ROUTES.PUBLIC.STORE_DETAIL(store.storeSlug)) }, store.id))) }));
|
|
11
11
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Div, Heading, Text, TextLink } from "../../../ui";
|
|
2
|
+
import { Div, Grid, Heading, Text, TextLink } from "../../../ui";
|
|
3
3
|
import { MediaImage } from "../../media/MediaImage";
|
|
4
4
|
import { LAYOUT } from "../../../tokens";
|
|
5
5
|
const CLS_PLACEHOLDER = "w-full";
|
|
@@ -7,5 +7,5 @@ export function CollectionCard({ collection, href }) {
|
|
|
7
7
|
return (_jsxs(TextLink, { rounded: "xl", href: href, shadow: "hover-lg", className: "group relative block overflow-hidden bg-[var(--appkit-color-surface)] dark:bg-[var(--appkit-color-surface-elevated)] transition", children: [collection.image ? (_jsx(Div, { className: `relative ${LAYOUT.cardHeight.md} w-full`, overflow: "hidden", children: _jsx(MediaImage, { src: collection.image, alt: collection.title, size: "card", className: "transition-transform duration-300 group-hover:scale-105" }) })) : (_jsx(Div, { surface: "muted", className: `${LAYOUT.cardHeight.md} ${CLS_PLACEHOLDER}` })), _jsxs(Div, { surface: "default", padding: "sm", children: [_jsx(Heading, { level: 3, className: "text-[var(--appkit-color-text)]", weight: "semibold", children: collection.title }), collection.subtitle && (_jsx(Text, { className: "mt-1 text-[var(--appkit-color-text-muted)]", size: "sm", children: collection.subtitle })), collection.productCount !== undefined && (_jsxs(Text, { className: "mt-2 text-gray-400", size: "xs", children: [collection.productCount, " ", collection.productCount === 1 ? "item" : "items"] }))] })] }));
|
|
8
8
|
}
|
|
9
9
|
export function CollectionGrid({ collections, getHref }) {
|
|
10
|
-
return (_jsx(
|
|
10
|
+
return (_jsx(Grid, { cols: "cards", gap: "lg", children: collections.map((c) => (_jsx(CollectionCard, { collection: c, href: getHref(c.slug) }, c.slug))) }));
|
|
11
11
|
}
|
|
@@ -8,7 +8,7 @@ import { serverLogger } from "../../../monitoring/server-logger";
|
|
|
8
8
|
// this section silently rendered nothing instead of failing loudly.
|
|
9
9
|
import { sieveFilter, sieveAnd, SIEVE_OP } from "../../../utils/sieve-builder";
|
|
10
10
|
import { sortBy } from "../../../constants/sort";
|
|
11
|
-
import { Container,
|
|
11
|
+
import { Container, Grid, Heading, Section, Stack, Text } from "../../../ui";
|
|
12
12
|
import { eventRepository } from "../repository/events.repository";
|
|
13
13
|
import { EventCard } from "./EventCard";
|
|
14
14
|
/**
|
|
@@ -40,5 +40,5 @@ export async function EventRafflesSection({ config, }) {
|
|
|
40
40
|
}
|
|
41
41
|
if (events.length === 0)
|
|
42
42
|
return null;
|
|
43
|
-
return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), subtitle ? (_jsx(Text, { size: "sm", color: "muted", children: subtitle })) : null] }), _jsx(
|
|
43
|
+
return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), subtitle ? (_jsx(Text, { size: "sm", color: "muted", children: subtitle })) : null] }), _jsx(Grid, { cols: "cardsWide", gap: "3", children: events.map((event) => (_jsx(EventCard, { event: event }, event.id))) })] }) }) }));
|
|
44
44
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useCallback, useMemo } from "react";
|
|
4
4
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
5
5
|
import { useEvents } from "../hooks/useEvents";
|
|
6
|
-
import { Div, ListingFilterDrawer, ListingToolbar, Pagination, Row, Stack, Text, StickyToolbar } from "../../../ui";
|
|
6
|
+
import { Div, Grid, ListingFilterDrawer, ListingToolbar, Pagination, Row, Stack, Text, StickyToolbar } from "../../../ui";
|
|
7
7
|
import { EventCard } from "./EventCard";
|
|
8
8
|
import { EventFilters, EVENT_PUBLIC_SORT_OPTIONS } from "./EventFilters";
|
|
9
9
|
import { EVENT_FIELDS } from "../../../constants/field-names";
|
|
@@ -123,5 +123,5 @@ export function EventsIndexListing({ initialData, facetCounts }) {
|
|
|
123
123
|
}, [searchInput, table]);
|
|
124
124
|
return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search events...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || sortBy(EVENT_FIELDS.STARTS_AT, "ASC"), sortOptions: EVENT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, toggles: [
|
|
125
125
|
{ label: "Show expired", active: showExpired, onChange: (next) => table.set(TABLE_KEYS.SHOW_EXPIRED, next ? "true" : "") },
|
|
126
|
-
] }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(
|
|
126
|
+
] }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Grid, { cols: "cards", gap: "lg", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-video", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p4}`, gap: "sm", children: [_jsx(Div, { className: "h-4 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-2/3", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-8 mt-2", surface: "subtle", rounded: "default" })] })] }, i))) })) : events.length === 0 ? (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "No events found." })) : view === "list" ? (_jsx(Stack, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", border: "subtle", rounded: "xl", children: events.map((event) => (_jsx(EventCard, { event: event }, event.id))) })) : (_jsx(Grid, { cols: "cards", gap: "lg", children: events.map((event) => (_jsx(EventCard, { event: event }, event.id))) })) }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(EventFilters, { table: pendingTable, variant: "public", counts: facetCounts, hideEmpty: Boolean(facetCounts) }) })] }));
|
|
127
127
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import { Div, Pagination, Row, Stack, Text } from "../../../ui";
|
|
3
|
+
import { Div, Grid, Pagination, Row, Stack, Text } from "../../../ui";
|
|
4
4
|
import { EventCard } from "./EventCard";
|
|
5
5
|
const __P = {
|
|
6
6
|
p4: "p-[var(--appkit-space-4)]",
|
|
7
7
|
};
|
|
8
8
|
export function EventsListView({ events, isLoading, totalPages = 1, currentPage = 1, total = 0, onPageChange, onParticipate, emptyLabel = "No events found", slots, }) {
|
|
9
9
|
if (isLoading) {
|
|
10
|
-
return (_jsx(
|
|
10
|
+
return (_jsx(Grid, { cols: "cards", gap: "lg", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: "animate-pulse overflow-hidden", surface: "subtle", rounded: "xl", border: "default", children: [_jsx(Div, { className: "aspect-video", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p4}`, gap: "sm", children: [_jsx(Div, { className: "h-4 w-16", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-5 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-3/4", surface: "subtle", rounded: "default" })] })] }, i))) }));
|
|
11
11
|
}
|
|
12
12
|
if (events.length === 0) {
|
|
13
13
|
if (slots?.renderEmptyState) {
|
|
@@ -17,7 +17,7 @@ export function EventsListView({ events, isLoading, totalPages = 1, currentPage
|
|
|
17
17
|
}
|
|
18
18
|
return (_jsxs(Stack, { gap: "xl", children: [slots?.renderHeader
|
|
19
19
|
? slots.renderHeader({ total })
|
|
20
|
-
: null, _jsx(
|
|
20
|
+
: null, _jsx(Grid, { cols: "cards", gap: "lg", children: events.map((event, i) => slots?.renderCard ? (_jsx(React.Fragment, { children: slots.renderCard(event, i) }, event.id)) : (_jsx(EventCard, { event: event, onParticipate: onParticipate ? (e) => onParticipate(e) : undefined }, event.id))) }), slots?.renderFooter ? slots.renderFooter({
|
|
21
21
|
page: currentPage,
|
|
22
22
|
totalPages,
|
|
23
23
|
}) : totalPages > 1 && onPageChange ? (_jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: onPageChange }) })) : null] }));
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"use client";
|
|
10
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
11
|
import Link from "next/link";
|
|
12
|
-
import { Badge, Div, Heading, Row, Section, Stack, Text } from "../../../ui";
|
|
12
|
+
import { Badge, Div, Grid, Heading, Row, Section, Stack, Text } from "../../../ui";
|
|
13
13
|
import { MediaImage } from "../../media/MediaImage";
|
|
14
14
|
import { ROUTES } from "../../../next/routing/route-map";
|
|
15
15
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
@@ -29,7 +29,7 @@ export function FeaturedBundlesSection({ title = BUNDLE_COPY.featuredDefaultTitl
|
|
|
29
29
|
const items = initialItems.filter((c) => c.categoryType === "bundle");
|
|
30
30
|
if (items.length === 0)
|
|
31
31
|
return null;
|
|
32
|
-
return (_jsx(Section, { className: `${className}`, padding: "y-2xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, reverse: hand === "left", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), description && (_jsx(Text, { size: "sm", color: "muted", children: description }))] }), _jsx(Link, { href: String(ROUTES.PUBLIC.BUNDLES), className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:underline", children: BUNDLE_COPY.featured.viewAll })] }), _jsx(
|
|
32
|
+
return (_jsx(Section, { className: `${className}`, padding: "y-2xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", wrap: true, reverse: hand === "left", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), description && (_jsx(Text, { size: "sm", color: "muted", children: description }))] }), _jsx(Link, { href: String(ROUTES.PUBLIC.BUNDLES), className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:underline", children: BUNDLE_COPY.featured.viewAll })] }), _jsx(Grid, { cols: "cards", gap: "3", children: items.map((bundle) => (_jsx(FeaturedBundleCard, { bundle: bundle, onBuyNow: onBuyNow }, bundle.id))) })] }) }));
|
|
33
33
|
}
|
|
34
34
|
function FeaturedBundleCard({ bundle, onBuyNow }) {
|
|
35
35
|
const stock = bundle.bundleStockStatus ?? "in_stock";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { normalizeError } from "../../../errors/normalize";
|
|
3
|
-
import { Anchor, Div, Heading, HandModeRow, Row, Section, Span, Stack, Text } from "../../../ui";
|
|
3
|
+
import { Anchor, Div, GRID_MAP, Heading, HandModeRow, Row, Section, Span, Stack, Text } from "../../../ui";
|
|
4
4
|
import { MediaImage } from "../../media/MediaImage";
|
|
5
5
|
import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
|
|
6
6
|
import { fetchGoogleReviews } from "../lib/google-reviews-fetcher";
|
|
@@ -65,7 +65,7 @@ export async function GoogleReviewsSection(config) {
|
|
|
65
65
|
}
|
|
66
66
|
const gridClass = layout === "carousel"
|
|
67
67
|
? "flex gap-[var(--appkit-space-4)] overflow-x-auto pb-[var(--appkit-space-2)] snap-x snap-mandatory"
|
|
68
|
-
:
|
|
68
|
+
: `${GRID_MAP.cards} gap-[var(--appkit-space-4)]`;
|
|
69
69
|
const cardClass = layout === "carousel" ? "flex-shrink-0 w-[85%] sm:w-72 snap-start" : "";
|
|
70
70
|
const mapsHref = googleMapsUrl || (placeId ? `https://search.google.com/local/reviews?placeid=${placeId}` : "");
|
|
71
71
|
return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: "w-full max-w-7xl mx-auto", padding: "x-md", children: [_jsxs(HandModeRow, { className: "mb-8", align: "end", justify: "between", gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, children: "What Our Customers Say" }), aggregateRating > 0 && (_jsxs(Row, { className: "mt-1", align: "center", gap: "sm", children: [_jsx(StarRating, { rating: Math.round(aggregateRating) }), _jsxs(Text, { size: "sm", variant: "muted", children: [aggregateRating.toFixed(1), " \u00B7 ", totalRatings.toLocaleString(), " reviews"] })] }))] }), linkToGoogleMaps && mapsHref && (_jsx(Anchor, { href: mapsHref, tone: "none", underline: "hover", className: CLS_GMAPS_LINK, children: "View on Google \u2192" }))] }), reviews.length === 0 ? (_jsx(EmptyState, {})) : (_jsx(Div, { className: gridClass, children: reviews.map((review, i) => (_jsx(Div, { className: cardClass, children: _jsx(ReviewCard, { review: review, showRating: showRating, showDate: showDate }) }, `${review.authorName}-${i}`))) }))] }) }));
|
|
@@ -17,5 +17,5 @@ export function HomepageSkeleton() {
|
|
|
17
17
|
const trustCardH = "h-[clamp(120px,18vh,170px)]";
|
|
18
18
|
const categoryTileH = "h-[clamp(112px,16vh,160px)]";
|
|
19
19
|
const newsletterH = "h-[clamp(220px,28vh,320px)]";
|
|
20
|
-
return (_jsxs(Div, { className: `w-full ${__O.hidden}`, children: [_jsx(Div, { className: `${skeleton.card} w-full ${heroSkeletonH}`, "aria-hidden": "true" }), _jsx(Section, { className: `${__P.p8}`, surface: "muted", children: _jsx(Grid, {
|
|
20
|
+
return (_jsxs(Div, { className: `w-full ${__O.hidden}`, children: [_jsx(Div, { className: `${skeleton.card} w-full ${heroSkeletonH}`, "aria-hidden": "true" }), _jsx(Section, { className: `${__P.p8}`, surface: "muted", children: _jsx(Grid, { cols: "statTiles", children: Array.from({ length: 4 }).map((_, i) => (_jsx(Stack, { className: `${skeleton.card} ${trustCardH}`, align: "center", gap: "3", padding: "lg" }, i))) }) }), _jsxs(Section, { className: `${__P.p8}`, surface: "subtle", children: [_jsx(Div, { className: `${skeleton.heading} w-48 mx-auto mb-6` }), _jsx(Grid, { cols: "statTiles", children: Array.from({ length: 6 }).map((_, i) => (_jsx(Div, { className: `${skeleton.card} ${categoryTileH}` }, i))) })] }), _jsxs(Section, { className: `${__P.p8}`, surface: "muted", children: [_jsxs(Row, { className: `${flex.between} mb-6`, children: [_jsx(Div, { className: `${skeleton.heading} w-52` }), _jsx(Div, { className: `${skeleton.text} w-24` })] }), _jsx(Grid, { cols: "cards", children: Array.from({ length: 5 }).map((_, i) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Div, { className: `${skeleton.image} w-full pb-[100%]` }), _jsx(Div, { className: `${skeleton.text} w-3/4` }), _jsx(Div, { className: `${skeleton.text} w-1/2` })] }, i))) })] }), _jsxs(Section, { className: `${__P.p8}`, surface: "subtle", children: [_jsxs(Row, { className: `${flex.between} mb-6`, children: [_jsx(Div, { className: `${skeleton.heading} w-56` }), _jsx(Div, { className: `${skeleton.text} w-24` })] }), _jsx(Grid, { cols: "cards", children: Array.from({ length: 5 }).map((_, i) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Div, { className: `${skeleton.image} w-full pb-[100%]` }), _jsx(Div, { className: `${skeleton.text} w-3/4` }), _jsx(Div, { className: `${skeleton.text} w-1/2` })] }, i))) })] }), _jsx(Section, { className: `${__P.p8}`, surface: "muted", children: _jsx(Div, { className: `${skeleton.card} max-w-2xl mx-auto ${newsletterH}`, rounded: "2xl" }) })] }));
|
|
21
21
|
}
|
|
@@ -35,5 +35,5 @@ export function SecurityHighlightsSection({ title, subtitle, pillLabel, items, l
|
|
|
35
35
|
observer.observe(el);
|
|
36
36
|
return () => observer.disconnect();
|
|
37
37
|
}, []);
|
|
38
|
-
return (_jsx(Section, { ref: sectionRef, paddingY: "y-2-5xl", className: className, surface: "muted", paddingX: "x-md", children: _jsxs(Div, { className: "max-w-6xl mx-auto", children: [_jsxs(Div, { className: "text-left mb-10", children: [pillLabel && (_jsx(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: pillLabel })), _jsx(Heading, { level: 2, className: `mt-4 ${themed.textPrimary}`, size: "3xl", mdSize: "4xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { variant: "secondary", className: "mt-2 max-w-xl", children: subtitle }))] }), _jsx(Grid, {
|
|
38
|
+
return (_jsx(Section, { ref: sectionRef, paddingY: "y-2-5xl", className: className, surface: "muted", paddingX: "x-md", children: _jsxs(Div, { className: "max-w-6xl mx-auto", children: [_jsxs(Div, { className: "text-left mb-10", children: [pillLabel && (_jsx(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: pillLabel })), _jsx(Heading, { level: 2, className: `mt-4 ${themed.textPrimary}`, size: "3xl", mdSize: "4xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { variant: "secondary", className: "mt-2 max-w-xl", children: subtitle }))] }), _jsx(Grid, { cols: "cards", gap: "xl", children: items.map((item, index) => (_jsx(SecurityCard, { item: item, visible: visible, delayClass: CARD_DELAYS[Math.min(index, CARD_DELAYS.length - 1)] }, item.key))) }), learnMoreHref && (_jsx(Div, { className: "text-left mt-8", children: _jsx(TextLink, { href: learnMoreHref, children: learnMoreLabel }) }))] }) }));
|
|
39
39
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { normalizeError } from "../../../errors/normalize";
|
|
3
|
-
import { Anchor, Div, Heading, HandModeRow, Section, Stack, Text } from "../../../ui";
|
|
3
|
+
import { Anchor, Div, GRID_MAP, Heading, HandModeRow, Section, Stack, Text } from "../../../ui";
|
|
4
4
|
import { SocialPostCard } from "./SocialPostCard";
|
|
5
5
|
import { fetchInstagramPosts, fetchFacebookPosts, fetchTikTokPosts, fetchDeviantArtPosts, } from "../lib/social-feed-fetcher";
|
|
6
6
|
import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
|
|
@@ -93,7 +93,7 @@ export async function SocialFeedSection(config) {
|
|
|
93
93
|
? "flex gap-[var(--appkit-space-3)] overflow-x-auto pb-[var(--appkit-space-2)] snap-x snap-mandatory"
|
|
94
94
|
: layout === "masonry"
|
|
95
95
|
? "columns-2 sm:columns-3 lg:columns-4 gap-[var(--appkit-space-3)] space-y-3"
|
|
96
|
-
:
|
|
96
|
+
: `${GRID_MAP.cards} gap-[var(--appkit-space-3)]`;
|
|
97
97
|
const cardClass = layout === "carousel"
|
|
98
98
|
? "flex-shrink-0 w-[85%] sm:w-56 snap-start"
|
|
99
99
|
: layout === "masonry"
|
|
@@ -31,5 +31,5 @@ export function StatsCounterSection({ stats, className = "", }) {
|
|
|
31
31
|
observer.observe(el);
|
|
32
32
|
return () => observer.disconnect();
|
|
33
33
|
}, []);
|
|
34
|
-
return (_jsx(Section, { border: "subtle", paddingY: "y-2-5xl", ref: sectionRef, className: `border-y ${className}`, paddingX: "x-md", surface: "default", children: _jsx(Div, { className: "mx-auto max-w-5xl", children: _jsx(Grid, {
|
|
34
|
+
return (_jsx(Section, { border: "subtle", paddingY: "y-2-5xl", ref: sectionRef, className: `border-y ${className}`, paddingX: "x-md", surface: "default", children: _jsx(Div, { className: "mx-auto max-w-5xl", children: _jsx(Grid, { cols: "statTiles", gap: "none", children: stats.map((stat, i) => (_jsx(StatCard, { stat: stat, visible: visible, delayClass: STAT_DELAYS[Math.min(i, STAT_DELAYS.length - 1)], isLast: i === stats.length - 1 }, stat.key))) }) }) }));
|
|
35
35
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Div, Row, Section, Stack, Text } from "../../../ui";
|
|
2
|
+
import { Div, Grid, Row, Section, Stack, Text } from "../../../ui";
|
|
3
3
|
const BADGE_ICONS = {
|
|
4
4
|
shipping: (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: "h-7 w-7", "aria-hidden": "true", children: [_jsx("path", { d: "M5 12H19" }), _jsx("path", { d: "M15 7l5 5-5 5" }), _jsx("path", { d: "M1 17V7a4 4 0 0 1 4-4h10" }), _jsx("path", { d: "M22 17a4 4 0 0 1-4 4H5" })] })),
|
|
5
5
|
support: (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: "h-7 w-7", "aria-hidden": "true", children: _jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) })),
|
|
@@ -42,5 +42,5 @@ const FALLBACK_BADGES = [
|
|
|
42
42
|
];
|
|
43
43
|
export function TrustBadges({ badges }) {
|
|
44
44
|
const items = badges && badges.length > 0 ? badges : FALLBACK_BADGES;
|
|
45
|
-
return (_jsx(Section, { className: "bg-[var(--section-bg)] [border-top:var(--section-border)] [border-bottom:var(--section-border)]", children: _jsx(Div, { className: "mx-auto max-w-7xl sm:py-[2.5rem]", paddingY: "y-lg", paddingX: "x-md", children: _jsx(
|
|
45
|
+
return (_jsx(Section, { className: "bg-[var(--section-bg)] [border-top:var(--section-border)] [border-bottom:var(--section-border)]", children: _jsx(Div, { className: "mx-auto max-w-7xl sm:py-[2.5rem]", paddingY: "y-lg", paddingX: "x-md", children: _jsx(Grid, { cols: "statTiles", gap: "lg", children: items.map((badge) => (_jsxs(Stack, { className: "text-left [flex-direction:row] items-[center] gap-[1rem]", align: "center", gap: "sm", children: [_jsx(Row, { className: "h-12 w-12 shrink-0 bg-[rgba(240,196,23,0.12)] text-[var(--color-red)] [border:2px_solid_var(--border-ink)]", align: "center", justify: "center", rounded: "full", children: BADGE_ICONS[badge.iconKey] }), _jsxs(Div, { children: [_jsx(Text, { className: "font-black uppercase tracking-wide text-[var(--section-title-color)]", size: "sm", children: badge.title }), _jsx(Text, { className: "text-[var(--color-muted)]", size: "xs", weight: "medium", children: badge.sub })] })] }, badge.id))) }) }) }));
|
|
46
46
|
}
|
|
@@ -38,5 +38,5 @@ export function TrustFeaturesSection({ items, title, variant = "cards", classNam
|
|
|
38
38
|
const doubled = [...items, ...items];
|
|
39
39
|
return (_jsx(Section, { border: "subtle", ref: sectionRef, className: `overflow-hidden border-y ${className}`, padding: "y-md", children: _jsx(Div, { layout: "flex", className: "animate-marquee", children: doubled.map((item, i) => (_jsx(TrustStripItem, { item: item }, `${item.iconName}-${i}`))) }) }));
|
|
40
40
|
}
|
|
41
|
-
return (_jsx(Section, { ref: sectionRef, paddingY: "y-2-5xl", className: className, paddingX: "x-md", surface: "muted", children: _jsxs(Div, { className: "max-w-6xl mx-auto", children: [title && (_jsx(Div, { className: "text-left mb-8", children: _jsx(Heading, { level: 2, color: "primary", size: "3xl", weight: "bold", mdSize: "4xl", children: title }) })), _jsx(Grid, {
|
|
41
|
+
return (_jsx(Section, { ref: sectionRef, paddingY: "y-2-5xl", className: className, paddingX: "x-md", surface: "muted", children: _jsxs(Div, { className: "max-w-6xl mx-auto", children: [title && (_jsx(Div, { className: "text-left mb-8", children: _jsx(Heading, { level: 2, color: "primary", size: "3xl", weight: "bold", mdSize: "4xl", children: title }) })), _jsx(Grid, { cols: "statTiles", gap: "lg", children: items.map((item, index) => (_jsx(TrustFeatureCard, { item: item, visible: visible, delayClass: CARD_DELAYS[Math.min(index, CARD_DELAYS.length - 1)] }, item.iconName))) })] }) }));
|
|
42
42
|
}
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { useCallback, useEffect, useState } from "react";
|
|
13
13
|
import { getClientRealtimeProvider } from "../../../contracts/client-realtime";
|
|
14
|
+
import { acquireRealtimeSession } from "../../../contracts/realtime-session";
|
|
14
15
|
import { conversationPingPath, userConversationsPingPath, } from "../realtime";
|
|
15
16
|
import { normalizeError } from "../../../errors/normalize";
|
|
16
17
|
import { apiClient, ApiClientError } from "../../../http/ApiClient";
|
|
17
|
-
import { CONVERSATION_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
18
|
+
import { CONVERSATION_ENDPOINTS, ADMIN_ENDPOINTS, } from "../../../constants/api-endpoints";
|
|
18
19
|
/**
|
|
19
20
|
* @deprecated Re-exported for backwards compat — import `conversationPingPath`
|
|
20
21
|
* from `@mohasinac/appkit` instead.
|
|
@@ -69,22 +70,45 @@ export function useConversation(conversationId) {
|
|
|
69
70
|
setIsConnected(false);
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
let unsubscribe;
|
|
74
|
+
let release;
|
|
75
|
+
let cancelled = false;
|
|
76
|
+
// 🛑 Sign in BEFORE subscribing. Without a session `auth == null`, and the
|
|
77
|
+
// `chats/$conversationId` rule requires
|
|
78
|
+
// `auth.token.conversationIds[$conversationId]` — a claim that, until this
|
|
79
|
+
// change, NOTHING issued (the token route only ever sent `chatIds`, from
|
|
80
|
+
// the unrelated `chatRooms` collection). Two stacked faults on one channel:
|
|
81
|
+
// no sign-in, and a claim with no issuer. The recipient of a message never
|
|
82
|
+
// saw it until they manually refreshed; the sender only saw their own
|
|
83
|
+
// because `send()` calls `refetch()` directly.
|
|
84
|
+
void (async () => {
|
|
85
|
+
try {
|
|
86
|
+
release = await acquireRealtimeSession("messages", () => apiClient.post(ADMIN_ENDPOINTS.REALTIME_TOKEN, {}));
|
|
87
|
+
if (cancelled) {
|
|
88
|
+
release?.();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
unsubscribe = getClientRealtimeProvider().subscribe(conversationPingPath(conversationId), () => {
|
|
92
|
+
setIsConnected(true);
|
|
93
|
+
void refetch();
|
|
94
|
+
}, (err) => {
|
|
95
|
+
void normalizeError(err);
|
|
96
|
+
setIsConnected(false);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (_err) {
|
|
100
|
+
void normalizeError(_err);
|
|
101
|
+
// Provider unregistered or token fetch failed — falls back to the
|
|
102
|
+
// one-shot fetch above.
|
|
78
103
|
setIsConnected(false);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
void normalizeError(_err);
|
|
84
|
-
// Realtime provider not registered — falls back to one-shot fetch above.
|
|
104
|
+
}
|
|
105
|
+
})();
|
|
106
|
+
return () => {
|
|
107
|
+
cancelled = true;
|
|
85
108
|
setIsConnected(false);
|
|
86
|
-
|
|
87
|
-
|
|
109
|
+
unsubscribe?.();
|
|
110
|
+
release?.();
|
|
111
|
+
};
|
|
88
112
|
}, [conversationId, refetch]);
|
|
89
113
|
const sendMessage = useCallback(async (body) => {
|
|
90
114
|
if (!conversationId)
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { useCallback, useEffect, useState } from "react";
|
|
8
8
|
import { getClientRealtimeProvider } from "../../../contracts/client-realtime";
|
|
9
|
+
import { acquireRealtimeSession } from "../../../contracts/realtime-session";
|
|
9
10
|
import { userConversationsPingPath } from "../realtime";
|
|
10
11
|
import { normalizeError } from "../../../errors/normalize";
|
|
11
12
|
import { apiClient } from "../../../http/ApiClient";
|
|
12
|
-
import { CONVERSATION_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
13
|
+
import { CONVERSATION_ENDPOINTS, ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
13
14
|
const LIST_ENDPOINT = CONVERSATION_ENDPOINTS.LIST;
|
|
14
15
|
async function fetchList(endpoint) {
|
|
15
16
|
const data = await apiClient.get(endpoint);
|
|
@@ -46,19 +47,40 @@ export function useConversations(userId, opts = {}) {
|
|
|
46
47
|
useEffect(() => {
|
|
47
48
|
if (!userId)
|
|
48
49
|
return;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
let unsubscribe;
|
|
51
|
+
let release;
|
|
52
|
+
let cancelled = false;
|
|
53
|
+
// 🛑 Sign in BEFORE subscribing. This hook used to call subscribe() with no
|
|
54
|
+
// authenticated session at all, so `auth == null` and the
|
|
55
|
+
// `chats/user/$userId` rule (`auth.uid == $userId`) could never pass. Every
|
|
56
|
+
// update was permission-denied and the error handler below was a literal
|
|
57
|
+
// `// ignore`, so the whole live-list channel was inert while looking wired.
|
|
58
|
+
void (async () => {
|
|
59
|
+
try {
|
|
60
|
+
release = await acquireRealtimeSession("messages", () => apiClient.post(ADMIN_ENDPOINTS.REALTIME_TOKEN, {}));
|
|
61
|
+
if (cancelled) {
|
|
62
|
+
release?.();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
unsubscribe = getClientRealtimeProvider().subscribe(userConversationsPingPath(userId), () => {
|
|
66
|
+
void refetch();
|
|
67
|
+
}, (err) => {
|
|
68
|
+
// Surfaced, not ignored. A denied subscription is a real defect and
|
|
69
|
+
// silence here is what hid it for months.
|
|
70
|
+
void normalizeError(err);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (_err) {
|
|
74
|
+
void normalizeError(_err);
|
|
75
|
+
// Provider unregistered or token fetch failed — non-fatal; the list
|
|
76
|
+
// still works via the one-shot fetch above, just without live updates.
|
|
77
|
+
}
|
|
78
|
+
})();
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true;
|
|
81
|
+
unsubscribe?.();
|
|
82
|
+
release?.();
|
|
83
|
+
};
|
|
62
84
|
}, [userId, refetch]);
|
|
63
85
|
const totalUnread = conversations.reduce((sum, c) => sum + (c[unreadField] ?? 0), 0);
|
|
64
86
|
return { conversations, isLoading, error, totalUnread, refetch };
|