Article/PurchaseNav/DiscontinuedItem
Both Locales
English
Japanese
Component Source
discontinued-item.tsx
import type { FunctionComponent } from 'preact';
import type { PurchaseNavProduct } from '../types';
import { ProductImage } from '../shared/product-image';
import {
formatProductFullTitle,
getStatusMessages,
BASE_CONTAINER_CLASS,
TEXT_CONTENT_CONTAINER_CLASS,
} from '../constants';
import type { Locale } from '@/lib/i18n/types';
interface DiscontinuedItemProps {
product: PurchaseNavProduct;
locale?: Locale;
}
export const DiscontinuedItem: FunctionComponent<DiscontinuedItemProps> = ({
product,
locale = 'ja',
}) => {
const { name, subtitle, imageSlug, imageMetadata, brandName } = product;
const messages = getStatusMessages(locale);
return (
<div className={BASE_CONTAINER_CLASS}>
<ProductImage
imageSlug={imageSlug}
imageMetadata={imageMetadata}
brandName={brandName}
productName={name}
subtitle={subtitle}
/>
<div className={TEXT_CONTENT_CONTAINER_CLASS}>
<div className="text-sm sm:text-base md:text-lg">
{formatProductFullTitle(brandName, name, subtitle)}
</div>
<div className="md:block">
<div className="inline-block uppercase text-sm md:text-base sm:text-xl italic pr-[10px] mt-vgap-2xs">
Discontinued.
</div>
</div>
<div className="md:block">
<div className="pt-[4px] text-xs md:text-sm hidden md:inline-block">
{messages.discontinued}
</div>
</div>
<div className="italic text-zd-subtext text-xs md:text-sm mt-vgap-2xs">
{messages.discontinuedSpecsNote}
</div>
</div>
</div>
);
};
products.ts
import type { PurchaseNavProduct } from '../types';
/**
* Mock brand display names for testing
*/
export const MOCK_BRAND_NAME = 'OAM';
export const MOCK_BRAND_NAME_TAKAZUDO = 'Takazudo Modular';
/**
* Standard blurHash for mock product images
* Used across all mock products for consistent loading placeholders
*/
export const MOCK_BLURHASH = 'LEHV6nWB2yk8pyo0adR*.7kCMdnj';
/**
* Mock URL constants
*/
export const MOCK_URLS = {
mercariProduct: 'https://mercari-shops.com/shops/mock-shop/items/mock-id',
contact: '/contact/',
} as const;
/**
* Creates a fully-typed mock product for testing.
*
* Mirrors the serializable shape produced at SSR by
* `src/lib/resolve-purchase-nav-client-props.ts` (#476): brand name
* pre-resolved, image slug pre-extracted, optional pre-resolved metadata.
*/
export const createMockProduct = (
overrides: Partial<PurchaseNavProduct> = {},
): PurchaseNavProduct => ({
slug: 'mock-product',
name: 'Mock Product',
brandName: MOCK_BRAND_NAME,
imageSlug: 'oam-tiny-view1',
imageMetadata: undefined,
...overrides,
});
/**
* Pre-configured mock products for PurchaseNav status testing
*/
export const mockMercariProducts = {
available: createMockProduct({
slug: 'mock-available',
name: 'Available Product',
mercariProductId: 'mock-mercari-id-available',
detailHref: '/products/mock-available-intro/',
price: 153800,
}),
sold: createMockProduct({
slug: 'mock-sold',
name: 'Sold Out Product',
mercariProductId: 'mock-mercari-id-sold',
mercariStatus: 'sold',
detailHref: '/products/mock-sold-intro/',
price: 89000,
}),
incoming: createMockProduct({
slug: 'mock-incoming',
name: 'Incoming Product',
mercariProductId: 'mock-mercari-id-incoming',
mercariStatus: 'incoming',
detailHref: '/products/mock-incoming-intro/',
price: 45000,
}),
askToBuy: createMockProduct({
slug: 'mock-ask-to-buy',
name: 'Ask to Buy Product',
mercariProductId: 'mock-mercari-id-asktobuy',
mercariStatus: 'askToBuy',
detailHref: '/products/mock-ask-to-buy-intro/',
price: 275000,
}),
discontinued: createMockProduct({
slug: 'mock-discontinued',
name: 'Discontinued Product',
mercariProductId: 'mock-mercari-id-discontinued',
mercariStatus: 'discontinued',
detailHref: '/products/mock-discontinued-intro/',
price: 32000,
}),
unavailable: createMockProduct({
slug: 'mock-unavailable',
name: 'Unavailable Product',
mercariProductId: 'mock-mercari-id-unavailable',
mercariStatus: 'unavailable',
detailHref: '/products/mock-unavailable-intro/',
price: 28000,
}),
};
/**
* Pre-configured mock products for RelatedProducts testing
*/
export const mockRelatedProducts = {
product1: createMockProduct({
slug: 'mock-product-1',
name: 'Mock Product One',
detailHref: '/products/mock-product-1-intro/',
}),
product2: createMockProduct({
slug: 'mock-product-2',
name: 'Mock Product Two',
detailHref: '/products/mock-product-2-intro/',
}),
product3: createMockProduct({
slug: 'mock-product-3',
name: 'Mock Product Three',
brandName: MOCK_BRAND_NAME_TAKAZUDO,
detailHref: '/products/mock-product-3-intro/',
}),
productNoDetail: createMockProduct({
slug: 'mock-product-no-detail',
name: 'Mock Product Without Detail',
detailHref: undefined,
}),
};
Story Source
discontinued-item.stories.tsx
import { DiscontinuedItem } from './status-items/discontinued-item';
import { mockMercariProducts } from './__mocks__/products';
/**
* DiscontinuedItem Component Stories
*
* Renders the right-column notice for discontinued products:
* - Italic uppercase "Discontinued." label (mirrors IncomingItem register)
* - JA/EN status message
* - Italic gray subline noting specs are preserved for reference
*/
export const meta = {
title: 'Article/PurchaseNav/DiscontinuedItem',
};
const product = mockMercariProducts.discontinued;
/**
* Japanese locale (default)
*/
export const Japanese = () => (
<div className="font-futura bg-zd-dark p-vgap-sm">
<DiscontinuedItem product={product} locale="ja" />
</div>
);
/**
* English locale
*/
export const English = () => (
<div className="font-futura bg-zd-dark p-vgap-sm">
<DiscontinuedItem product={product} locale="en" />
</div>
);
/**
* Both locales side by side
*/
export const BothLocales = () => (
<div className="font-futura bg-zd-dark flex flex-col gap-vgap-sm p-vgap-sm">
<div>
<div className="text-zd-gray text-xs mb-vgap-2xs">JA</div>
<DiscontinuedItem product={product} locale="ja" />
</div>
<div>
<div className="text-zd-gray text-xs mb-vgap-2xs">EN</div>
<DiscontinuedItem product={product} locale="en" />
</div>
</div>
);