fix(cart): say it is a demo where the customer can see it (#195)
Production runs demo mode with no PayPal credentials — that combination is the whole reason #191 turned it on — and in exactly that configuration the storefront rendered a full-width primary button reading plainly `Checkout`. The `(Demo)` suffix was gated on a PayPal client id being present, so the one configuration that needs the word was the only one that never got it. The button is not decorative. It posts to `/demo/purchase`, which marks the item `sold`, writes a `completed` row into `orders` at the real price, and emails everyone who favorited it through production's real SMTP. Nobody is charged, which is what the compose banner promises and is true — but a customer cannot tell they have placed a pretend order, the inventory says otherwise, other customers are told it sold, and nobody is expecting to ship anything. #190, #191 and #192 all reason carefully about not charging by accident; none of them consider accepting an order by accident. Three things now say so, because one of them was never going to be enough: The label is unconditional. `type` still follows the PayPal client id — secondary when real PayPal buttons sit above it, primary when it is the only way to check out — and that distinction is worth keeping, but it is about prominence rather than about what the order is. A notice sits above it for the whole of demo mode, before an address is picked and whether or not PayPal is configured. A parenthesis on the control someone has already decided to press is the weakest possible moment to tell them. The confirmation stopped saying `Order complete!`, which is exactly what a real order says. It now names the two things a customer would otherwise assume: nothing was charged, and nothing will be shipped. Three end-to-end tests, written first and failing first against a browser — the label assertion failed with `Expected "Checkout (Demo)", Received "Checkout"` on an `ant-btn-primary ant-btn-block` element, which is the defect exactly as reported. The suite already runs `DEMO_MODE=true` with no PayPal credentials, so it reproduces production's configuration without any new fixture. `CartPage.checkoutButton` matches the label by prefix rather than in full, deliberately: a locator naming the correct label would have gone looking for the right button and found nothing, which is how a test for this can quietly pass by being wrong in the same direction as the bug. Verified: 3 new tests pass, frontend build clean, lint 0 errors, 25 unit tests pass, and the cart-countdown and orders suites still pass. The favorites and favorites-filter suites fail here, and fail identically with this change stashed — 9 failures without it, 8 with — so they are pre-existing and not from this. Worth their own issue. Closes #195 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import List from 'antd/es/list';
|
||||
import Button from 'antd/es/button';
|
||||
import Empty from 'antd/es/empty';
|
||||
import Card from 'antd/es/card';
|
||||
import Alert from 'antd/es/alert';
|
||||
import Radio from 'antd/es/radio';
|
||||
import Form from 'antd/es/form';
|
||||
import Input from 'antd/es/input';
|
||||
@@ -128,7 +129,10 @@ export default function Cart() {
|
||||
setCheckingOut(true);
|
||||
try {
|
||||
await demoCartPurchase(selectedAddressId);
|
||||
message.success('Order complete!');
|
||||
// The item really is marked sold and everyone who favorited it really is
|
||||
// emailed, so the one moment the customer is told what happened is the one
|
||||
// moment a demo order has to stop looking like a real one. #195.
|
||||
message.success('Demo order complete — nothing was charged and nothing will be shipped.');
|
||||
refreshCartContext();
|
||||
loadAll();
|
||||
} catch (err) {
|
||||
@@ -235,17 +239,38 @@ export default function Cart() {
|
||||
</Card>
|
||||
|
||||
<Card title="Checkout" style={{ marginTop: 24 }}>
|
||||
{/*
|
||||
Shown for the whole of demo mode: before an address is picked,
|
||||
and whether or not PayPal is configured. The word on the button
|
||||
is the smaller half of this — it asks the customer to notice a
|
||||
parenthesis on the control they have already decided to press.
|
||||
*/}
|
||||
{config?.demoMode && (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
style={{ marginBottom: 12 }}
|
||||
message="Demonstration only"
|
||||
description="This shop is not taking payments at the moment. Placing an order here costs nothing, and nothing will be shipped."
|
||||
/>
|
||||
)}
|
||||
{!selectedAddressId && <Text type="warning">Select a shipping address to check out.</Text>}
|
||||
{config?.paypalClientId && selectedAddressId && <div id="paypal-cart-buttons" />}
|
||||
{config?.demoMode && selectedAddressId && (
|
||||
<Button
|
||||
block
|
||||
// Secondary when real PayPal buttons sit above it, primary when
|
||||
// it is the only way to check out. The label does not follow
|
||||
// that: a demo order is a demo order either way, and gating the
|
||||
// word on a PayPal client id meant production — which runs demo
|
||||
// mode precisely because it has no PayPal credentials — was the
|
||||
// one place the label never appeared. #195.
|
||||
type={config.paypalClientId ? 'default' : 'primary'}
|
||||
style={{ marginTop: 8 }}
|
||||
loading={checkingOut}
|
||||
onClick={handleDemoCheckout}
|
||||
>
|
||||
{config.paypalClientId ? 'Checkout (Demo)' : 'Checkout'}
|
||||
Checkout (Demo)
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user