update the api
This commit is contained in:
@@ -57,13 +57,7 @@ export default function CommercePage() {
|
||||
|
||||
<FeatureUnavailable
|
||||
title="Orders, products and payments"
|
||||
description="The Loyaly platform records a purchase against a visit and reports revenue in aggregate, but does not yet expose an orders list, a product catalogue, stock levels, payment-method splits or refunds. Those panels are held back rather than filled with sample data."
|
||||
endpoints={[
|
||||
'GET /api/purchases — orders list (POST exists; there is no read)',
|
||||
'GET /api/products — catalogue, categories, stock',
|
||||
'GET /api/reports/payments — method split',
|
||||
'GET /api/reports/refunds',
|
||||
]}
|
||||
description="Sales are recorded as a total against a visit, so revenue and basket size are real. What is not recorded yet is the detail behind them — the individual orders, the product catalogue and stock, how customers paid, and refunds. Those sections stay empty rather than being filled with sample data."
|
||||
/>
|
||||
</VStack>
|
||||
);
|
||||
|
||||
@@ -121,13 +121,7 @@ export default function DashboardPage() {
|
||||
|
||||
<FeatureUnavailable
|
||||
title="Customer activity and engagement"
|
||||
description="Selfies, spins, scratch cards, challenges, referrals, events and their impact on repeat visits and revenue are not part of the Loyaly platform contract yet. These panels previously showed generated figures; they are held back until the resources exist so no number on this page is invented."
|
||||
endpoints={[
|
||||
'GET /api/activities — the activity catalogue and participation',
|
||||
'GET /api/activities/impact — customers, repeat visits, attributed purchases',
|
||||
'GET /api/campaigns — campaign funnels',
|
||||
'GET /api/reports/journey — visit → engage → purchase → return → refer',
|
||||
]}
|
||||
description="Selfies, spins, scratch cards, challenges, referrals and events are not being recorded by any till or app yet, so there is nothing to measure their effect on repeat visits or revenue. As soon as they are, this section fills in on its own — no number here is estimated."
|
||||
/>
|
||||
</VStack>
|
||||
);
|
||||
|
||||
@@ -7,31 +7,27 @@ import {FeatureUnavailable} from '@/shared/components/patterns/FeatureUnavailabl
|
||||
/**
|
||||
* LYTs.
|
||||
*
|
||||
* The entire loyalty programme — reward catalogue, claims, redemptions,
|
||||
* outstanding LYT liability, expiry windows, accrual rate — has no counterpart
|
||||
* in the Loyaly platform contract. Every figure this page used to show was
|
||||
* generated locally, including the "outstanding liability" a merchant would
|
||||
* reasonably read as money they owe.
|
||||
* The merchant-app specification (§2.1) states the product direction does NOT
|
||||
* use loyalty points, LYT balances, redemption or tier calculation — so this
|
||||
* is not a panel waiting on an endpoint, it is a feature the product dropped.
|
||||
* The copy says that, rather than implying a reward catalogue is on its way.
|
||||
*
|
||||
* The page is kept, and states what it needs. Nothing is simulated.
|
||||
* Every figure this page used to show was generated locally, including an
|
||||
* "outstanding liability" in rupees that a merchant would reasonably read as
|
||||
* money they owe. The route is kept so an existing bookmark still lands
|
||||
* somewhere that explains itself. Nothing is simulated.
|
||||
*/
|
||||
export default function LytsPage() {
|
||||
return (
|
||||
<VStack gap={5}>
|
||||
<PageHeader
|
||||
title="Lyts"
|
||||
description="Customer activities and rewards. 1 LYT = ₹1."
|
||||
description="Loyalty rewards are not part of the current product."
|
||||
/>
|
||||
|
||||
<FeatureUnavailable
|
||||
title="The LYT programme"
|
||||
description="Rewards, claims, redemptions and outstanding LYT balance are not part of the platform contract. This page previously showed generated figures — including an outstanding liability in rupees — which a merchant could not tell from real ones."
|
||||
endpoints={[
|
||||
'GET /api/rewards — catalogue, cost in LYTs, status, expiry',
|
||||
'GET /api/rewards/redemptions — claimed vs redeemed over time',
|
||||
'GET /api/lyts/ledger — issued, redeemed, outstanding balance',
|
||||
'GET /api/activities — what earns LYTs, and participation',
|
||||
]}
|
||||
description="Loyalty points are not part of the current product. The merchant app records visits and sales, not point balances, redemptions or tiers — so there is no LYT liability to report here. This page previously showed generated figures, including an outstanding balance in rupees that a merchant could not tell from real money."
|
||||
/>
|
||||
</VStack>
|
||||
);
|
||||
|
||||
@@ -25,8 +25,7 @@ export default async function MerchantProfilePage() {
|
||||
) : (
|
||||
<FeatureUnavailable
|
||||
title="Business profile"
|
||||
description="The platform identifies the signed-in user but does not yet expose the company record this form edits — business name, GSTIN, timezone, currency and LYT accrual. Rather than showing an editable form backed by nothing, it is held until the resource exists."
|
||||
endpoints={['GET /api/settings/profile', 'PATCH /api/settings/profile']}
|
||||
description="Your sign-in details are shown above. The wider company record — business name, GSTIN, registered address, timezone and currency — is not editable here yet, so this section is left out rather than offering a form that would not save."
|
||||
/>
|
||||
)}
|
||||
</SettingsPage>
|
||||
|
||||
@@ -33,12 +33,7 @@ export default function LeaderboardPage() {
|
||||
|
||||
<FeatureUnavailable
|
||||
title="Attendance and performance ranking"
|
||||
description="Ranking staff needs rostering data — shifts, attendance, sales attributed per person — which the platform does not record. /api/team answers who can sign in, not who was on the floor."
|
||||
endpoints={[
|
||||
'GET /api/staff — shop-floor roster, distinct from console accounts',
|
||||
'GET /api/staff/attendance — present, late, absent, on leave',
|
||||
'GET /api/reports/staff-sales — sales attributed per person',
|
||||
]}
|
||||
description="Ranking staff needs shift and attendance records, and sales credited to the person who made them. The platform records who can sign in to this console — not who was on the shop floor, or which sale was theirs. Until the app records that, any ranking here would be guesswork."
|
||||
/>
|
||||
</VStack>
|
||||
);
|
||||
|
||||
@@ -86,11 +86,18 @@ export function toConversion(
|
||||
revenue: current.revenue ?? 0,
|
||||
conversionPct: current.conversion ?? null,
|
||||
basketSize: current.average_basket ?? null,
|
||||
// The server's conversion report is a scalar summary with no time series,
|
||||
// so there is nothing to chart. Empty rather than fabricated.
|
||||
buckets: [],
|
||||
// SalesReport carries no timezone either; the footfall report names it.
|
||||
timezone: null,
|
||||
buckets: (current.points ?? []).map((p) => ({
|
||||
label: p.bucket,
|
||||
// The conversion series carries no visitor split; those belong to the
|
||||
// footfall report and are 0 here so the bucket shape stays uniform.
|
||||
visitors: 0,
|
||||
newVisitors: 0,
|
||||
returningVisitors: 0,
|
||||
purchases: p.baskets ?? 0,
|
||||
revenue: p.revenue ?? 0,
|
||||
conversion: 0,
|
||||
})),
|
||||
timezone: current.timezone ?? null,
|
||||
previousPurchases: previous?.purchasers ?? null,
|
||||
previousRevenue: previous?.revenue ?? null,
|
||||
};
|
||||
|
||||
@@ -242,10 +242,19 @@ export interface ApiFootfallReport {
|
||||
}
|
||||
|
||||
/**
|
||||
* The conversion report is a SCALAR SUMMARY. The server's SalesReport carries
|
||||
* no time series at all, so there is no `buckets`/`points` field to read and
|
||||
* anything charting this over time needs a backend change first.
|
||||
* The conversion report carries both the window totals and a `points` series.
|
||||
* The totals are NOT the sum of the series: `purchasers` is unique people over
|
||||
* the whole window, while a per-bucket `purchasers` counts somebody once per
|
||||
* bucket they bought in.
|
||||
*/
|
||||
export interface ApiSalesPoint {
|
||||
bucket: string;
|
||||
/** Distinct people who bought in THIS bucket. Not summable across buckets. */
|
||||
purchasers: number;
|
||||
baskets: number;
|
||||
revenue: number;
|
||||
}
|
||||
|
||||
export interface ApiConversionReport {
|
||||
visitors: number;
|
||||
/** People who bought — named `purchasers`, not `purchases`. Reading the
|
||||
@@ -255,6 +264,9 @@ export interface ApiConversionReport {
|
||||
revenue: number;
|
||||
average_basket: number;
|
||||
currency?: string;
|
||||
/** The time series, named `points` like the footfall report. */
|
||||
points?: ApiSalesPoint[];
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -7,33 +7,44 @@ import {Icon} from '@astryxdesign/core/Icon';
|
||||
import {ICONS} from '@/shared/utils/icons';
|
||||
|
||||
/**
|
||||
* "This needs a backend resource that does not exist yet."
|
||||
* "This section has nothing to show yet, and here is why."
|
||||
*
|
||||
* ── Why this exists instead of fixture data ──────────────────────────────
|
||||
* Several screens in this console were built against a loyalty domain — LYT
|
||||
* balances, reward catalogues, engagement activities, product inventory — that
|
||||
* the Loyaly platform does not expose. The choice was to keep inventing
|
||||
* numbers or to say so. Invented numbers are worse than a blank panel: a
|
||||
* merchant cannot tell them from real ones, and the first time they act on one
|
||||
* the whole product loses its credibility.
|
||||
* Several screens were built against a domain the platform does not record.
|
||||
* The choice was to keep inventing numbers or to say so. Invented numbers are
|
||||
* worse than a blank panel: a merchant cannot tell them from real ones, and
|
||||
* the first time they act on one the whole product loses its credibility.
|
||||
*
|
||||
* So the screen stays, its layout stays, and the panel states plainly what it
|
||||
* is waiting for. When the endpoint ships, one hook replaces one component.
|
||||
* ── Why there is no `endpoints` prop any more ────────────────────────────
|
||||
* There was. Every panel printed "Required backend resources" and then
|
||||
* `GET /api/staff — shop-floor roster, distinct from console accounts`, in a
|
||||
* monospace font, on a screen a shop owner opens to run their business. That
|
||||
* is an engineering backlog rendered as product copy, and it breaks the same
|
||||
* rule the merchant-app spec sets for the API itself (§35): expose business
|
||||
* operations, not implementation details. A merchant cannot act on a route
|
||||
* name — being shown one only says the product is unfinished.
|
||||
*
|
||||
* Gating it on `NODE_ENV` was tried and is NOT sufficient, which is worth
|
||||
* recording because it looks like it should be: the arrays were passed as
|
||||
* props from each page, so the literals were still constructed and still
|
||||
* shipped in the production bundle — verified by grepping `.next/static` after
|
||||
* a clean build. Not rendering is not the same as not shipping. The prop is
|
||||
* gone instead, and what each section needs is recorded in the page's own
|
||||
* doc comment, where the people who can act on it actually work.
|
||||
*
|
||||
* This is NOT an error state. The platform's own vocabulary has a status for
|
||||
* exactly this — 501, "the feature is off for this deployment, not an error" —
|
||||
* and the treatment is deliberately calm: secondary text, no red, no alarm.
|
||||
*
|
||||
* `description` is merchant copy. Write it as a sentence a shop owner would
|
||||
* accept: what is missing, why, and what would change it — never a route.
|
||||
*/
|
||||
export function FeatureUnavailable({
|
||||
title,
|
||||
description,
|
||||
endpoints,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
/** The API resources this section needs. Naming them turns a dead end into
|
||||
* a work item somebody can pick up. */
|
||||
endpoints?: string[];
|
||||
}) {
|
||||
return (
|
||||
<Card>
|
||||
@@ -46,19 +57,6 @@ export function FeatureUnavailable({
|
||||
<Text size="sm" color="secondary">
|
||||
{description}
|
||||
</Text>
|
||||
|
||||
{endpoints?.length ? (
|
||||
<VStack gap={1}>
|
||||
<Text size="xsm" color="secondary" weight="medium">
|
||||
Required backend resources
|
||||
</Text>
|
||||
{endpoints.map((e) => (
|
||||
<Text key={e} size="xsm" color="disabled" className="font-mono">
|
||||
{e}
|
||||
</Text>
|
||||
))}
|
||||
</VStack>
|
||||
) : null}
|
||||
</VStack>
|
||||
</Card>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user