@mohasinac/appkit 3.3.6 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/classified/ClassifiedDetailView.js +2 -1
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.js +2 -1
- package/dist/_internal/client/features/live/LiveItemDetailView.js +2 -1
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +1 -1
- package/dist/_internal/client/features/lottery/LotteryDetailView.js +2 -1
- package/dist/_internal/client/features/lottery/LotteryListView.js +2 -1
- package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +2 -1
- package/dist/_internal/server/features/checkout/actions.js +0 -1
- package/dist/_internal/server/features/lottery/actions.js +0 -2
- package/dist/_internal/server/features/reviews/actions.js +1 -1
- package/dist/features/about/components/FAQPageView.js +0 -2
- package/dist/features/about/components/FeesView.js +13 -1
- package/dist/features/about/components/HowCheckoutWorksView.js +17 -3
- package/dist/features/about/components/HowEmiWorksView.d.ts +3 -0
- package/dist/features/about/components/HowEmiWorksView.js +117 -0
- package/dist/features/about/components/PublicProfileView.js +1 -4
- package/dist/features/about/index.d.ts +2 -0
- package/dist/features/about/index.js +1 -0
- package/dist/features/account/components/BecomeSellerView.d.ts +1 -1
- package/dist/features/account/components/BecomeSellerView.js +1 -1
- package/dist/features/account/components/NotificationBell.js +1 -2
- package/dist/features/account/components/UserSupportView.js +0 -1
- package/dist/features/admin/components/AdminAdsView.js +3 -3
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBundleEditorView.js +0 -4
- package/dist/features/admin/components/AdminCarouselEditorView.js +3 -3
- package/dist/features/admin/components/AdminCarouselView.js +3 -2
- package/dist/features/admin/components/AdminGuideHubView.js +2 -2
- package/dist/features/admin/components/AdminMediaView.js +2 -4
- package/dist/features/admin/components/AdminUserEditorView.d.ts +1 -1
- package/dist/features/admin/components/AdminUserEditorView.js +1 -1
- package/dist/features/admin/components/QuickActionsPanel.js +2 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +1 -1
- package/dist/features/blog/components/BlogFeaturedCard.js +2 -1
- package/dist/features/blog/components/BlogIndexListing.js +1 -1
- package/dist/features/blog/components/BlogListView.js +2 -1
- package/dist/features/blog/components/BlogPostForm.js +1 -1
- package/dist/features/blog/components/BlogPostView.js +2 -1
- package/dist/features/cart/components/CartDrawer.js +2 -1
- package/dist/features/categories/components/BrandDetailPageView.js +2 -1
- package/dist/features/categories/components/BundleCollage.js +2 -3
- package/dist/features/categories/components/CategoriesIndexListing.js +1 -5
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
- package/dist/features/categories/components/CategoryGrid.js +2 -1
- package/dist/features/categories/components/CategoryProductsView.d.ts +1 -1
- package/dist/features/categories/components/CategoryProductsView.js +1 -1
- package/dist/features/categories/components/ConcernCard.js +2 -1
- package/dist/features/collections/components/CollectionCard.js +2 -1
- package/dist/features/consultation/components/ConsultationForm.js +0 -1
- package/dist/features/contact/components/ContactInfoSidebar.js +2 -2
- package/dist/features/copilot/components/AdminCopilotView.js +0 -9
- package/dist/features/events/components/EventFormDrawer.d.ts +1 -1
- package/dist/features/events/components/EventFormDrawer.js +1 -1
- package/dist/features/events/components/EventPollWidget.js +2 -2
- package/dist/features/events/components/EventsIndexListing.js +1 -1
- package/dist/features/faq/actions/faq-actions.d.ts +2 -10
- package/dist/features/faq/actions/faq-actions.js +0 -4
- package/dist/features/homepage/components/AdSlot.js +2 -1
- package/dist/features/homepage/components/CustomCardsSection.js +2 -3
- package/dist/features/homepage/components/FeaturedBundlesSection.js +2 -3
- package/dist/features/homepage/components/GoogleReviewsSection.js +4 -3
- package/dist/features/homepage/components/HeroBanner.js +0 -1
- package/dist/features/homepage/components/SectionCarousel.d.ts +1 -1
- package/dist/features/homepage/components/SectionCarousel.js +1 -2
- package/dist/features/homepage/components/SocialFeedSection.js +2 -5
- package/dist/features/homepage/components/SocialPostCard.js +4 -3
- package/dist/features/media/MediaPickerModal.js +2 -3
- package/dist/features/media/upload/MediaUploadList.js +2 -2
- package/dist/features/orders/components/OrdersList.js +2 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -0
- package/dist/features/products/api/route.js +1 -2
- package/dist/features/products/components/CompareOverlay.js +2 -3
- package/dist/features/products/components/CustomSectionTabContent.js +2 -2
- package/dist/features/products/components/FeatureBadge.js +0 -6
- package/dist/features/products/components/GroupSettingsPanel.js +2 -3
- package/dist/features/products/components/MarketplaceBundleCard.js +2 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -3
- package/dist/features/products/components/PrizeDrawCollage.js +2 -3
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +3 -3
- package/dist/features/products/components/PrizeRevealModal.js +3 -4
- package/dist/features/products/components/ProductDetailActions.d.ts +1 -1
- package/dist/features/products/components/ProductDetailActions.js +1 -1
- package/dist/features/products/components/ProductDetailPageView.d.ts +3 -1
- package/dist/features/products/components/ProductDetailPageView.js +3 -2
- package/dist/features/products/components/ProductFeatureBadges.d.ts +7 -1
- package/dist/features/products/components/ProductFeatureBadges.js +16 -3
- package/dist/features/products/components/ProductGradingTab.js +2 -2
- package/dist/features/products/components/ProductsIndexPageView.js +2 -2
- package/dist/features/products/components/SublistingCarouselSection.js +3 -4
- package/dist/features/promotions/components/CouponsIndexListing.d.ts +1 -1
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/reviews/components/ReviewDetailShell.js +4 -3
- package/dist/features/reviews/components/ReviewModal.js +2 -1
- package/dist/features/reviews/components/ReviewsIndexListing.js +1 -1
- package/dist/features/reviews/components/ReviewsList.js +2 -1
- package/dist/features/scams/components/ScamAwarenessModal.js +2 -2
- package/dist/features/search/components/SearchView.d.ts +1 -1
- package/dist/features/search/components/SearchView.js +1 -1
- package/dist/features/seller/components/SellerAddressesView.js +1 -5
- package/dist/features/seller/components/SellerPreOrdersView.js +3 -2
- package/dist/features/shell/StepForm.js +1 -1
- package/dist/features/stores/components/InteractiveStoreCard.js +1 -2
- package/dist/features/stores/components/StoreAboutView.js +2 -2
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +2 -2
- package/dist/features/stores/components/StoreFinanceGuideView.js +2 -2
- package/dist/features/stores/components/StoreGuideHubView.js +2 -2
- package/dist/features/stores/components/StoreListingsGuideView.js +2 -2
- package/dist/features/stores/components/StoreNavTabs.js +2 -2
- package/dist/features/stores/components/StoreOrdersGuideView.js +2 -2
- package/dist/features/stores/components/StoreSettingsGuideView.js +3 -3
- package/dist/features/wishlist/components/WishlistView.js +0 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/next/routing/route-map.d.ts +2 -0
- package/dist/next/routing/route-map.js +1 -0
- package/dist/seed/factories/product.factory.js +0 -32
- package/dist/seed/faq-seed-data.js +23 -4
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/index.js +6 -458
- package/dist/ui/DataTable.js +0 -21
- package/dist/ui/components/Anchor.d.ts +3 -1
- package/dist/ui/components/Anchor.js +1 -0
- package/dist/ui/components/Breadcrumb.js +2 -1
- package/dist/ui/components/FormActionBar.js +2 -1
- package/dist/ui/components/ListingLayout.d.ts +1 -1
- package/dist/ui/components/ListingLayout.js +1 -1
- package/dist/ui/components/Motion.js +1 -1
- package/dist/ui/components/QuickCreateModal.js +2 -1
- package/dist/ui/components/SlottedListingView.d.ts +1 -1
- package/dist/ui/components/SlottedListingView.js +1 -2
- package/package.json +1 -1
|
@@ -5,7 +5,6 @@ import { Button, Row, Select } from "../../../ui";
|
|
|
5
5
|
import { Form } from "../../../ui/components/Form";
|
|
6
6
|
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
7
7
|
import { FieldTextarea } from "../../../ui/forms/FieldTextarea";
|
|
8
|
-
const CLS_INPUT = "w-full rounded-md border border-neutral-300 px-3 py-2 text-sm";
|
|
9
8
|
const TIME_SLOTS = [
|
|
10
9
|
"9:00 AM",
|
|
11
10
|
"10:00 AM",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Div, Heading, Row, Span, Stack, Text } from "../../../ui";
|
|
2
|
+
import { Anchor, Div, Heading, Row, Span, Stack, Text } from "../../../ui";
|
|
3
3
|
const __P = {
|
|
4
4
|
p4: "p-4",
|
|
5
5
|
};
|
|
6
6
|
export function ContactInfoSidebar({ infoItems, labels = {}, renderActions, }) {
|
|
7
|
-
return (_jsxs(Stack, { as: "aside", gap: "lg", children: [_jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-4", children: labels.title ?? "Contact Information" }), _jsx(Stack, { gap: "md", children: infoItems.map((item) => (_jsxs(Row, { align: "start", gap: "3", children: [_jsx(Span, { size: "xl", className: "flex-shrink-0 mt-0.5", children: item.icon }), _jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide mb-0.5", color: "muted", size: "xs", weight: "medium", transform: "uppercase", children: item.label }), item.href ? (_jsx(
|
|
7
|
+
return (_jsxs(Stack, { as: "aside", gap: "lg", children: [_jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-4", children: labels.title ?? "Contact Information" }), _jsx(Stack, { gap: "md", children: infoItems.map((item) => (_jsxs(Row, { align: "start", gap: "3", children: [_jsx(Span, { size: "xl", className: "flex-shrink-0 mt-0.5", children: item.icon }), _jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide mb-0.5", color: "muted", size: "xs", weight: "medium", transform: "uppercase", children: item.label }), item.href ? (_jsx(Anchor, { href: item.href, size: "sm", underline: "hover", children: item.value })) : (_jsx(Text, { size: "sm", children: item.value }))] })] }, item.label))) })] }), (labels.businessHoursLabel || labels.responseTimeLabel) && (_jsxs(Stack, { gap: "sm", border: "default", surface: "muted", className: __P.p4, rounded: "xl", children: [labels.businessHoursLabel && labels.businessHoursValue && (_jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: labels.businessHoursLabel }), _jsx(Text, { className: "mt-0.5", size: "sm", children: labels.businessHoursValue })] })), labels.responseTimeLabel && labels.responseTimeValue && (_jsxs(Div, { children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: labels.responseTimeLabel }), _jsx(Text, { className: "mt-0.5", size: "sm", children: labels.responseTimeValue })] }))] })), renderActions?.()] }));
|
|
8
8
|
}
|
|
@@ -18,15 +18,6 @@ function MessageBubble({ msg }) {
|
|
|
18
18
|
const isUser = msg.role === "user";
|
|
19
19
|
return (_jsx(Div, { layout: "flex", justify: isUser ? "end" : "start", children: _jsxs(Div, { textSize: "sm", className: `max-w-[80%] py-[0.625rem] ${isUser ? "bg-primary text-white" : "bg-[var(--appkit-color-surface)] dark:bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text)]"}`, padding: "x-md", style: { borderRadius: isUser ? "1.5rem 0.125rem 1.5rem 1.5rem" : "0.125rem 1.5rem 1.5rem 1.5rem" }, children: [_jsx(Text, { className: "whitespace-pre-wrap leading-relaxed", size: "sm", children: msg.content }), msg.durationMs ? (_jsxs(Span, { size: "xs", className: "mt-1 block opacity-60", children: [(msg.durationMs / 1000).toFixed(1), "s"] })) : null] }) }));
|
|
20
20
|
}
|
|
21
|
-
function renderCopilotChatPanel(props) {
|
|
22
|
-
const { messages, conversationId, isLoading, error, input, setInput, labels, messagesEndRef, handleSubmit } = props;
|
|
23
|
-
return (_jsxs(Stack, { className: "border border-neutral-200 h-[calc(100vh-300px)]", surface: "default", rounded: "xl", children: [_jsx(Div, { border: "bottom", className: __P.p3, children: _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", weight: "medium", children: [labels?.conversationId ?? LBL_CONVERSATION_ID, ": ", conversationId] }) }), _jsxs(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "md", children: [messages.length === 0 ? (_jsxs(Stack, { justify: "center", className: "h-full text-center", align: "center", children: [_jsx(Text, { weight: "medium", children: labels?.noMessages ?? "No messages yet" }), _jsx(Text, { variant: "secondary", className: "mt-1", size: "sm", children: labels?.noMessagesDesc ?? "Ask anything to get started" })] })) : null, messages.map((msg) => _jsx(MessageBubble, { msg: msg }, msg.id)), isLoading ? (_jsx(Row, { justify: "start", children: _jsx(Div, { paddingY: "y-xs-tall", surface: "muted", className: "", padding: "x-md", style: { borderRadius: "0.125rem 1.5rem 1.5rem 1.5rem" }, children: _jsx(Span, { size: "sm", className: "opacity-60 animate-pulse", children: "Thinking..." }) }) })) : null, error ? _jsx(Text, { className: CLS_ERROR_TEXT, children: labels?.errorLabel ?? "An error occurred. Please try again." }) : null, _jsx(Div, { ref: messagesEndRef })] }), _jsx(Div, { border: "top", className: __P.p3, children: _jsxs(Form, { onSubmit: handleSubmit, gap: "xs", className: "flex", children: [_jsx(Input, { type: "text", bare: true, value: input, onChange: (e) => setInput(e.target.value), placeholder: labels?.inputPlaceholder ?? "Ask anything...", disabled: isLoading, className: "flex-1 h-10" }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading || !input.trim(), className: "shrink-0", children: isLoading ? labels?.sendingButton ?? "Sending..." : labels?.sendButton ?? "Send" })] }) })] }));
|
|
24
|
-
}
|
|
25
|
-
function renderCopilotHistoryPanel(props) {
|
|
26
|
-
const { historyQuery, conversationInput, setConversationInput, loadConversation, labels } = props;
|
|
27
|
-
return (_jsxs(Stack, { surface: "card", padding: "sm", gap: "3", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", children: labels?.historyTitle ?? "Conversation History" }), _jsxs(Form, { spacing: "sm", onSubmit: (e) => { e.preventDefault(); if (!conversationInput.trim())
|
|
28
|
-
return; loadConversation(conversationInput.trim()); }, children: [_jsx(Input, { label: labels?.conversationId ?? LBL_CONVERSATION_ID, value: conversationInput, onChange: (e) => setConversationInput(e.target.value), placeholder: "conv_..." }), _jsx(Button, { type: "submit", variant: "secondary", size: "sm", className: "w-full", children: labels?.loadConversation ?? "Load conversation" })] }), historyQuery.error ? (_jsx(Alert, { variant: "warning", title: "History unavailable", children: historyQuery.error instanceof Error ? historyQuery.error.message : "Could not load history" })) : null, _jsxs(Stack, { className: `max-h-72 ${__O.yAuto}`, gap: "sm", children: [(historyQuery.data?.messages ?? []).map((log, index) => (_jsxs(Div, { className: "border border-neutral-200", rounded: "lg", padding: "xs", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", weight: "medium", children: new Date(log.createdAt).toLocaleString() }), _jsxs(Text, { className: `mt-1`, truncate: 3, size: "sm", children: ["Q: ", log.prompt] }), _jsxs(Text, { className: `mt-1`, color: "muted", truncate: 3, size: "sm", children: ["A: ", log.response] })] }, `${log.createdAt}-${index}`))), !historyQuery.isLoading && (historyQuery.data?.messages?.length ?? 0) === 0 ? (_jsx(Text, { variant: "secondary", size: "sm", children: "No server history found for this conversation yet." })) : null] })] }));
|
|
29
|
-
}
|
|
30
21
|
export function AdminCopilotView({ endpoint, historyEndpoint = "/api/copilot/history", renderHeader, labels = {}, className = "", }) {
|
|
31
22
|
const [input, setInput] = useState("");
|
|
32
23
|
const [conversationInput, setConversationInput] = useState("");
|
|
@@ -19,4 +19,4 @@ export interface EventFormDrawerProps {
|
|
|
19
19
|
renderFooter?: () => React.ReactNode;
|
|
20
20
|
className?: string;
|
|
21
21
|
}
|
|
22
|
-
export declare function EventFormDrawer({ isOpen, onClose, renderHeader, renderBaseFields, renderMediaFields, renderTypeConfig, renderTypeSelector, renderFooter, className, }: EventFormDrawerProps): React.JSX.Element | null;
|
|
22
|
+
export declare function EventFormDrawer({ isOpen, onClose: _onClose, renderHeader, renderBaseFields, renderMediaFields, renderTypeConfig, renderTypeSelector, renderFooter, className, }: EventFormDrawerProps): React.JSX.Element | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Stack } from "../../../ui";
|
|
3
|
-
export function EventFormDrawer({ isOpen, onClose, renderHeader, renderBaseFields, renderMediaFields, renderTypeConfig, renderTypeSelector, renderFooter, className = "", }) {
|
|
3
|
+
export function EventFormDrawer({ isOpen, onClose: _onClose, renderHeader, renderBaseFields, renderMediaFields, renderTypeConfig, renderTypeSelector, renderFooter, className = "", }) {
|
|
4
4
|
if (!isOpen)
|
|
5
5
|
return null;
|
|
6
6
|
return (_jsxs(Stack, { className: `${className}`, gap: "lg", children: [renderHeader?.(), renderTypeSelector?.(), renderBaseFields?.(), renderMediaFields?.(), renderTypeConfig?.(), renderFooter?.()] }));
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { useAuth } from "../../../react/contexts/SessionContext";
|
|
5
5
|
import { ROUTES } from "../../../next";
|
|
6
|
-
import { Div, LoginRequiredModal, Span, Stack, Text } from "../../../ui";
|
|
6
|
+
import { Div, LoginRequiredModal, Span, Stack, Text, TextLink } from "../../../ui";
|
|
7
7
|
import { normalizeError } from "../../../errors/normalize";
|
|
8
8
|
const CLS_THANKS_BOX = "rounded-xl border border-success dark:border-success bg-success-surface px-6 py-8 text-center space-y-2";
|
|
9
9
|
export function EventPollWidget({ eventId, pollConfig, eventStatus, totalEntries, entriesEndpoint, className = "", }) {
|
|
@@ -62,7 +62,7 @@ export function EventPollWidget({ eventId, pollConfig, eventStatus, totalEntries
|
|
|
62
62
|
return (_jsxs(Stack, { paddingX: "x-5", className: className, gap: "3", rounded: "xl", padding: "y-md", border: "default", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "This poll has closed." }), totalEntries !== undefined && (_jsxs(Text, { size: "sm", color: "faint", children: [totalEntries.toLocaleString(), " vote", totalEntries !== 1 ? "s" : "", " cast"] })), _jsx(Stack, { gap: "sm", children: pollConfig.options.map((opt) => (_jsx(Div, { textSize: "sm", paddingY: "y-xs-tall", color: "muted", border: "subtle", padding: "x-md", rounded: "lg", children: opt.label }, opt.id))) })] }));
|
|
63
63
|
}
|
|
64
64
|
if (pollConfig.requireLogin && !user) {
|
|
65
|
-
return (_jsxs(Stack, { className: `text-center ${className}`, gap: "3", rounded: "xl", paddingY: "y-xl", paddingX: "x-lg", border: "default", children: [_jsx(Text, { weight: "semibold", color: "primary", children: "Login to vote" }), _jsx(Text, { size: "sm", color: "muted", children: "You need an account to participate in this poll." }), _jsx("
|
|
65
|
+
return (_jsxs(Stack, { className: `text-center ${className}`, gap: "3", rounded: "xl", paddingY: "y-xl", paddingX: "x-lg", border: "default", children: [_jsx(Text, { weight: "semibold", color: "primary", children: "Login to vote" }), _jsx(Text, { size: "sm", color: "muted", children: "You need an account to participate in this poll." }), _jsx(TextLink, { variant: "bare", href: String(ROUTES.AUTH.LOGIN), rounded: "xl", size: "sm", weight: "semibold", className: "inline-block bg-primary px-6 py-2.5 text-white hover:bg-primary-600", children: "Log In" })] }));
|
|
66
66
|
}
|
|
67
67
|
if (isSubmitted) {
|
|
68
68
|
return (_jsxs(Div, { className: `${CLS_THANKS_BOX} ${className}`, children: [_jsx(Text, { className: "text-success", weight: "semibold", children: "Vote recorded!" }), _jsx(Text, { size: "sm", color: "muted", children: "Thanks for participating." })] }));
|
|
@@ -116,7 +116,7 @@ export function EventsIndexListing({ initialData }) {
|
|
|
116
116
|
sort: table.get(TABLE_KEYS.SORT) || sortBy(EVENT_FIELDS.STARTS_AT, "ASC"),
|
|
117
117
|
filters: filterParts.length > 0 ? sieveAnd(...filterParts) : undefined,
|
|
118
118
|
};
|
|
119
|
-
const { events,
|
|
119
|
+
const { events, totalPages, isLoading } = useEvents(params, { initialData });
|
|
120
120
|
const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
|
|
121
121
|
const commitSearch = useCallback(() => {
|
|
122
122
|
table.set(TABLE_KEYS.QUERY, searchInput.trim());
|
|
@@ -161,17 +161,10 @@ export declare const faqUpdateSchema: z.ZodObject<{
|
|
|
161
161
|
}>;
|
|
162
162
|
export type FaqCreateInput = z.infer<typeof faqCreateSchema>;
|
|
163
163
|
export type FaqUpdateInput = z.infer<typeof faqUpdateSchema>;
|
|
164
|
-
|
|
165
|
-
faqId: z.ZodString;
|
|
166
|
-
vote: z.ZodEnum<["helpful", "not-helpful"]>;
|
|
167
|
-
}, "strip", z.ZodTypeAny, {
|
|
168
|
-
faqId: string;
|
|
169
|
-
vote: "helpful" | "not-helpful";
|
|
170
|
-
}, {
|
|
164
|
+
export interface VoteFaqActionInput {
|
|
171
165
|
faqId: string;
|
|
172
166
|
vote: "helpful" | "not-helpful";
|
|
173
|
-
}
|
|
174
|
-
export type VoteFaqActionInput = z.infer<typeof voteSchema>;
|
|
167
|
+
}
|
|
175
168
|
export interface VoteFaqActionResult {
|
|
176
169
|
helpful: number;
|
|
177
170
|
notHelpful: number;
|
|
@@ -188,4 +181,3 @@ export declare function listFaqs(params?: {
|
|
|
188
181
|
}): Promise<FirebaseSieveResult<FAQDocument>>;
|
|
189
182
|
export declare function listPublicFaqs(category?: string, limit?: number): Promise<FAQDocument[]>;
|
|
190
183
|
export declare function getFaqById(id: string): Promise<FAQDocument | null>;
|
|
191
|
-
export {};
|
|
@@ -30,10 +30,6 @@ export const faqCreateSchema = faqBaseSchema.refine((data) => {
|
|
|
30
30
|
return matches.length <= 10;
|
|
31
31
|
}, { message: "Too many template variables (max 10)" });
|
|
32
32
|
export const faqUpdateSchema = faqBaseSchema.partial();
|
|
33
|
-
const voteSchema = z.object({
|
|
34
|
-
faqId: z.string().min(1),
|
|
35
|
-
vote: z.enum(["helpful", "not-helpful"]),
|
|
36
|
-
});
|
|
37
33
|
// --- Actions --------------------------------------------------------------
|
|
38
34
|
export async function voteFaq(input) {
|
|
39
35
|
const faq = await faqsRepository.findById(input.faqId);
|
|
@@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
5
5
|
import { getAdSlot, isAdSlotRenderable } from "../ad-registry";
|
|
6
6
|
import { useActiveAd } from "../hooks/useActiveAd";
|
|
7
7
|
import { Anchor, Div, Span, Text } from "../../../ui";
|
|
8
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
8
9
|
// ---------------------------------------------------------------------------
|
|
9
10
|
// Sub-renderers
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
@@ -33,7 +34,7 @@ function ManualAdBanner({ ad }) {
|
|
|
33
34
|
const { creative } = ad;
|
|
34
35
|
if (!creative)
|
|
35
36
|
return null;
|
|
36
|
-
return (_jsxs(Anchor, { href: creative.ctaHref || "#", target: creative.ctaHref ? "_blank" : undefined, rel: "noopener noreferrer", underline: "none", layout: "flex", gap: "lg", rounded: "lg", padding: "inline", hoverSurface: "subtle", className: "w-full overflow-hidden bg-[var(--appkit-color-surface)]", children: [creative.imageUrl ? (_jsx("
|
|
37
|
+
return (_jsxs(Anchor, { href: creative.ctaHref || "#", target: creative.ctaHref ? "_blank" : undefined, rel: "noopener noreferrer", underline: "none", layout: "flex", gap: "lg", rounded: "lg", padding: "inline", hoverSurface: "subtle", className: "w-full overflow-hidden bg-[var(--appkit-color-surface)]", children: [creative.imageUrl ? (_jsx(Div, { className: "relative h-14 w-14 flex-shrink-0 overflow-hidden", rounded: "default", children: _jsx(MediaImage, { src: creative.imageUrl, alt: creative.title || "Advertisement", size: "thumbnail" }) })) : null, _jsxs(Div, { className: "flex-1 min-w-0", children: [creative.title ? (_jsx(Text, { className: "truncate", color: "primary", size: "sm", weight: "semibold", children: creative.title })) : null, creative.body ? (_jsx(Text, { className: "truncate", color: "muted", size: "xs", children: creative.body })) : null] }), creative.ctaLabel ? (_jsx(Span, { color: "inverse", size: "xs", weight: "semibold", className: "flex-shrink-0 bg-primary py-[0.375rem]", rounded: "md", padding: "x-sm", children: creative.ctaLabel })) : null] }));
|
|
37
38
|
}
|
|
38
39
|
// ---------------------------------------------------------------------------
|
|
39
40
|
// Primary component
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Row } from "@mohasinac/appkit/ui";
|
|
4
|
-
import { Div, Heading, HorizontalScroller, Section, Stack, Text } from "../../../ui";
|
|
4
|
+
import { Anchor, Div, Heading, HorizontalScroller, Section, Stack, Text } from "../../../ui";
|
|
5
5
|
import { DynamicBgDiv } from "../../../ui/components/DynamicBgDiv";
|
|
6
6
|
import { THEMED_TEXT_PRIMARY } from "../../../_internal/shared/styles/themed";
|
|
7
7
|
import { SectionCarousel } from "./SectionCarousel";
|
|
@@ -45,7 +45,7 @@ const VARIANT_CLASS = {
|
|
|
45
45
|
function CardItem({ card }) {
|
|
46
46
|
const radiusClass = RADIUS_CLASS[card.borderRadius ?? "lg"] ?? "rounded-lg";
|
|
47
47
|
const shadowClass = SHADOW_CLASS[card.shadowLevel ?? "sm"] ?? "shadow-sm";
|
|
48
|
-
return (_jsxs(DynamicBgDiv, { color: card.backgroundColor, textColor: card.textColor, className: `flex flex-col overflow-hidden ${radiusClass} ${shadowClass} bg-[var(--appkit-color-surface)]`, children: [card.image && (_jsx(Div, { className: `relative w-full aspect-video ${__O.hidden}`, children: _jsx(MediaImage, { src: card.image, alt: card.imageAlt ?? "", size: "card" }) })), _jsxs(Stack, { className: `${__P.p4} flex-1`, gap: "sm", children: [card.eyebrow && (_jsx(Text, { className: "tracking-widest opacity-70", size: "xs", weight: "semibold", transform: "uppercase", children: card.eyebrow })), card.title && (_jsx(Text, { className: "leading-snug", size: "base", weight: "bold", children: card.title })), card.body && (_jsx(Text, { className: "opacity-80 leading-relaxed flex-1", size: "sm", children: card.body })), card.buttons && card.buttons.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, className: "mt-2", children: card.buttons.map((btn, i) => (_jsx(
|
|
48
|
+
return (_jsxs(DynamicBgDiv, { color: card.backgroundColor, textColor: card.textColor, className: `flex flex-col overflow-hidden ${radiusClass} ${shadowClass} bg-[var(--appkit-color-surface)]`, children: [card.image && (_jsx(Div, { className: `relative w-full aspect-video ${__O.hidden}`, children: _jsx(MediaImage, { src: card.image, alt: card.imageAlt ?? "", size: "card" }) })), _jsxs(Stack, { className: `${__P.p4} flex-1`, gap: "sm", children: [card.eyebrow && (_jsx(Text, { className: "tracking-widest opacity-70", size: "xs", weight: "semibold", transform: "uppercase", children: card.eyebrow })), card.title && (_jsx(Text, { className: "leading-snug", size: "base", weight: "bold", children: card.title })), card.body && (_jsx(Text, { className: "opacity-80 leading-relaxed flex-1", size: "sm", children: card.body })), card.buttons && card.buttons.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, className: "mt-2", children: card.buttons.map((btn, i) => (_jsx(Anchor, { href: btn.href, target: btn.target ?? "_self", rel: btn.target === "_blank" ? "noopener noreferrer" : undefined, tone: "none", underline: "none", className: VARIANT_CLASS[btn.variant] ?? VARIANT_CLASS.primary, children: btn.label }, i))) })), card.formEmbed && (_jsx("iframe", { src: card.formEmbed, sandbox: "allow-forms allow-scripts", loading: "lazy", className: "w-full mt-3 border-0 rounded-md min-h-[200px]", title: "Embedded form" }))] })] }));
|
|
49
49
|
}
|
|
50
50
|
// --- Section Header ----------------------------------------------------------
|
|
51
51
|
function SectionHeader({ title }) {
|
|
@@ -56,7 +56,6 @@ function SectionHeader({ title }) {
|
|
|
56
56
|
}
|
|
57
57
|
export function CustomCardsSection(config) {
|
|
58
58
|
const { title, layout, columns = 3, cards, autoScroll, scrollIntervalMs } = config;
|
|
59
|
-
const themed = { textPrimary: THEMED_TEXT_PRIMARY };
|
|
60
59
|
if (!cards?.length)
|
|
61
60
|
return null;
|
|
62
61
|
// autoScroll: wrap all cards in SectionCarousel (client carousel)
|
|
@@ -1,6 +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 { Badge, Div, Heading, Row, Section, Stack, Text } from "../../../ui";
|
|
4
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
4
5
|
import { ROUTES } from "../../../next/routing/route-map";
|
|
5
6
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
6
7
|
import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
@@ -21,9 +22,7 @@ function FeaturedBundleCard({ bundle, onBuyNow }) {
|
|
|
21
22
|
const memberCount = bundle.bundleProductIds?.length ?? 0;
|
|
22
23
|
const cover = bundle.display?.coverImage;
|
|
23
24
|
const href = String(ROUTES.PUBLIC.BUNDLE_DETAIL?.(bundle.slug) ?? "#");
|
|
24
|
-
return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-3 hover:no-underline", children: [_jsx(Div, { className: `mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: cover ? (
|
|
25
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
26
|
-
_jsx("img", { src: cover, alt: bundle.name, className: "h-full w-full object-cover transition-transform group-hover:scale-105", loading: "lazy" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })) }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePriceInPaise
|
|
25
|
+
return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-3 hover:no-underline", children: [_jsx(Div, { className: `relative mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: cover ? (_jsx(MediaImage, { src: cover, alt: bundle.name, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })) }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePriceInPaise
|
|
27
26
|
? formatCurrency(bundle.bundlePriceInPaise / 100, "INR")
|
|
28
27
|
: BUNDLE_COPY.featured.priceFallback }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }), onBuyNow && (_jsx(Div, { border: "subtle", className: `border-t ${__P.p3}`, padding: "t-xs", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }));
|
|
29
28
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
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 { Div, Heading, Row, Section, Span, Stack, Text } from "../../../ui";
|
|
3
|
+
import { Anchor, Div, Heading, Row, Section, Span, Stack, Text } from "../../../ui";
|
|
4
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
4
5
|
import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
|
|
5
6
|
import { fetchGoogleReviews } from "../lib/google-reviews-fetcher";
|
|
6
7
|
const __P = {
|
|
@@ -26,7 +27,7 @@ function ReviewCard({ review, showRating, showDate, }) {
|
|
|
26
27
|
day: "numeric",
|
|
27
28
|
})
|
|
28
29
|
: null;
|
|
29
|
-
return (_jsxs(Stack, { className: `${__P.p4} bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)]`, gap: "3", rounded: "xl", shadow: "sm", children: [_jsxs(Row, { align: "center", gap: "3", children: [review.authorPhotoUrl ? (_jsx(
|
|
30
|
+
return (_jsxs(Stack, { className: `${__P.p4} bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)]`, gap: "3", rounded: "xl", shadow: "sm", children: [_jsxs(Row, { align: "center", gap: "3", children: [review.authorPhotoUrl ? (_jsx(Div, { className: "relative w-9 h-9 flex-shrink-0 overflow-hidden", rounded: "full", children: _jsx(MediaImage, { src: review.authorPhotoUrl, alt: review.authorName, size: "avatar" }) })) : (_jsx(Row, { className: "w-9 h-9 bg-[var(--appkit-color-primary)] flex-shrink-0", align: "center", justify: "center", rounded: "full", children: _jsx(Span, { color: "inverse", size: "sm", weight: "bold", children: review.authorName.charAt(0).toUpperCase() }) })), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: "truncate", size: "sm", weight: "semibold", children: review.authorName }), dateStr && (_jsx(Text, { size: "xs", color: "muted", children: dateStr }))] })] }), showRating && _jsx(StarRating, { rating: review.rating }), review.text && (_jsx(Text, { className: "leading-relaxed line-clamp-4", color: "muted", size: "sm", children: review.text })), _jsx(GoogleBadge, {})] }));
|
|
30
31
|
}
|
|
31
32
|
// --- Empty / Not Configured States -------------------------------------------
|
|
32
33
|
function NotConfiguredState() {
|
|
@@ -67,5 +68,5 @@ export async function GoogleReviewsSection(config) {
|
|
|
67
68
|
: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4";
|
|
68
69
|
const cardClass = layout === "carousel" ? "flex-shrink-0 w-[85%] sm:w-72 snap-start" : "";
|
|
69
70
|
const mapsHref = googleMapsUrl || (placeId ? `https://search.google.com/local/reviews?placeid=${placeId}` : "");
|
|
70
|
-
return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: "w-full max-w-7xl mx-auto", padding: "x-md", children: [_jsxs(Row, { 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(
|
|
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(Row, { 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}`))) }))] }) }));
|
|
71
72
|
}
|
|
@@ -4,7 +4,6 @@ import { useState, useEffect, useRef, useCallback } from "react";
|
|
|
4
4
|
import Image from "next/image";
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import { Button, Div, Heading, Row, Span, Stack } from "../../../ui";
|
|
7
|
-
const FONT_BANGERS = "var(--font-bangers, Bangers, cursive)";
|
|
8
7
|
export function HeroBanner({ banners, autoplayMs = 5000 }) {
|
|
9
8
|
const [current, setCurrent] = useState(0);
|
|
10
9
|
const [animating, setAnimating] = useState(false);
|
|
@@ -72,4 +72,4 @@ export interface SectionCarouselProps<T = unknown> {
|
|
|
72
72
|
*/
|
|
73
73
|
minItemWidth?: number;
|
|
74
74
|
}
|
|
75
|
-
export declare function SectionCarousel<T = unknown>({ title, description, headingVariant, pillLabel, backgroundImage, viewMoreHref, viewMoreLabel, items, renderItem, perView, gap, autoScroll, autoScrollInterval, keyExtractor, rows, className, isLoading, skeletonCount, lightText, showPeek, minItemWidth, }: SectionCarouselProps<T>): React.JSX.Element | null;
|
|
75
|
+
export declare function SectionCarousel<T = unknown>({ title, description, headingVariant, pillLabel, backgroundImage, viewMoreHref, viewMoreLabel, items, renderItem, perView, gap, autoScroll, autoScrollInterval, keyExtractor, rows, className, isLoading, skeletonCount, lightText, showPeek: _showPeek, minItemWidth, }: SectionCarouselProps<T>): React.JSX.Element | null;
|
|
@@ -9,11 +9,10 @@ const __O = {
|
|
|
9
9
|
};
|
|
10
10
|
// --- Skeleton ----------------------------------------------------------------
|
|
11
11
|
function CarouselSkeleton({ count }) {
|
|
12
|
-
const themed = { bgSecondary: THEMED_BG_SECONDARY, textPrimary: THEMED_TEXT_PRIMARY, textSecondary: THEMED_TEXT_SECONDARY };
|
|
13
12
|
return (_jsx(Div, { layout: "flex", gap: "4", className: `${__O.hidden}`, padding: "x-md", children: Array.from({ length: count }).map((_, i) => (_jsxs(Stack, { className: "flex-none min-w-[clamp(150px,18vw,260px)] max-w-[clamp(240px,36vw,380px)] h-[clamp(180px,26vh,260px)]", gap: "sm", children: [_jsx(Div, { className: `aspect-square ${skeleton.image}`, rounded: "xl" }), _jsx(Div, { className: `${skeleton.text} w-3/4` }), _jsx(Div, { className: `${skeleton.text} w-1/2` })] }, i))) }));
|
|
14
13
|
}
|
|
15
14
|
// --- Component ---------------------------------------------------------------
|
|
16
|
-
export function SectionCarousel({ title, description, headingVariant = "editorial", pillLabel, backgroundImage, viewMoreHref, viewMoreLabel = "View all →", items, renderItem, perView = CAROUSEL_PER_VIEW.standard, gap = 16, autoScroll = false, autoScrollInterval = 3500, keyExtractor, rows = 1, className = "", isLoading = false, skeletonCount = 4, lightText, showPeek = false, minItemWidth = 220, }) {
|
|
15
|
+
export function SectionCarousel({ title, description, headingVariant = "editorial", pillLabel, backgroundImage, viewMoreHref, viewMoreLabel = "View all →", items, renderItem, perView = CAROUSEL_PER_VIEW.standard, gap = 16, autoScroll = false, autoScrollInterval = 3500, keyExtractor, rows = 1, className = "", isLoading = false, skeletonCount = 4, lightText, showPeek: _showPeek = false, minItemWidth = 220, }) {
|
|
17
16
|
if (!isLoading && items.length === 0)
|
|
18
17
|
return null;
|
|
19
18
|
const hasBg = Boolean(backgroundImage);
|
|
@@ -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 { Div, Heading, Row, Section, Stack, Text } from "../../../ui";
|
|
3
|
+
import { Anchor, Div, Heading, Row, 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";
|
|
@@ -22,9 +22,6 @@ function platformProfileUrl(platform, handle) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
// --- Skeleton ----------------------------------------------------------------
|
|
25
|
-
function SocialFeedSkeleton({ count }) {
|
|
26
|
-
return (_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: Array.from({ length: count }).map((_, i) => (_jsx(Div, { className: "aspect-square animate-pulse", surface: "subtle", rounded: "xl" }, i))) }));
|
|
27
|
-
}
|
|
28
25
|
// --- Empty state -------------------------------------------------------------
|
|
29
26
|
function SocialFeedEmpty({ platform }) {
|
|
30
27
|
return (_jsxs(Stack, { justify: "center", color: "faint", padding: "y-4xl", align: "center", children: [_jsxs("svg", { className: "w-10 h-10 mb-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "3" }), _jsx("path", { d: "M9 9h6M9 12h6M9 15h4" })] }), _jsxs(Text, { size: "sm", children: ["No posts from ", PLATFORM_LABELS[platform], " yet."] })] }));
|
|
@@ -102,5 +99,5 @@ export async function SocialFeedSection(config) {
|
|
|
102
99
|
: layout === "masonry"
|
|
103
100
|
? "break-inside-avoid"
|
|
104
101
|
: "";
|
|
105
|
-
return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: "w-full max-w-7xl mx-auto", padding: "x-md", children: [_jsxs(Row, { className: "mb-8", align: "end", justify: "between", gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: "mb-1", children: title || `${PLATFORM_LABELS[platform]} Feed` }), subtitle && (_jsx(Text, { size: "sm", variant: "muted", children: subtitle }))] }), profileUrl && (_jsxs(
|
|
102
|
+
return (_jsx(Section, { padding: "y-3xl", surface: "muted", children: _jsxs(Div, { className: "w-full max-w-7xl mx-auto", padding: "x-md", children: [_jsxs(Row, { className: "mb-8", align: "end", justify: "between", gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: "mb-1", children: title || `${PLATFORM_LABELS[platform]} Feed` }), subtitle && (_jsx(Text, { size: "sm", variant: "muted", children: subtitle }))] }), profileUrl && (_jsxs(Anchor, { href: profileUrl, tone: "none", underline: "none", className: "shrink-0 text-sm font-medium text-[var(--appkit-color-primary)] hover:opacity-80", children: ["Follow on ", PLATFORM_LABELS[platform], " \u2192"] }))] }), error ? (_jsx(Div, { textSize: "sm", className: "text-center", color: "faint", padding: "y-3xl", children: error })) : posts.length === 0 ? (_jsx(SocialFeedEmpty, { platform: platform })) : (_jsx(Div, { className: gridClass, children: posts.slice(0, count).map((post) => (_jsx(Div, { className: cardClass, children: _jsx(SocialPostCard, { post: post, showCaption: showCaption, showStats: showStats }) }, post.id))) }))] }) }));
|
|
106
103
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Div, Row, Span, Stack, Text } from "../../../ui";
|
|
2
|
+
import { Anchor, Div, Row, Span, Stack, Text } from "../../../ui";
|
|
3
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
3
4
|
const __P = {
|
|
4
5
|
p3: "p-3",
|
|
5
6
|
};
|
|
@@ -46,12 +47,12 @@ function YouTubeCard({ post, showCaption }) {
|
|
|
46
47
|
const thumbnail = `https://img.youtube.com/vi/${post.videoId}/maxresdefault.jpg`;
|
|
47
48
|
const href = `https://youtu.be/${post.videoId}`;
|
|
48
49
|
const meta = PLATFORM_META.youtube;
|
|
49
|
-
return (_jsxs(
|
|
50
|
+
return (_jsxs(Anchor, { href: href, tone: "none", underline: "none", className: "group relative block aspect-video w-full overflow-hidden rounded-xl bg-zinc-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-youtube)]", "aria-label": post.caption ?? `Watch on YouTube`, children: [_jsx(MediaImage, { src: thumbnail, alt: post.caption ?? "YouTube video", size: "card", className: "transition-transform duration-300 group-hover:scale-105" }), _jsx(Row, { className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Row, { className: "h-14 w-14 bg-[var(--appkit-color-youtube)] transition-transform duration-200 group-hover:scale-110", align: "center", justify: "center", rounded: "full", shadow: "lg", children: _jsx("svg", { className: "ml-1 h-6 w-6 text-white", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M8 5v14l11-7z" }) }) }) }), _jsxs(Row, { textWeight: "medium", textSize: "xs", className: `absolute right-2 top-2 text-white px-[0.375rem] ${meta.colorClass}`, padding: "y-2xs", align: "center", gap: "xs", rounded: "default", children: [meta.icon, _jsx(Span, { className: "hidden sm:inline", children: meta.label })] }), post.channelName && (_jsx(Div, { textSize: "xs", surface: "overlay-lg", className: "absolute bottom-2 left-2 py-[0.125rem] text-white backdrop-blur-sm", padding: "x-xs", rounded: "default", children: post.channelName })), showCaption && post.caption && (_jsx(Stack, { justify: "end", surface: "overlay-lg", className: `absolute inset-0 ${__P.p3} opacity-0 transition-opacity duration-200 group-hover:opacity-100`, children: _jsx(Text, { color: "inverse", className: "line-clamp-2", size: "xs", children: post.caption }) }))] }));
|
|
50
51
|
}
|
|
51
52
|
export function SocialPostCard({ post, showCaption = true, showStats = true }) {
|
|
52
53
|
if (post.platform === "youtube") {
|
|
53
54
|
return _jsx(YouTubeCard, { post: post, showCaption: showCaption });
|
|
54
55
|
}
|
|
55
56
|
const meta = PLATFORM_META[post.platform];
|
|
56
|
-
return (_jsxs(
|
|
57
|
+
return (_jsxs(Anchor, { href: post.permalink, tone: "none", underline: "none", className: "group relative block aspect-square overflow-hidden rounded-xl bg-[var(--appkit-color-surface)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)] bg-[var(--appkit-color-surface-elevated)]", "aria-label": post.caption ?? `View on ${meta.label}`, children: [post.imageUrl ? (_jsx(MediaImage, { src: post.imageUrl, alt: post.caption ?? `${meta.label} post`, size: "card", className: "transition-transform duration-300 group-hover:scale-105" })) : (_jsx(Row, { className: "h-full w-full", surface: "subtle", align: "center", justify: "center", children: _jsx(Span, { size: "sm", color: "faint", children: "No preview" }) })), post.mediaType === "video" && (_jsx(Row, { className: "pointer-events-none absolute inset-0", align: "center", justify: "center", children: _jsx(Row, { surface: "overlay-sm", className: "h-12 w-12 backdrop-blur-sm", align: "center", justify: "center", rounded: "full", children: _jsx("svg", { className: "ml-1 h-5 w-5 text-white", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M8 5v14l11-7z" }) }) }) })), post.mediaType === "carousel" && (_jsx(Div, { className: "pointer-events-none absolute right-2 top-2", children: _jsx(Row, { surface: "overlay-sm", className: "h-6 w-6 backdrop-blur-sm", rounded: "default", align: "center", justify: "center", children: _jsxs("svg", { className: "h-3.5 w-3.5 text-white", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("rect", { x: "2", y: "3", width: "15", height: "15", rx: "2" }), _jsx("path", { d: "M7 8h14M7 12h14M7 16h14" })] }) }) })), _jsxs(Row, { textWeight: "medium", textSize: "xs", className: `absolute left-2 top-2 px-[0.375rem] text-white ${meta.colorClass}`, padding: "y-2xs", align: "center", gap: "xs", rounded: "default", children: [meta.icon, _jsx(Span, { className: "hidden sm:inline", children: meta.label })] }), _jsxs(Stack, { justify: "end", surface: "overlay-xl", className: `pointer-events-none absolute inset-0 ${__P.p3} opacity-0 transition-opacity duration-200 group-hover:opacity-100`, children: [showCaption && post.caption && (_jsx(Text, { color: "inverse", className: "mb-2 line-clamp-3", size: "xs", children: post.caption })), showStats && (_jsxs(Row, { textSize: "xs", className: "text-white/80", align: "center", gap: "3", children: [post.stats.likes != null && (_jsxs(Span, { layout: "flex", gap: "xs", children: [_jsx("svg", { className: "h-3 w-3", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" }) }), formatCount(post.stats.likes)] })), post.stats.views != null && (_jsxs(Span, { layout: "flex", gap: "xs", children: [_jsx("svg", { className: "h-3 w-3", viewBox: "0 0 24 24", fill: "currentColor", children: _jsx("path", { d: "M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" }) }), formatCount(post.stats.views)] })), post.stats.comments != null && (_jsxs(Span, { layout: "flex", gap: "xs", children: [_jsx("svg", { className: "h-3 w-3", viewBox: "0 0 24 24", fill: "currentColor", 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" }) }), formatCount(post.stats.comments)] }))] }))] })] }));
|
|
57
58
|
}
|
|
@@ -4,6 +4,7 @@ import { normalizeError } from "../../errors/normalize";
|
|
|
4
4
|
import React, { useState, useCallback } from "react";
|
|
5
5
|
import { Modal } from "../../ui/components/Modal";
|
|
6
6
|
import { Button, Div, Input, Label, Row, Stack, Text, Alert } from "../../ui";
|
|
7
|
+
import { MediaImage } from "./MediaImage";
|
|
7
8
|
import { MediaUploadField } from "./upload/MediaUploadField";
|
|
8
9
|
import { useMediaUpload, useMediaCleanup } from "./index";
|
|
9
10
|
const MEDIA_LIST_ENDPOINT = "/api/admin/media";
|
|
@@ -128,9 +129,7 @@ export function MediaPickerModal({ isOpen, onClose, onSelect, accept = "image/*"
|
|
|
128
129
|
isSelected
|
|
129
130
|
? "border-primary-500 ring-2 ring-primary-300"
|
|
130
131
|
: "border-[var(--appkit-color-border)]",
|
|
131
|
-
].join(" "), "aria-pressed": isSelected, children: [_jsx(Row, { className: "aspect-square w-full", surface: "subtle", align: "center", justify: "center", children: isImage ? (
|
|
132
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
133
|
-
_jsx("img", { src: f.downloadURL, alt: f.name, className: "h-full w-full object-cover", loading: "lazy" })) : (_jsx(Text, { size: "sm", color: "muted", children: f.contentType ?? "file" })) }), _jsx(Text, { className: "block truncate p-1.5 font-mono", size: "xs", children: f.name.split("/").pop() })] }, f.name));
|
|
132
|
+
].join(" "), "aria-pressed": isSelected, children: [_jsx(Row, { className: "relative aspect-square w-full", surface: "subtle", align: "center", justify: "center", children: isImage ? (_jsx(MediaImage, { src: f.downloadURL, alt: f.name, size: "thumbnail" })) : (_jsx(Text, { size: "sm", color: "muted", children: f.contentType ?? "file" })) }), _jsx(Text, { className: "block truncate p-1.5 font-mono", size: "xs", children: f.name.split("/").pop() })] }, f.name));
|
|
134
133
|
}), existingLoading && (_jsx(Text, { paddingY: "lg", className: "col-span-full", align: "center", color: "muted", children: "Loading\u2026" })), !existingLoading && filteredExisting.length === 0 && (_jsx(Text, { paddingY: "lg", className: "col-span-full", align: "center", color: "muted", children: "No files match." }))] })] })), tab === "upload" && (_jsx(MediaUploadField, { label: "File", value: uploadedUrl, onChange: setUploadedUrl, onUpload: handleUpload, accept: accept, maxSizeMB: maxSizeMB, onAbort: handleAbort })), tab === "url" && (_jsxs(Stack, { gap: "sm", children: [_jsx(Label, { htmlFor: "media-picker-url", children: "Image or video URL" }), _jsx(Input, { id: "media-picker-url", value: externalUrl, onChange: (e) => {
|
|
135
134
|
setExternalUrl(e.target.value);
|
|
136
135
|
setUrlError("");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
|
-
import { Alert, Button, Div, Label, Row, Spinner, Stack, Text } from "../../../ui";
|
|
4
|
+
import { Alert, Anchor, Button, Div, Label, Row, Spinner, Stack, Text } from "../../../ui";
|
|
5
5
|
import { MediaImage } from "../MediaImage";
|
|
6
6
|
import { MediaVideo } from "../MediaVideo";
|
|
7
7
|
import { inferMediaTypeFromMime } from "../types/index";
|
|
@@ -107,5 +107,5 @@ export function MediaUploadList({ label, value, onChange, onUpload, accept = "im
|
|
|
107
107
|
unstageUrl(target.url);
|
|
108
108
|
}
|
|
109
109
|
};
|
|
110
|
-
return (_jsxs(Stack, { gap: "sm", children: [_jsx(Label, { className: "block", color: "muted", size: "sm", weight: "medium", children: label }), value.length > 0 && (_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2", children: value.map((item, index) => (_jsxs(Div, { rounded: "lg", padding: "sm", border: "default", surface: "muted", children: [item.type === "video" ? (_jsx(Div, { className: `relative aspect-square max-h-24 ${__O.hidden}`, rounded: "lg", children: _jsx(MediaVideo, { src: item.url, alt: item.alt || `Media ${index + 1}`, controls: true }) })) : item.type === "image" ? (_jsx(Div, { className: `relative aspect-square max-h-24 ${__O.hidden}`, rounded: "lg", children: _jsx(MediaImage, { src: item.url, alt: item.alt || `Media ${index + 1}`, size: "card", objectFit: "contain" }) })) : (_jsx(
|
|
110
|
+
return (_jsxs(Stack, { gap: "sm", children: [_jsx(Label, { className: "block", color: "muted", size: "sm", weight: "medium", children: label }), value.length > 0 && (_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2", children: value.map((item, index) => (_jsxs(Div, { rounded: "lg", padding: "sm", border: "default", surface: "muted", children: [item.type === "video" ? (_jsx(Div, { className: `relative aspect-square max-h-24 ${__O.hidden}`, rounded: "lg", children: _jsx(MediaVideo, { src: item.url, alt: item.alt || `Media ${index + 1}`, controls: true }) })) : item.type === "image" ? (_jsx(Div, { className: `relative aspect-square max-h-24 ${__O.hidden}`, rounded: "lg", children: _jsx(MediaImage, { src: item.url, alt: item.alt || `Media ${index + 1}`, size: "card", objectFit: "contain" }) })) : (_jsx(Anchor, { href: item.url, tone: "none", underline: "none", className: CLS_PDF_LINK, children: fileNameFromUrl(item.url) })), !disabled && (_jsx(Div, { className: "mt-2", children: _jsx(Button, { type: "button", size: "sm", variant: "danger", onClick: () => removeAt(index), children: "Remove" }) }))] }, `${item.url}-${index}`))) })), !disabled && value.length < maxItems && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => inputRef.current?.click(), disabled: isLoading, children: isLoading ? "Uploading..." : `+ Add Files (${value.length}/${maxItems})` })), isLoading && (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: "Uploading" })] })), _jsx("input", { ref: inputRef, type: "file", multiple: true, accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-list-input" }), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error })] }));
|
|
111
111
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Div, Pagination, Row, Span, Stack, Text } from "../../../ui";
|
|
3
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
3
4
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
4
5
|
import { getDefaultLocale } from "../../../core/baseline-resolver";
|
|
5
6
|
const CLS_PRIZE_BADGE = "inline-flex items-center rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-fuchsia-700 dark:text-fuchsia-300";
|
|
@@ -34,7 +35,7 @@ export function OrderCard({ order, onClick, labels = {} }) {
|
|
|
34
35
|
.sort()[0];
|
|
35
36
|
return (_jsxs(Div, { padding: "5", role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: onClick
|
|
36
37
|
? (e) => (e.key === "Enter" || e.key === " ") && onClick(order)
|
|
37
|
-
: undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (_jsxs(Row, { gap: "sm", className: "mt-2", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: [revealsRemaining, " ", revealsRemaining === 1 ? "reveal" : "reveals", " pending"] }), earliestDeadline && (_jsxs(Span, { size: "xs", color: "muted", children: ["before ", new Date(earliestDeadline).toLocaleDateString(getDefaultLocale(), { month: "short", day: "numeric" })] }))] })), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { gap: "sm", children: [item.image && (_jsx("
|
|
38
|
+
: undefined, onClick: onClick ? () => onClick(order) : undefined, className: `border border-neutral-200 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, rounded: "xl", surface: "default", children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5", color: "faint", size: "xs", children: date }))] }), _jsx(Span, { size: "xs", weight: "semibold", className: `capitalize ${statusColor}`, rounded: "full", padding: "pill-md", children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (_jsxs(Row, { gap: "sm", className: "mt-2", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: [revealsRemaining, " ", revealsRemaining === 1 ? "reveal" : "reveals", " pending"] }), earliestDeadline && (_jsxs(Span, { size: "xs", color: "muted", children: ["before ", new Date(earliestDeadline).toLocaleDateString(getDefaultLocale(), { month: "short", day: "numeric" })] }))] })), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { gap: "sm", children: [item.image && (_jsx(Div, { className: "relative h-10 w-10 flex-shrink-0 overflow-hidden", rounded: "lg", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) })), _jsxs(Div, { children: [_jsx(Text, { truncate: true, size: "sm", weight: "medium", color: "primary", children: item.title }), _jsxs(Text, { size: "xs", color: "faint", children: ["\u00D7", item.quantity] })] })] }, i))), order.items.length > 3 && (_jsxs(Span, { size: "xs", className: "self-center", color: "faint", children: ["+", order.items.length - 3, " more"] }))] }), _jsxs(Row, { justify: "between", className: "mt-4 border-t border-neutral-100", padding: "t-sm", children: [_jsxs(Span, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [order.currency ?? "", " Total"] }), _jsx(Span, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: formatCurrency(order.total, order.currency) })] })] }));
|
|
38
39
|
}
|
|
39
40
|
export function OrdersList({ orders, isLoading, onOrderClick, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No orders found", }) {
|
|
40
41
|
if (isLoading) {
|
|
@@ -54,6 +54,7 @@ function PreOrderInfoSection({ title, productionStatus, maxPerUser, deliveryDate
|
|
|
54
54
|
returnable: "Cancellable",
|
|
55
55
|
freeShipping: "Free Shipping",
|
|
56
56
|
codAvailable: "Cash on Delivery",
|
|
57
|
+
emiAvailable: "EMI Available",
|
|
57
58
|
wishlistCount: (n) => `${n} wishlisted`,
|
|
58
59
|
categoryProductCount: (n, cat) => `${n} in ${cat}`,
|
|
59
60
|
} }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-2.5 py-1 text-xs font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category })), !category && categoryName && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-2.5 py-1 text-xs font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand }))] })), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this product" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] }));
|
|
@@ -184,9 +184,8 @@ export const POST = createRouteHandler({
|
|
|
184
184
|
auth: true,
|
|
185
185
|
roles: ["seller", "moderator", "admin"],
|
|
186
186
|
schema: productMutateSchema,
|
|
187
|
-
handler: async ({
|
|
187
|
+
handler: async ({ body }) => {
|
|
188
188
|
const payload = body;
|
|
189
|
-
const userRecord = (user ?? {});
|
|
190
189
|
const { db } = getProviders();
|
|
191
190
|
if (!db) {
|
|
192
191
|
return NextResponse.json({ success: false, error: "Database provider not registered" }, { status: 503 });
|
|
@@ -13,6 +13,7 @@ const CLS_REMOVE_BTN = "shrink-0 rounded-full p-1 text-zinc-400 hover:bg-[var(--
|
|
|
13
13
|
import Link from "next/link";
|
|
14
14
|
import { X } from "lucide-react";
|
|
15
15
|
import { Button, Div, Heading, Row, Span, Stack, Text } from "../../../ui";
|
|
16
|
+
import { MediaImage } from "../../media/MediaImage";
|
|
16
17
|
import { useSwipe } from "../../../react/hooks/useSwipe";
|
|
17
18
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
18
19
|
import { COMPARE_MAX_ITEMS } from "../constants/action-defs";
|
|
@@ -57,9 +58,7 @@ function FieldRow({ label, children }) {
|
|
|
57
58
|
function CompareColumn({ item, productType, labels, onRemove, onClose }) {
|
|
58
59
|
const href = detailHref(item, productType);
|
|
59
60
|
const img = item.mainImage ?? item.images?.[0];
|
|
60
|
-
return (_jsxs(Stack, { gap: "md", className: COLUMN_CARD_CLASS, children: [_jsxs(Row, { justify: "between", align: "start", gap: "sm", children: [_jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, className: "text-sm font-semibold text-[var(--appkit-color-text)] hover:underline line-clamp-2", children: item.title ?? item.id }), onRemove && (_jsx("button", { type: "button", onClick: () => onRemove(item.id), "aria-label": `${labels.remove} ${item.title ?? item.id}`, className: CLS_REMOVE_BTN, children: _jsx(X, { className: "h-4 w-4" }) }))] }), _jsx(FieldRow, { label: labels.field.image, children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, children: _jsx(Div, { className: `relative aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: img ? (
|
|
61
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
62
|
-
_jsx("img", { src: img, alt: item.title ?? item.id, className: "h-full w-full object-cover" })) : null }) }) }), _jsx(FieldRow, { label: labels.field.price, children: _jsx(Text, { size: "base", weight: "semibold", color: "primary", children: priceLabel(item) }) }), _jsx(FieldRow, { label: labels.field.condition, children: _jsx(Text, { children: item.condition ? _jsx(Span, { className: CHIP_CLASS, children: item.condition }) : "—" }) }), _jsx(FieldRow, { label: labels.field.brand, children: _jsx(Text, { children: item.brand ? _jsx(Span, { className: CHIP_CLASS, children: item.brand }) : "—" }) }), _jsx(FieldRow, { label: labels.field.category, children: _jsx(Text, { children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) || (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) ? (_jsx(Span, { className: CHIP_CLASS, children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) ?? (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) })) : ("—") }) }), _jsx(FieldRow, { label: labels.field.store, children: _jsx(Text, { size: "sm", color: "muted", children: item.storeName ?? "—" }) }), _jsx(Button, { asChild: true, variant: "primary", size: "sm", className: "mt-auto", children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, onClick: onClose, children: labels.view }) })] }));
|
|
61
|
+
return (_jsxs(Stack, { gap: "md", className: COLUMN_CARD_CLASS, children: [_jsxs(Row, { justify: "between", align: "start", gap: "sm", children: [_jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, className: "text-sm font-semibold text-[var(--appkit-color-text)] hover:underline line-clamp-2", children: item.title ?? item.id }), onRemove && (_jsx("button", { type: "button", onClick: () => onRemove(item.id), "aria-label": `${labels.remove} ${item.title ?? item.id}`, className: CLS_REMOVE_BTN, children: _jsx(X, { className: "h-4 w-4" }) }))] }), _jsx(FieldRow, { label: labels.field.image, children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, children: _jsx(Div, { className: `relative aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: img ? (_jsx(MediaImage, { src: img, alt: item.title ?? item.id, size: "card" })) : null }) }) }), _jsx(FieldRow, { label: labels.field.price, children: _jsx(Text, { size: "base", weight: "semibold", color: "primary", children: priceLabel(item) }) }), _jsx(FieldRow, { label: labels.field.condition, children: _jsx(Text, { children: item.condition ? _jsx(Span, { className: CHIP_CLASS, children: item.condition }) : "—" }) }), _jsx(FieldRow, { label: labels.field.brand, children: _jsx(Text, { children: item.brand ? _jsx(Span, { className: CHIP_CLASS, children: item.brand }) : "—" }) }), _jsx(FieldRow, { label: labels.field.category, children: _jsx(Text, { children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) || (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) ? (_jsx(Span, { className: CHIP_CLASS, children: (Array.isArray(item.categoryNames) ? item.categoryNames[0] : item.categoryName) ?? (Array.isArray(item.categorySlugs) ? item.categorySlugs[0] : item.category) })) : ("—") }) }), _jsx(FieldRow, { label: labels.field.store, children: _jsx(Text, { size: "sm", color: "muted", children: item.storeName ?? "—" }) }), _jsx(Button, { asChild: true, variant: "primary", size: "sm", className: "mt-auto", children: _jsx(Link, { href: href, target: "_blank", rel: REL_NOOPENER, onClick: onClose, children: labels.view }) })] }));
|
|
63
62
|
}
|
|
64
63
|
function useFetchedProducts(productIds, passedItems, isOpen) {
|
|
65
64
|
const [fetched, setFetched] = useState([]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Row } from "@mohasinac/appkit/ui";
|
|
3
|
-
import { Dd, Dl, Dt, RichText, Stack, Text } from "../../../ui";
|
|
3
|
+
import { Anchor, Dd, Dl, Dt, RichText, Stack, Text } from "../../../ui";
|
|
4
4
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
5
5
|
function renderFieldValue(f) {
|
|
6
6
|
if (f.type === "boolean")
|
|
@@ -10,5 +10,5 @@ function renderFieldValue(f) {
|
|
|
10
10
|
export function CustomSectionTabContent({ section }) {
|
|
11
11
|
const html = section.text ? normalizeRichTextHtml(section.text) : null;
|
|
12
12
|
const fields = section.fields?.filter((f) => f.key && f.value) ?? [];
|
|
13
|
-
return (_jsxs(Stack, { gap: "md", children: [html && (_jsx(RichText, { html: html, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })), fields.length > 0 && (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: fields.map((f, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { weight: "medium", className: "w-36 flex-shrink-0 capitalize", color: "primary", children: f.key }), _jsx(Dd, { className: "flex-1", color: "muted", children: f.type === "url" ? (_jsx(
|
|
13
|
+
return (_jsxs(Stack, { gap: "md", children: [html && (_jsx(RichText, { html: html, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })), fields.length > 0 && (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: fields.map((f, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { weight: "medium", className: "w-36 flex-shrink-0 capitalize", color: "primary", children: f.key }), _jsx(Dd, { className: "flex-1", color: "muted", children: f.type === "url" ? (_jsx(Anchor, { href: f.value, tone: "none", underline: "hover", className: "text-primary-600 dark:text-primary-400 break-all", children: f.value })) : (renderFieldValue(f)) })] }, i))) })), !html && fields.length === 0 && (_jsx(Text, { size: "sm", color: "faint", children: "No content in this section." }))] }));
|
|
14
14
|
}
|
|
@@ -36,12 +36,6 @@ function FeatureIcon({ icon, className = ICON_SIZE_CLASS, }) {
|
|
|
36
36
|
const Cmp = FEATURE_ICON_MAP[icon] ?? FEATURE_ICON_MAP.tag;
|
|
37
37
|
return _jsx(Cmp, { className: className });
|
|
38
38
|
}
|
|
39
|
-
function colorStyleFor(iconColor) {
|
|
40
|
-
if (!iconColor)
|
|
41
|
-
return undefined;
|
|
42
|
-
const ref = `var(${iconColor})`;
|
|
43
|
-
return { color: ref, borderColor: ref };
|
|
44
|
-
}
|
|
45
39
|
export function FeatureBadge({ featureId, features }) {
|
|
46
40
|
const f = features.find((x) => x.id === featureId);
|
|
47
41
|
const spanRef = useRef(null);
|