@mohasinac/appkit 4.41.14 โ 4.41.16
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/features/events/actions/event-actions.d.ts +26 -0
- package/dist/features/events/actions/event-actions.js +27 -1
- package/dist/features/faq/components/FAQPageContent.d.ts +2 -0
- package/dist/features/faq/components/FAQPageContent.js +19 -2
- package/dist/features/homepage/components/AnnouncementBar.js +25 -1
- package/dist/features/seller/components/SellerProductsView.js +18 -2
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/package.json +1 -1
|
@@ -64,6 +64,32 @@ export declare function listPublicEvents(params?: string | {
|
|
|
64
64
|
page?: number;
|
|
65
65
|
pageSize?: number;
|
|
66
66
|
}): Promise<FirebaseSieveResult<EventDocument>>;
|
|
67
|
+
/**
|
|
68
|
+
* ๐ ONLY `draft` IS HIDDEN. Every other status is public history.
|
|
69
|
+
*
|
|
70
|
+
* This read `status !== "active"`, which collapsed four distinct states into
|
|
71
|
+
* "this event does not exist":
|
|
72
|
+
*
|
|
73
|
+
* ended a finished raffle โ the thing people come back to read, and the
|
|
74
|
+
* only place `raffleWinnerDisplayName` is ever shown. The seeded
|
|
75
|
+
* `event-won-original-set-raffle` has a real recorded winner and
|
|
76
|
+
* its page answered 404.
|
|
77
|
+
* cancelled should say it was cancelled. Vanishing tells a visitor holding
|
|
78
|
+
* the link that they misremembered it, not that it was called off.
|
|
79
|
+
* paused temporarily not accepting entries, which is not the same as gone.
|
|
80
|
+
* draft genuinely not published yet โ the one case 404 is right for.
|
|
81
|
+
*
|
|
82
|
+
* The rendering layer was ALREADY built for this: `events/[id]/layout.tsx`
|
|
83
|
+
* computes `eventStatus`, derives `isActive`, and gates `showParticipateTab`
|
|
84
|
+
* on it. Participation is refused by that gate, not by hiding the page โ so
|
|
85
|
+
* this filter was the only thing standing between a visitor and content the
|
|
86
|
+
* UI knew how to show.
|
|
87
|
+
*
|
|
88
|
+
* The API (`GET /api/events/[id]`) already returned cancelled and ended events
|
|
89
|
+
* with 200 while the page 404'd them, so the two layers disagreed about what
|
|
90
|
+
* exists. Measured live before the fix:
|
|
91
|
+
* active 200/renders ยท cancelled 200/404 ยท ended 200/404 ยท paused 404/404
|
|
92
|
+
*/
|
|
67
93
|
export declare function getPublicEventById(id: string): Promise<EventDocument | null>;
|
|
68
94
|
export declare function getEventLeaderboard(eventId: string): Promise<import("../types").LeaderboardEntry[]>;
|
|
69
95
|
/**
|
|
@@ -140,9 +140,35 @@ export async function listPublicEvents(params) {
|
|
|
140
140
|
// PUBLIC by name โ it backs the homepage strip and /events.
|
|
141
141
|
return { ...result, items: hidePublicTestData(result.items) };
|
|
142
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* ๐ ONLY `draft` IS HIDDEN. Every other status is public history.
|
|
145
|
+
*
|
|
146
|
+
* This read `status !== "active"`, which collapsed four distinct states into
|
|
147
|
+
* "this event does not exist":
|
|
148
|
+
*
|
|
149
|
+
* ended a finished raffle โ the thing people come back to read, and the
|
|
150
|
+
* only place `raffleWinnerDisplayName` is ever shown. The seeded
|
|
151
|
+
* `event-won-original-set-raffle` has a real recorded winner and
|
|
152
|
+
* its page answered 404.
|
|
153
|
+
* cancelled should say it was cancelled. Vanishing tells a visitor holding
|
|
154
|
+
* the link that they misremembered it, not that it was called off.
|
|
155
|
+
* paused temporarily not accepting entries, which is not the same as gone.
|
|
156
|
+
* draft genuinely not published yet โ the one case 404 is right for.
|
|
157
|
+
*
|
|
158
|
+
* The rendering layer was ALREADY built for this: `events/[id]/layout.tsx`
|
|
159
|
+
* computes `eventStatus`, derives `isActive`, and gates `showParticipateTab`
|
|
160
|
+
* on it. Participation is refused by that gate, not by hiding the page โ so
|
|
161
|
+
* this filter was the only thing standing between a visitor and content the
|
|
162
|
+
* UI knew how to show.
|
|
163
|
+
*
|
|
164
|
+
* The API (`GET /api/events/[id]`) already returned cancelled and ended events
|
|
165
|
+
* with 200 while the page 404'd them, so the two layers disagreed about what
|
|
166
|
+
* exists. Measured live before the fix:
|
|
167
|
+
* active 200/renders ยท cancelled 200/404 ยท ended 200/404 ยท paused 404/404
|
|
168
|
+
*/
|
|
143
169
|
export async function getPublicEventById(id) {
|
|
144
170
|
const event = await eventRepository.findByIdOrSlug(id);
|
|
145
|
-
if (!event || event.status
|
|
171
|
+
if (!event || event.status === "draft")
|
|
146
172
|
return null;
|
|
147
173
|
return event;
|
|
148
174
|
}
|
|
@@ -26,6 +26,8 @@ interface FAQPageContentProps {
|
|
|
26
26
|
resultCount: (count: number) => string;
|
|
27
27
|
inCategory: (categoryLabel: string) => string;
|
|
28
28
|
loading: string;
|
|
29
|
+
emptyTitle?: string;
|
|
30
|
+
emptyDescription?: string;
|
|
29
31
|
contactTitle: string;
|
|
30
32
|
contactDescription: string;
|
|
31
33
|
contactEmailUs: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Alert, Div, Heading, Input, SlottedListingView, Text } from "../../../ui";
|
|
3
|
+
import { Alert, Div, EmptyState, Heading, Input, SlottedListingView, Text } from "../../../ui";
|
|
4
4
|
import { sortBy } from "../../../constants/sort";
|
|
5
5
|
import { FAQCategorySidebar } from "./FAQCategorySidebar";
|
|
6
6
|
import { FAQSortDropdown } from "./FAQSortDropdown";
|
|
@@ -69,7 +69,24 @@ export function FAQPageContent({ initialCategory = "all", categories, routeHelpe
|
|
|
69
69
|
* Saying so costs nothing and makes the next index gap
|
|
70
70
|
* self-reporting rather than a phantom empty state.
|
|
71
71
|
*/
|
|
72
|
-
_jsx(Alert, { variant: "error", title: "Couldn't load questions", children: "Something went wrong fetching these FAQs. Try a different sort or reload the page." })) :
|
|
72
|
+
_jsx(Alert, { variant: "error", title: "Couldn't load questions", children: "Something went wrong fetching these FAQs. Try a different sort or reload the page." })) : faqs.length === 0 ? (
|
|
73
|
+
/*
|
|
74
|
+
* ๐ AN EXPLICIT EMPTY BRANCH โ without it the page rendered a
|
|
75
|
+
* BARE WHITE AREA.
|
|
76
|
+
*
|
|
77
|
+
* The chain fell through to <FAQAccordion faqs={[]} />, which
|
|
78
|
+
* renders nothing at all. Measured on /faqs?q=zzzznope: the
|
|
79
|
+
* page showed "0 questions", the search box, a divider, and
|
|
80
|
+
* then blank space โ no message anywhere in the DOM.
|
|
81
|
+
*
|
|
82
|
+
* A blank region is the worst possible answer here because it
|
|
83
|
+
* is indistinguishable from a page that failed to load, which
|
|
84
|
+
* is exactly the confusion the isError branch above exists to
|
|
85
|
+
* prevent. Three states, three distinct renderings: loading,
|
|
86
|
+
* failed, and genuinely nothing found.
|
|
87
|
+
*/
|
|
88
|
+
_jsx(EmptyState, { title: labels.emptyTitle ?? "No questions found", description: labels.emptyDescription ??
|
|
89
|
+
"Try a different search term, or pick another category from the list." })) : renderAccordion ? (renderAccordion(faqs)) : (_jsx(FAQAccordion, { faqs: faqs })), renderPagination: () => renderPagination?.(total) ?? null, total: total, isLoading: isLoading }), faqs.length > 0 ? (_jsx(Div, { className: "mt-12", children: _jsx(ContactCTA, { email: contact.email, phone: contact.phone, contactHref: routeHelpers.contactHref, labels: {
|
|
73
90
|
title: labels.contactTitle,
|
|
74
91
|
description: labels.contactDescription,
|
|
75
92
|
emailUs: labels.contactEmailUs,
|
|
@@ -55,8 +55,32 @@ export function AnnouncementBar({ message, link, className = "", onDismiss, over
|
|
|
55
55
|
}, [message, userAlreadyDismissed]);
|
|
56
56
|
if (dismissed)
|
|
57
57
|
return null;
|
|
58
|
+
/*
|
|
59
|
+
* ๐ THE OVERLAY IS md AND UP ONLY โ IT PAINTED OVER THE HERO HEADING ON
|
|
60
|
+
* EVERY PHONE WIDTH.
|
|
61
|
+
*
|
|
62
|
+
* `absolute` reserves no space, which is the whole point of the overlay: the
|
|
63
|
+
* hero's own top padding is sized to sit a ONE-LINE banner over it. A real
|
|
64
|
+
* announcement wraps on a narrow screen, grows downward, and there is
|
|
65
|
+
* nothing to stop it.
|
|
66
|
+
*
|
|
67
|
+
* Measured on production against the live announcement, against the <h1>:
|
|
68
|
+
*
|
|
69
|
+
* 320px โ 30px overlap 768px โ 10px clear
|
|
70
|
+
* 390px โ 10px overlap 1280px โ 10px clear
|
|
71
|
+
* 430px โ 10px overlap
|
|
72
|
+
*
|
|
73
|
+
* So it was every phone width and no tablet or desktop width โ which is why
|
|
74
|
+
* it survived: the breakpoints people check it at are the ones where the
|
|
75
|
+
* message still fits on one line.
|
|
76
|
+
*
|
|
77
|
+
* Below `md` it is an ordinary in-flow bar that pushes the hero down, with a
|
|
78
|
+
* SOLID background (the translucent + blur treatment only reads as
|
|
79
|
+
* deliberate when there is artwork behind it). From `md` up, the designed
|
|
80
|
+
* overlay is unchanged.
|
|
81
|
+
*/
|
|
58
82
|
const positionCls = overlay
|
|
59
|
-
? "absolute inset-x-0 top-0 z-20 bg-primary-700/85 backdrop-blur-sm"
|
|
83
|
+
? "relative md:absolute inset-x-0 top-0 z-20 bg-primary-700 md:bg-primary-700/85 md:backdrop-blur-sm"
|
|
60
84
|
: "relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-primary-700";
|
|
61
85
|
return (_jsx(Div, { className: `${positionCls} transition-opacity duration-200 ${mounted ? "opacity-100" : "opacity-0"} ${className}`, role: "banner", children: _jsx(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: _jsxs(Row, { textWeight: "medium", textSize: "sm", className: "relative text-left text-white", padding: "y-xs", align: "center", justify: "start", children: [link ? (_jsx(TextLink, { href: link, className: "text-white underline", children: message })) : (_jsx(Span, { children: message })), _jsx(IconButton, { type: "button", onClick: () => {
|
|
62
86
|
saveLocalDismissal(message);
|
|
@@ -252,11 +252,27 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, }) {
|
|
|
252
252
|
.filter(Boolean)
|
|
253
253
|
.join(" ยท ")
|
|
254
254
|
: "";
|
|
255
|
+
/*
|
|
256
|
+
* ๐ THE STATUS GOES IN THE VISIBLE LINE, NOT ONLY IN `status`.
|
|
257
|
+
*
|
|
258
|
+
* `status` is mapped correctly and the API does emit it (measured:
|
|
259
|
+
* "published" on every row) โ but this view's right-hand slot holds
|
|
260
|
+
* Edit / Duplicate / Delete, so unlike the admin table there is no
|
|
261
|
+
* status chip and the value was never rendered anywhere. A seller
|
|
262
|
+
* could not tell which of their listings were live without opening
|
|
263
|
+
* each one, and a checklist case that begins "find a listing whose
|
|
264
|
+
* status reads Published" had nothing to read.
|
|
265
|
+
*
|
|
266
|
+
* Same family as Root Cause #52: the data was already on the row and
|
|
267
|
+
* the UI simply never presented it.
|
|
268
|
+
*/
|
|
269
|
+
const statusLabel = toStringValue(item.status, "draft");
|
|
270
|
+
const secondaryBase = auctionSecondary || toStringValue(item.condition, "");
|
|
255
271
|
return {
|
|
256
272
|
id: toStringValue(item.id, `product-${index}`),
|
|
257
273
|
primary: toStringValue(item.title ?? item.name, "Untitled product"),
|
|
258
|
-
secondary:
|
|
259
|
-
status:
|
|
274
|
+
secondary: [secondaryBase, statusLabel].filter(Boolean).join(" ยท "),
|
|
275
|
+
status: statusLabel,
|
|
260
276
|
updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
|
|
261
277
|
imageUrl: toStringValue(item.mainImage ?? item.images?.[0], undefined),
|
|
262
278
|
image: toStringValue(item.mainImage ?? item.images?.[0], undefined),
|