This commit is contained in:
@@ -17,6 +17,8 @@ import {
|
||||
marketValueOverrides,
|
||||
marketValuations,
|
||||
products,
|
||||
sellers,
|
||||
sellerSettings,
|
||||
} from "../../../infrastructure/database/schema.js";
|
||||
|
||||
export function createListingRepository(db) {
|
||||
@@ -220,7 +222,12 @@ export function createListingRepository(db) {
|
||||
|
||||
async findPublicById(listingId) {
|
||||
const [result] = await db
|
||||
.select({ listing: listings, condition: conditions })
|
||||
.select({
|
||||
listing: listings,
|
||||
condition: conditions,
|
||||
sellerDisplayName: sellers.displayName,
|
||||
sellerSettings: sellerSettings.settings,
|
||||
})
|
||||
.from(listings)
|
||||
.innerJoin(
|
||||
inventoryItems,
|
||||
@@ -228,6 +235,8 @@ export function createListingRepository(db) {
|
||||
)
|
||||
.innerJoin(products, eq(products.id, inventoryItems.productId))
|
||||
.innerJoin(conditions, eq(conditions.id, inventoryItems.conditionId))
|
||||
.innerJoin(sellers, eq(sellers.id, products.sellerId))
|
||||
.leftJoin(sellerSettings, eq(sellerSettings.sellerId, sellers.id))
|
||||
.where(
|
||||
and(
|
||||
eq(listings.id, listingId),
|
||||
@@ -263,6 +272,9 @@ export function createListingRepository(db) {
|
||||
marketValueMinor: override?.valueMinor ?? valuation?.valueMinor ?? null,
|
||||
marketValueCurrency: override?.currency ?? valuation?.currency ?? null,
|
||||
marketValueUpdatedAt: override?.createdAt ?? valuation?.calculatedAt ?? null,
|
||||
sellerDisplayName: result.sellerDisplayName,
|
||||
sellerContactEmail: result.sellerSettings?.general?.publicContactEmail ?? null,
|
||||
sellerContactPhone: result.sellerSettings?.general?.publicContactPhone ?? null,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
inventoryItems,
|
||||
listings,
|
||||
products,
|
||||
sellers,
|
||||
sellerSettings,
|
||||
} from '../../../infrastructure/database/schema.js';
|
||||
|
||||
export function createProductRepository(db) {
|
||||
@@ -24,10 +26,16 @@ export function createProductRepository(db) {
|
||||
|
||||
async findPublicBySlug(slug) {
|
||||
const [result] = await db
|
||||
.select({ product: products })
|
||||
.select({
|
||||
product: products,
|
||||
sellerDisplayName: sellers.displayName,
|
||||
sellerSettings: sellerSettings.settings,
|
||||
})
|
||||
.from(products)
|
||||
.innerJoin(inventoryItems, eq(inventoryItems.productId, products.id))
|
||||
.innerJoin(listings, eq(listings.inventoryItemId, inventoryItems.id))
|
||||
.innerJoin(sellers, eq(sellers.id, products.sellerId))
|
||||
.leftJoin(sellerSettings, eq(sellerSettings.sellerId, sellers.id))
|
||||
.where(and(
|
||||
eq(products.slug, slug),
|
||||
eq(inventoryItems.status, 'ACTIVE'),
|
||||
@@ -35,7 +43,13 @@ export function createProductRepository(db) {
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
return result?.product ?? null;
|
||||
if (!result) return null;
|
||||
return {
|
||||
...result.product,
|
||||
sellerDisplayName: result.sellerDisplayName,
|
||||
sellerContactEmail: result.sellerSettings?.general?.publicContactEmail ?? null,
|
||||
sellerContactPhone: result.sellerSettings?.general?.publicContactPhone ?? null,
|
||||
};
|
||||
},
|
||||
|
||||
async findBySellerIdAndSlug(sellerId, slug) {
|
||||
|
||||
@@ -36,6 +36,9 @@ function createTestApp() {
|
||||
conditionId: 'condition-1',
|
||||
askingPriceMinor: 2499,
|
||||
status: 'PENDING',
|
||||
sellerDisplayName: 'Pika Collectibles',
|
||||
sellerContactEmail: 'hello@pika.example',
|
||||
sellerContactPhone: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -72,6 +75,8 @@ test('returns a public listing detail or a not-found response', async (testConte
|
||||
|
||||
assert.equal(detailResponse.statusCode, 200);
|
||||
assert.equal('inventoryItemId' in detailResponse.json().data, false);
|
||||
assert.equal(detailResponse.json().data.sellerDisplayName, 'Pika Collectibles');
|
||||
assert.equal(detailResponse.json().data.sellerContactEmail, 'hello@pika.example');
|
||||
assert.equal(missingResponse.statusCode, 404);
|
||||
assert.equal(missingResponse.json().error.code, 'LISTING_NOT_FOUND');
|
||||
});
|
||||
@@ -21,6 +21,9 @@ function createTestApp() {
|
||||
name: 'Pikachu Plush',
|
||||
slug,
|
||||
description: 'A friendly plush.',
|
||||
sellerDisplayName: 'Pika Collectibles',
|
||||
sellerContactEmail: 'hello@pika.example',
|
||||
sellerContactPhone: null,
|
||||
createdAt: new Date('2026-09-07T00:00:00.000Z'),
|
||||
updatedAt: new Date('2026-09-07T00:00:00.000Z'),
|
||||
};
|
||||
@@ -51,6 +54,9 @@ test('returns a public product without private seller identity', async (testCont
|
||||
eventName: null,
|
||||
releaseYear: null,
|
||||
tags: [],
|
||||
sellerDisplayName: 'Pika Collectibles',
|
||||
sellerContactEmail: 'hello@pika.example',
|
||||
sellerContactPhone: null,
|
||||
createdAt: '2026-09-07T00:00:00.000Z',
|
||||
updatedAt: '2026-09-07T00:00:00.000Z',
|
||||
});
|
||||
|
||||
@@ -24,6 +24,9 @@ export const ListingDetail = {
|
||||
{ id: "front", label: "FRONT", alt: "Front of Charizard ex" },
|
||||
{ id: "back", label: "BACK", alt: "Back of Charizard ex" },
|
||||
],
|
||||
sellerDisplayName: "Pika Collectibles",
|
||||
sellerContactEmail: "hello@pika.example",
|
||||
sellerContactPhone: "555-0100",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ defineProps({
|
||||
description: { type: String, default: "" },
|
||||
facts: { type: Array, default: () => [] },
|
||||
images: { type: Array, default: () => [] },
|
||||
sellerDisplayName: { type: String, default: "" },
|
||||
sellerContactEmail: { type: String, default: "" },
|
||||
sellerContactPhone: { type: String, default: "" },
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -47,6 +50,14 @@ defineProps({
|
||||
<dd>{{ fact.value }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<section v-if="sellerDisplayName" class="collectible-detail__seller" aria-labelledby="collectible-detail-seller-title">
|
||||
<p id="collectible-detail-seller-title" class="catalog-kicker">Sold by</p>
|
||||
<p class="collectible-detail__seller-name">{{ sellerDisplayName }}</p>
|
||||
<ul v-if="sellerContactEmail || sellerContactPhone" class="collectible-detail__seller-contacts">
|
||||
<li v-if="sellerContactEmail"><a :href="`mailto:${sellerContactEmail}`">{{ sellerContactEmail }}</a></li>
|
||||
<li v-if="sellerContactPhone"><a :href="`tel:${sellerContactPhone}`">{{ sellerContactPhone }}</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
@@ -72,6 +83,23 @@ defineProps({
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
.collectible-detail__seller {
|
||||
border-top: var(--border-thin) solid var(--color-border);
|
||||
padding-top: var(--space-4);
|
||||
}
|
||||
.collectible-detail__seller-name {
|
||||
font-weight: var(--weight-bold);
|
||||
}
|
||||
.collectible-detail__seller-contacts {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
margin: var(--space-2) 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.collectible-detail__seller-contacts a {
|
||||
color: var(--color-interactive-text);
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.collectible-detail {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -45,6 +45,9 @@ onMounted(async () => {
|
||||
"
|
||||
:description="listing.notes"
|
||||
:images="listing.images"
|
||||
:seller-display-name="listing.sellerDisplayName"
|
||||
:seller-contact-email="listing.sellerContactEmail"
|
||||
:seller-contact-phone="listing.sellerContactPhone"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@@ -37,6 +37,9 @@ onMounted(async () => {
|
||||
...(product.cardNumber ? [{ label: 'Card number', value: product.cardNumber }] : []),
|
||||
...(product.releaseYear ? [{ label: 'Release year', value: product.releaseYear }] : []),
|
||||
]"
|
||||
:seller-display-name="product.sellerDisplayName"
|
||||
:seller-contact-email="product.sellerContactEmail"
|
||||
:seller-contact-phone="product.sellerContactPhone"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
Reference in New Issue
Block a user