This commit is contained in:
@@ -56,6 +56,7 @@ h1 { max-width: 760px; margin: var(--space-3) 0 var(--space-4); font-size: var(-
|
||||
.admin-list { margin-top: var(--space-8); }
|
||||
.admin-list h2 { margin-bottom: var(--space-3); font-size: var(--text-xl); }
|
||||
.admin-list ul { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
|
||||
.admin-listing-stack { display: grid; gap: var(--space-3); }
|
||||
.admin-list li { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; border-bottom: var(--border-thin) solid var(--color-border); padding: var(--space-3) 0; }
|
||||
.admin-list li span { color: var(--color-text-muted); font-size: var(--text-sm); }
|
||||
.admin-list a { color: var(--color-interactive-text); font-weight: var(--weight-bold); }
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { expect, userEvent, within } from '@storybook/test';
|
||||
|
||||
import AdminListingStatus from './AdminListingStatus.vue';
|
||||
|
||||
const listing = { id: 'listing-1', productName: 'Charizard ex #199/165', askingPriceMinor: 11500, status: 'DRAFT' };
|
||||
|
||||
export default { title: 'Molecules/AdminListingStatus', component: AdminListingStatus };
|
||||
export const DraftReady = { args: { listing, blockers: [] } };
|
||||
export const DraftBlockedByImages = { args: { listing, blockers: ['Add at least one completed image before publishing.'] } };
|
||||
export const Publishing = { args: { listing, blockers: [], isSaving: true } };
|
||||
export const PublishError = { args: { listing, blockers: [], errorMessage: 'Listing requirements are not met.' } };
|
||||
export const KeyboardPublish = {
|
||||
args: { listing, blockers: [] },
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(canvas.getByRole('button', { name: 'Publish' }));
|
||||
await expect(canvas.getByRole('button', { name: 'Publish' })).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
listing: { type: Object, required: true },
|
||||
blockers: { type: Array, default: () => [] },
|
||||
isSaving: { type: Boolean, default: false },
|
||||
errorMessage: { type: String, default: '' },
|
||||
});
|
||||
defineEmits(['publish', 'pending']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="admin-listing-status">
|
||||
<div class="admin-listing-status__identity">
|
||||
<p class="catalog-kicker">Listing</p>
|
||||
<h3>{{ listing.productName }}</h3>
|
||||
<span class="fixture-label">${{ (listing.askingPriceMinor / 100).toFixed(2) }}</span>
|
||||
</div>
|
||||
<span class="status" :class="`status--${listing.status.toLowerCase()}`">{{ listing.status }}</span>
|
||||
<ul v-if="blockers.length" class="admin-listing-status__blockers" aria-label="Publication blockers">
|
||||
<li v-for="blocker in blockers" :key="blocker">{{ blocker }}</li>
|
||||
</ul>
|
||||
<p v-if="errorMessage" class="state state--error" role="alert">{{ errorMessage }}</p>
|
||||
<div class="admin-listing-status__actions">
|
||||
<button v-if="listing.status === 'DRAFT'" class="button button--primary" type="button" :disabled="isSaving || blockers.length > 0" @click="$emit('publish')">{{ isSaving ? 'Publishing...' : 'Publish' }}</button>
|
||||
<button v-if="listing.status === 'DRAFT'" class="button button--secondary" type="button" :disabled="isSaving" @click="$emit('pending')">Mark pending</button>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-listing-status { display: grid; grid-template-columns: 1fr auto; gap: var(--space-3); border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-lg); padding: var(--space-4); background: var(--color-surface); }
|
||||
.admin-listing-status h3 { font-size: var(--text-lg); }
|
||||
.admin-listing-status__identity { display: grid; gap: var(--space-2); }
|
||||
.admin-listing-status__blockers { grid-column: 1 / -1; margin: 0; padding-left: var(--space-6); color: var(--color-warning-text); }
|
||||
.admin-listing-status__actions { display: flex; grid-column: 1 / -1; flex-wrap: wrap; gap: var(--space-2); }
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@ import { onMounted, ref } from 'vue';
|
||||
import { RouterLink } from 'vue-router';
|
||||
|
||||
import { useAuth } from '@/composables/useAuth.js';
|
||||
import AdminListingStatus from '@/components/molecules/AdminListingStatus.vue';
|
||||
import { createAdminInventory, createAdminProduct, listAdminCategories, listAdminConditions, listAdminInventory, listAdminProducts } from '@/services/admin-catalog-service.js';
|
||||
import { createAdminListing, listAdminListings, markListingPending, publishAdminListing } from '@/services/admin-listing-service.js';
|
||||
|
||||
@@ -137,7 +138,7 @@ onMounted(load);
|
||||
<section class="admin-list" aria-labelledby="listings-title">
|
||||
<h2 id="listings-title">Listings</h2>
|
||||
<p v-if="!listings.length && !isLoading" class="state state--empty">No listings yet.</p>
|
||||
<ul v-else><li v-for="listing in listings" :key="listing.id"><span>{{ listing.productName }}</span> · {{ listing.status }} · ${{ (listing.askingPriceMinor / 100).toFixed(2) }} <button v-if="listing.status === 'DRAFT'" class="button button--secondary" type="button" @click="transitionListing(publishAdminListing, listing.id)">Publish</button><button v-if="listing.status === 'DRAFT'" class="button button--secondary" type="button" @click="transitionListing(markListingPending, listing.id)">Mark pending</button></li></ul>
|
||||
<div v-else class="admin-listing-stack"><AdminListingStatus v-for="listing in listings" :key="listing.id" :listing="listing" :is-saving="isSaving" @publish="transitionListing(publishAdminListing, listing.id)" @pending="transitionListing(markListingPending, listing.id)" /></div>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user