From 4e0e4ebd480e8c95020195ac475f15c71250d28c Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Tue, 8 Sep 2026 23:15:50 -0400 Subject: [PATCH] feat: add admin listing workflow --- apps/web/src/pages/AdminHomePage.vue | 43 ++++++++++++++++++- .../web/src/services/admin-listing-service.js | 13 ++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/services/admin-listing-service.js diff --git a/apps/web/src/pages/AdminHomePage.vue b/apps/web/src/pages/AdminHomePage.vue index 783ee08..010e68e 100644 --- a/apps/web/src/pages/AdminHomePage.vue +++ b/apps/web/src/pages/AdminHomePage.vue @@ -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); @@ -86,6 +111,17 @@ onMounted(load); +
+

Create listing draft

+ + + + + +
@@ -98,5 +134,10 @@ onMounted(load);

No physical copies yet.

+
+

Listings

+

No listings yet.

+ +
diff --git a/apps/web/src/services/admin-listing-service.js b/apps/web/src/services/admin-listing-service.js new file mode 100644 index 0000000..5855b08 --- /dev/null +++ b/apps/web/src/services/admin-listing-service.js @@ -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 } }); }