This commit is contained in:
@@ -4,10 +4,12 @@ import { RouterLink } from 'vue-router';
|
||||
|
||||
import { useAuth } from '@/composables/useAuth.js';
|
||||
import { createAdminInventory, createAdminProduct, listAdminCategories, listAdminConditions, listAdminInventory, listAdminProducts } from '@/services/admin-catalog-service.js';
|
||||
import { createAdminListing, listAdminListings, markListingPending, publishAdminListing } from '@/services/admin-listing-service.js';
|
||||
|
||||
const { principal } = useAuth();
|
||||
const products = ref([]);
|
||||
const inventory = ref([]);
|
||||
const listings = ref([]);
|
||||
const categories = ref([]);
|
||||
const conditions = ref([]);
|
||||
const isLoading = ref(true);
|
||||
@@ -17,6 +19,8 @@ const productName = ref('');
|
||||
const categoryId = ref('');
|
||||
const inventoryProductId = ref('');
|
||||
const conditionId = ref('');
|
||||
const listingInventoryId = ref('');
|
||||
const askingPrice = ref('');
|
||||
|
||||
function csrfToken() {
|
||||
return document.cookie.split('; ').find((value) => value.startsWith('pokemon_inventory_csrf='))?.split('=')[1] ?? '';
|
||||
@@ -25,7 +29,7 @@ function csrfToken() {
|
||||
async function load() {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
[products.value, inventory.value, categories.value, conditions.value] = await Promise.all([listAdminProducts(), listAdminInventory(), listAdminCategories(), listAdminConditions()]);
|
||||
[products.value, inventory.value, categories.value, conditions.value, listings.value] = await Promise.all([listAdminProducts(), listAdminInventory(), listAdminCategories(), listAdminConditions(), listAdminListings()]);
|
||||
} catch (requestError) { error.value = requestError; }
|
||||
finally { isLoading.value = false; }
|
||||
}
|
||||
@@ -49,6 +53,27 @@ async function createInventory() {
|
||||
finally { isSaving.value = false; }
|
||||
}
|
||||
|
||||
async function createListing() {
|
||||
const item = inventory.value.find((candidate) => candidate.id === listingInventoryId.value);
|
||||
const product = products.value.find((candidate) => candidate.id === item?.productId);
|
||||
isSaving.value = true;
|
||||
try {
|
||||
const listing = await createAdminListing({ inventoryItemId: item.id, productName: product.name, categoryId: product.categoryId, conditionId: item.conditionId, askingPriceMinor: Number(askingPrice.value) }, csrfToken());
|
||||
listings.value.push(listing);
|
||||
askingPrice.value = '';
|
||||
} catch (requestError) { error.value = requestError; }
|
||||
finally { isSaving.value = false; }
|
||||
}
|
||||
|
||||
async function transitionListing(action, id) {
|
||||
isSaving.value = true;
|
||||
try {
|
||||
const listing = await action(id, csrfToken());
|
||||
listings.value = listings.value.map((candidate) => candidate.id === listing.id ? listing : candidate);
|
||||
} catch (requestError) { error.value = requestError; }
|
||||
finally { isSaving.value = false; }
|
||||
}
|
||||
|
||||
onMounted(load);
|
||||
</script>
|
||||
|
||||
@@ -86,6 +111,17 @@ onMounted(load);
|
||||
</select>
|
||||
<button class="button button--primary" type="submit" :disabled="isSaving">Create copy</button>
|
||||
</form>
|
||||
<form class="showcase-panel" @submit.prevent="createListing">
|
||||
<h2>Create listing draft</h2>
|
||||
<label class="fixture-label" for="listing-inventory">Inventory copy</label>
|
||||
<select id="listing-inventory" v-model="listingInventoryId" class="fixture-input" required>
|
||||
<option value="" disabled>Select a physical copy</option>
|
||||
<option v-for="item in inventory" :key="item.id" :value="item.id">{{ item.id }} · {{ item.status }}</option>
|
||||
</select>
|
||||
<label class="fixture-label" for="listing-price">Asking price (cents)</label>
|
||||
<input id="listing-price" v-model="askingPrice" class="fixture-input" type="number" min="1" required />
|
||||
<button class="button button--primary" type="submit" :disabled="isSaving">Create listing</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<section class="admin-list" aria-labelledby="products-title">
|
||||
@@ -98,5 +134,10 @@ onMounted(load);
|
||||
<p v-if="!inventory.length && !isLoading" class="state state--empty">No physical copies yet.</p>
|
||||
<ul v-else><li v-for="item in inventory" :key="item.id"><span>{{ item.id }}</span> · {{ item.status }} <RouterLink :to="{ name: 'admin-image-manager', params: { id: item.id } }">Manage images</RouterLink></li></ul>
|
||||
</section>
|
||||
<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>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
const apiBaseUrl = import.meta.env?.VITE_API_BASE_URL ?? '/api/v1';
|
||||
|
||||
async function request(path, options = {}) {
|
||||
const response = await fetch(`${apiBaseUrl}${path}`, { credentials: 'include', ...options, headers: { 'content-type': 'application/json', ...options.headers } });
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok) throw new Error(payload?.error?.message ?? 'Listing request failed.');
|
||||
return payload?.data;
|
||||
}
|
||||
|
||||
export function listAdminListings() { return request('/admin/listings'); }
|
||||
export function createAdminListing(input, csrfToken) { return request('/admin/listings', { method: 'POST', headers: { 'x-csrf-token': csrfToken }, body: JSON.stringify(input) }); }
|
||||
export function publishAdminListing(id, csrfToken) { return request(`/admin/listings/${encodeURIComponent(id)}/publish`, { method: 'POST', headers: { 'x-csrf-token': csrfToken } }); }
|
||||
export function markListingPending(id, csrfToken) { return request(`/admin/listings/${encodeURIComponent(id)}/pending`, { method: 'POST', headers: { 'x-csrf-token': csrfToken } }); }
|
||||
Reference in New Issue
Block a user