feat: connect admin image workflow
Quality / test (push) Canceled after 0s

This commit is contained in:
2026-09-08 22:16:59 -04:00
parent c382852924
commit a21d1ae854
9 changed files with 120 additions and 0 deletions
@@ -12,6 +12,7 @@ export default { title: 'Organisms/AdminImageManager', component: AdminImageMana
export const Completed = { args: { images, processingStatus: 'COMPLETED' } };
export const Processing = { args: { images, processingStatus: 'PROCESSING' } };
export const Failed = { args: { images, processingStatus: 'FAILED', errorMessage: 'Derivative generation failed.' } };
export const Saving = { args: { images, processingStatus: 'COMPLETED', isSaving: true } };
export const EditAltTextWithKeyboard = {
args: { images, processingStatus: 'COMPLETED' },
@@ -9,6 +9,7 @@ const props = defineProps({
images: { type: Array, required: true },
processingStatus: { type: String, default: 'COMPLETED' },
errorMessage: { type: String, default: '' },
isSaving: { type: Boolean, default: false },
});
const emit = defineEmits(['reorder', 'primary', 'alt-text', 'retry', 'delete']);
const altText = ref(props.images.find((image) => image.isPrimary)?.altText ?? '');
@@ -39,6 +40,7 @@ function saveAltText() {
<ImageGallery :images="orderedImages" />
<p v-if="isSaving" class="state state--loading" role="status">Saving image changes...</p>
<div class="admin-image-manager__controls">
<div class="admin-image-manager__row">
<ConditionBadge :label="orderedImages.find((image) => image.isPrimary)?.altText || 'Primary image'" :selected="true" />
@@ -0,0 +1,50 @@
import { ref } from 'vue';
import {
deleteImage,
listAdminImages,
reorderImage,
retryImageProcessing,
setPrimaryImage,
updateImageAltText,
} from '@/services/admin-image-service.js';
function csrfToken() {
return document.cookie.split('; ').find((value) => value.startsWith('pokemon_inventory_csrf='))?.split('=')[1] ?? '';
}
export function useAdminImages(inventoryItemId) {
const images = ref([]);
const isLoading = ref(false);
const isSaving = ref(false);
const error = ref(null);
async function load() {
isLoading.value = true;
error.value = null;
try { images.value = await listAdminImages(inventoryItemId); }
catch (requestError) { error.value = requestError; }
finally { isLoading.value = false; }
}
async function apply(action) {
isSaving.value = true;
error.value = null;
try { await action(csrfToken()); await load(); }
catch (requestError) { error.value = requestError; }
finally { isSaving.value = false; }
}
return {
images,
isLoading,
isSaving,
error,
load,
reorder: (imageId, sortOrder) => apply((token) => reorderImage(imageId, sortOrder, token)),
setPrimary: (imageId) => apply((token) => setPrimaryImage(imageId, token)),
updateAltText: (imageId, altText) => apply((token) => updateImageAltText(imageId, altText, token)),
retry: (imageId) => apply((token) => retryImageProcessing(imageId, token)),
remove: (imageId) => apply((token) => deleteImage(imageId, token)),
};
}
+33
View File
@@ -0,0 +1,33 @@
<script setup>
import { onMounted } from 'vue';
import { RouterLink, useRoute } from 'vue-router';
import AdminImageManager from '@/components/organisms/AdminImageManager.vue';
import { useAdminImages } from '@/composables/useAdminImages.js';
const route = useRoute();
const inventoryItemId = route.params.id;
const { images, isLoading, isSaving, error, load, reorder, setPrimary, updateAltText, retry, remove } = useAdminImages(inventoryItemId);
onMounted(load);
</script>
<template>
<main class="detail-page">
<RouterLink class="detail-back" :to="{ name: 'catalog' }">Back to catalog</RouterLink>
<p class="catalog-kicker">Admin inventory</p>
<h1>Manage images</h1>
<p v-if="isLoading" class="catalog-state" role="status">Loading images...</p>
<p v-else-if="error" class="catalog-state catalog-state--error" role="alert">{{ error.message }}</p>
<AdminImageManager
v-else
:images="images"
:processing-status="isSaving ? 'PROCESSING' : (images[0]?.processingStatus ?? 'COMPLETED')"
@reorder="reorder($event.id, $event.sortOrder)"
@primary="setPrimary"
@alt-text="updateAltText(images[0]?.id, $event)"
@retry="retry(images[0]?.id)"
@delete="remove"
/>
</main>
</template>
+2
View File
@@ -3,6 +3,7 @@ import { createRouter, createWebHistory } from 'vue-router';
import CatalogPage from '@/pages/CatalogPage.vue';
import ListingDetailPage from '@/pages/ListingDetailPage.vue';
import ProductDetailPage from '@/pages/ProductDetailPage.vue';
import AdminImagePage from '@/pages/AdminImagePage.vue';
export default createRouter({
history: createWebHistory(),
@@ -10,5 +11,6 @@ export default createRouter({
{ path: '/', name: 'catalog', component: CatalogPage },
{ path: '/listings/:id', name: 'listing-detail', component: ListingDetailPage },
{ path: '/products/:slug', name: 'product-detail', component: ProductDetailPage },
{ path: '/admin/inventory/:id/images', name: 'admin-image-manager', component: AdminImagePage },
],
});
@@ -54,3 +54,10 @@ export function deleteImage(imageId, csrfToken) {
headers: { 'x-csrf-token': csrfToken },
});
}
export async function listAdminImages(inventoryItemId) {
return request(`/admin/inventory/${encodeURIComponent(inventoryItemId)}/images`, {
method: 'GET',
headers: {},
});
}