Admin panel store: author and preview, never buy (#102) - #109
Conversation
The panel's Store tab rendered the buyer's storefront, Buy button and all, and
`POST /api/servers/:id/store/buy` was reachable with the `view` scope. An
operator opening their own store to check how a crate looks could spend
currency by clicking the thing that looks like a preview — and `view` is the
scope you hand someone who should be able to look and nothing else.
The panel is where a store is authored. The buyer is a player on the public
site, signed in with their own Minecraft name and their own balance.
`SF.mode` now tells the shared storefront what it is for. It defaults to
`'preview'`, which is the fail-safe direction: a host that forgets to declare
itself gets a storefront that cannot spend anything. The public site sets
`'buy'`; the panel sets `'preview'` and, when the viewer has the `store` scope,
`SF.canEdit`.
In preview the card action plays the product instead of buying it — a crate
rolls its own pool with its own configured animation, so the operator sees what
a player sees — and a dashed frame above the grid says so. Sold-out products
stay clickable there: with nothing to spend, the one that ran out is the one
most worth opening. Each card and the detail view also carry an Edit button
that opens the real product editor, resolving the id against the admin
catalogue first, because the storefront only holds the public shape and the
editor needs the commands that shape strips. New Item / New Crate sit in the
tab header.
The balance badge went with the Buy button; an operator's own balance is not
information about the store.
**Breaking API change**: `POST /api/v1/servers/{id}/store/buy` is removed, not
re-gated. A store-scoped key can already grant balance and deliver rewards, so
moving the same capability behind a different scope name would only hide it.
Asserting this needed running the pages, not reading them: both paste the same
`STORE_JS`, so every card in both carries the same `sfAction(...)` attribute and
a source grep cannot tell them apart. The smoke runs each page's own bootstrap,
checks the mode each one declares for itself, then presses a card's action and
watches for a request that spends money — required on the site, forbidden in the
panel. The route removal is checked with an owner token, so a refusal cannot
pass for a missing scope.
The double-spend and purchase-audit tests moved to the public site, which is now
the only surface that buys.
…heck .sf-modal is z-index 75 and .pm-modal is 50, both fixed and full-screen, so the product editor opened from an open detail rendered underneath it behind a dimmed backdrop. The same bug as the site login modal fixed in #106, in the affordance this PR adds. sfEdit closes the detail first. The Edit button is gated on SF.canEdit, set from the viewer scopes — but the smoke seeded a store-scoped session and only asserted the button was there, so deleting the check left the test green. The render is now re-run with canEdit false and the button must be gone. sfEdit's failure path used alert(), in the change that removes the panel store's last one. It uses sfNotice.
Self-reviewTwo defects, both in the Edit affordance this PR adds. 1. The product editor opened underneath the detail view it was opened from
Exactly the bug reported on the public site's login modal and fixed in #106, in Asserted, and the assertion was proved able to fail — removing the 2. Nothing proved the
|
Admin panel store: author and preview, never buy (#102)
The panel's Store tab rendered the buyer's storefront, Buy button and all, and
POST /api/servers/:id/store/buywas reachable with theviewscope. Anoperator opening their own store to check how a crate looks could spend
currency by clicking the thing that looks like a preview — and
viewis thescope you hand someone who should be able to look and nothing else.
The panel is where a store is authored. The buyer is a player on the public
site, signed in with their own Minecraft name and their own balance.
SF.modenow tells the shared storefront what it is for. It defaults to'preview', which is the fail-safe direction: a host that forgets to declareitself gets a storefront that cannot spend anything. The public site sets
'buy'; the panel sets'preview'and, when the viewer has thestorescope,SF.canEdit.In preview the card action plays the product instead of buying it — a crate
rolls its own pool with its own configured animation, so the operator sees what
a player sees — and a dashed frame above the grid says so. Sold-out products
stay clickable there: with nothing to spend, the one that ran out is the one
most worth opening. Each card and the detail view also carry an Edit button
that opens the real product editor, resolving the id against the admin
catalogue first, because the storefront only holds the public shape and the
editor needs the commands that shape strips. New Item / New Crate sit in the
tab header.
The balance badge went with the Buy button; an operator's own balance is not
information about the store.
Breaking API change:
POST /api/v1/servers/{id}/store/buyis removed, notre-gated. A store-scoped key can already grant balance and deliver rewards, so
moving the same capability behind a different scope name would only hide it.
Asserting this needed running the pages, not reading them: both paste the same
STORE_JS, so every card in both carries the samesfAction(...)attribute anda source grep cannot tell them apart. The smoke runs each page's own bootstrap,
checks the mode each one declares for itself, then presses a card's action and
watches for a request that spends money — required on the site, forbidden in the
panel. The route removal is checked with an owner token, so a refusal cannot
pass for a missing scope.
The double-spend and purchase-audit tests moved to the public site, which is now
the only surface that buys.