Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fa7be22
(SP: 1) [SHOP] harden Stripe terminal-state late-success handling and…
liudmylasovetovs Apr 2, 2026
7101995
(SP: 1) [SHOP] prove Stripe webhook replay correctness with focused r…
liudmylasovetovs Apr 2, 2026
3826d61
(SP: 1) [SHOP] repair stale Stripe payment contract tests for current…
liudmylasovetovs Apr 2, 2026
9a7e078
(SP: 1) [SHOP] block unsafe admin stock overwrite to preserve reserve…
liudmylasovetovs Apr 2, 2026
c0dd478
(SP: 1) [SHOP] enforce completeness validation on admin product activ…
liudmylasovetovs Apr 2, 2026
c182071
(SP: 1) [SHOP] remove float-based price fallback from checkout and ca…
liudmylasovetovs Apr 2, 2026
e944f06
(SP: 1) [SHOP] restore last-unit concurrency proof and oversell prote…
liudmylasovetovs Apr 2, 2026
fd3fdc2
(SP: 1) [SHOP] harden carrier-boundary shipment idempotency and paylo…
liudmylasovetovs Apr 2, 2026
c55eb21
(SP: 1) [SHOP] enforce canonical shipment success deduplication and c…
liudmylasovetovs Apr 2, 2026
ac05337
(SP: 1) [SHOP] make shipment worker terminal states explicit and keep…
liudmylasovetovs Apr 2, 2026
b25bebe
(SP: 1) [SHOP] fail-close quote-affecting admin shipping edits to pre…
liudmylasovetovs Apr 2, 2026
9f5760d
(SP: 2) [SHOP] include recipient in shipping idempotency fingerprint …
liudmylasovetovs Apr 3, 2026
d4956ab
(SP: 2) [SHOP] enforce canonical legal consent version pinning in che…
liudmylasovetovs Apr 3, 2026
7f2a4f2
(SP: 1) [SHOP] classify checkout validation and business errors as 42…
liudmylasovetovs Apr 3, 2026
4c90e5a
(SP: 1) [SHOP] prove checkout idempotency-key and user identity fail-…
liudmylasovetovs Apr 3, 2026
acda65c
(SP: 1) [SHOP] prove checkout rejects inactive-after-cart products wi…
liudmylasovetovs Apr 3, 2026
a857d83
(SP: 1) [SHOP] wire public seller address through env-backed config
liudmylasovetovs Apr 3, 2026
b5ba58b
(SP: 1) [SHOP] align returns policy wording with current runtime beha…
liudmylasovetovs Apr 3, 2026
c3ddfa2
(SP: 2) [SHOP] enforce fail-fast validation at shop-owned env boundaries
liudmylasovetovs Apr 3, 2026
7632e4b
(SP: 2) [SHOP] make runtime intent explicit on critical shop payment …
liudmylasovetovs Apr 3, 2026
62fd62b
(SP: 2) [SHOP] ops safety: env fail-fast, runtime explicitness, NFR s…
liudmylasovetovs Apr 3, 2026
7f77473
(SP: 2) [SHOP] repair stale test contracts across admin pricing, webh…
liudmylasovetovs Apr 3, 2026
b2eb925
(SP: 1) [SHOP] prove admin lifecycle concurrency and harden cancel re…
liudmylasovetovs Apr 3, 2026
1da2998
(SP: 1) [SHOP] document non-blocking admin audit behavior (best-effort)
liudmylasovetovs Apr 3, 2026
c8c80ee
(SP: 1) [SHOP] narrow notification projector to projection-only and f…
liudmylasovetovs Apr 3, 2026
c6ca408
(SP: 1) [SHOP] harden canonical notification event writes with inline…
liudmylasovetovs Apr 3, 2026
2fc774b
(SP: 1) [SHOP] keep size guide visible on unavailable product pages
liudmylasovetovs Apr 3, 2026
145946e
(SP: 3) [SHOP] address review feedback: harden checkout contracts, re…
liudmylasovetovs Apr 4, 2026
175ef56
(SP: 3) [SHOP] Close post-review checkout and notification test follo…
liudmylasovetovs Apr 4, 2026
b732609
(SP: 3) [SHOP] Close post-review replay, Monobank, and test-proof fol…
liudmylasovetovs Apr 4, 2026
88fef50
(SP: 1) [SHOP] Tighten shipping worker and admin shipping test proofs
liudmylasovetovs Apr 4, 2026
358ff9b
(SP: 1) [SHOP] Fix carrier-create conflict fixture to use shared cano…
liudmylasovetovs Apr 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ GMAIL_USER=
# Set explicitly in production to avoid incorrect absolute URLs.
SHOP_BASE_URL=

# Optional public seller address used by the Shop seller-information legal page.
# Leave empty to keep the current placeholder-based public rendering until launch data is finalized.
SHOP_SELLER_ADDRESS=

# Policy/consent version labels used by Shop flows.
SHOP_PRIVACY_VERSION=privacy-v1
SHOP_TERMS_VERSION=terms-v1
Expand Down
9 changes: 8 additions & 1 deletion frontend/app/[locale]/shop/products/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getMessages, getTranslations } from 'next-intl/server';

import { AddToCartButton } from '@/components/shop/AddToCartButton';
import { ProductGallery } from '@/components/shop/ProductGallery';
import { SizeGuideAccordion } from '@/components/shop/SizeGuideAccordion';
import { Link } from '@/i18n/routing';
import { getStorefrontAvailabilityState } from '@/lib/shop/availability';
import { formatMoney } from '@/lib/shop/currency';
Expand Down Expand Up @@ -39,7 +40,7 @@ export default async function ProductPage({
const commerceProduct =
result.kind === 'available' ? result.commerceProduct : null;
const availabilityState = getStorefrontAvailabilityState(commerceProduct);
const sizeGuide = getApparelSizeGuideForProduct(commerceProduct, locale);
const sizeGuide = getApparelSizeGuideForProduct(product, locale);
const galleryImages = getProductGalleryImages(product);

const NAV_LINK = cn(
Expand Down Expand Up @@ -146,6 +147,12 @@ export default async function ProductPage({
);
})()}

{commerceProduct === null && sizeGuide ? (
<section className="mt-6" aria-label="Size guide">
<SizeGuideAccordion sizeGuide={sizeGuide} />
</section>
) : null}

{commerceProduct ? (
<section aria-label="Purchase">
<AddToCartButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { cancelMonobankUnpaidPayment } from '@/lib/services/orders/monobank-canc
import { writeAdminAudit } from '@/lib/services/shop/events/write-admin-audit';
import { orderIdParamSchema, orderSummarySchema } from '@/lib/validation/shop';

export const runtime = 'nodejs';

function noStoreJson(body: unknown, init?: { status?: number }) {
const res = NextResponse.json(body, { status: init?.status ?? 200 });
res.headers.set('Cache-Control', 'no-store');
Expand Down
2 changes: 2 additions & 0 deletions frontend/app/api/shop/admin/orders/[id]/refund/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import { refundOrder } from '@/lib/services/orders';
import { writeAdminAudit } from '@/lib/services/shop/events/write-admin-audit';
import { orderIdParamSchema, orderSummarySchema } from '@/lib/validation/shop';

export const runtime = 'nodejs';

function noStoreJson(body: unknown, init?: { status?: number }) {
const res = NextResponse.json(body, { status: init?.status ?? 200 });
res.headers.set('Cache-Control', 'no-store');
Expand Down
42 changes: 42 additions & 0 deletions frontend/app/api/shop/admin/products/[id]/status/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ProductNotFoundError } from '@/lib/errors/products';
import { logError, logWarn } from '@/lib/logging';
import { requireAdminCsrf } from '@/lib/security/admin-csrf';
import { guardBrowserSameOrigin } from '@/lib/security/origin';
import { InvalidPayloadError, PriceConfigError } from '@/lib/services/errors';
import { toggleProductStatus } from '@/lib/services/products';
import { writeAdminAudit } from '@/lib/services/shop/events/write-admin-audit';

Expand Down Expand Up @@ -174,6 +175,47 @@ export async function PATCH(
);
}

if (error instanceof PriceConfigError) {
logWarn('admin_product_status_price_config_error', {
...baseMeta,
code: error.code,
productId: productIdForLog,
currency: error.currency,
durationMs: Date.now() - startedAtMs,
});

return noStoreJson(
{
error: error.message,
code: error.code,
productId: error.productId,
currency: error.currency,
field: 'prices',
},
{ status: 400 }
);
}

if (error instanceof InvalidPayloadError) {
logWarn('admin_product_status_invalid_payload_error', {
...baseMeta,
code: error.code,
productId: productIdForLog,
field: error.field,
durationMs: Date.now() - startedAtMs,
});

return noStoreJson(
{
error: error.message || 'Invalid product state',
code: error.code,
field: error.field,
details: error.details,
},
{ status: 400 }
);
}
Comment thread
liudmylasovetovs marked this conversation as resolved.

logError('admin_product_status_failed', error, {
...baseMeta,
code: 'ADMIN_PRODUCT_STATUS_FAILED',
Expand Down
Loading
Loading