I can’t change the size of the image, it just show its orignal size. I need something like thumbnail but no thumbnail component for admin extensions.
Hi @donsuper
Can you share a screenshot of where you’re adding this in? It’s possible the size of the image is intentionally restricted to ensure admin design patterns remain consistent.
I can get a thumbnail sized picture now, but don’t know it is the correct way to do it. And I can’t get the inlineAlignment=“space-between” working, the left and right column won’t push each other to opposite side to the end.
import { useState, useEffect, useCallback, default as React } from ‘react’;
import {
reactExtension,
useApi,
AdminAction,
BlockStack,
Text,
Badge,
InlineStack,
Image,
Divider,
Button,
Box,
Banner,
NumberField,
} from ‘@shopify/ui-extensions-react/admin’;
const TARGET = ‘admin.order-details.action.render’;
export default reactExtension(TARGET, () => );
function App() {
const { close, data } = useApi();
const orderId = data.selected[0].id;
const [lineItems, setLineItems] = useState();
const [pickedQuantities, setPickedQuantities] = useState({});
const [initialPickedQuantities, setInitialPickedQuantities] = useState({});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
const METADATA_NAMESPACE = “pro_picklist_creater_app”;
const METADATA_KEY = “picked_quantities”;
useEffect(() => {
if (!orderId) {
setLoading(false);
return;
}
async function fetchData() {
setLoading(true);
const response = await fetch("shopify:admin/api/graphql.json", {
method: "POST",
body: JSON.stringify({
query: `query GetOrderDetails($id: ID!) {
order(id: $id) {
pickedQuantities: metafield(namespace: "${METADATA_NAMESPACE}", key: "${METADATA_KEY}") {
value
}
lineItems(first: 50) {
edges {
node {
id
name
quantity
fulfillableQuantity
variant { image { url } sku }
product { featuredImage { url } }
}
}
}
}
}`,
variables: { id: orderId },
}),
});
if (!response.ok) {
setError('Failed to fetch data.');
setLoading(false);
return;
}
const responseData = await response.json();
if (responseData.errors) {
setError("GraphQL query failed.");
setLoading(false);
return;
}
const orderData = responseData.data?.order;
if (orderData) {
const parsedPickedQuantities = JSON.parse(orderData.pickedQuantities?.value || '{}');
setPickedQuantities(parsedPickedQuantities);
setInitialPickedQuantities(parsedPickedQuantities);
const unfulfilledItems = orderData.lineItems.edges
.map(edge => edge.node)
.filter(item => item.fulfillableQuantity > 0);
setLineItems(unfulfilledItems);
}
setLoading(false);
}
fetchData();
}, [orderId]);
const handleQuantityChange = useCallback((lineItemId, value) => {
setPickedQuantities((prev) => ({
…prev,
[lineItemId]: value,
}));
}, );
const handleCancelChanges = useCallback(() => {
setPickedQuantities(initialPickedQuantities);
}, [initialPickedQuantities]);
const handleSaveChanges = useCallback(async () => {
setSaving(true);
setError(null);
const response = await fetch("shopify:admin/api/graphql.json", {
method: "POST",
body: JSON.stringify({
query: `mutation SetMetafields($metafield: MetafieldsSetInput!) {
metafieldsSet(metafields: [$metafield]) {
userErrors { field, message }
}
}`,
variables: {
metafield: {
ownerId: orderId,
namespace: METADATA_NAMESPACE,
key: METADATA_KEY,
type: "json",
value: JSON.stringify(pickedQuantities),
},
},
}),
});
if (response.ok) {
const responseData = await response.json();
if (responseData.data?.metafieldsSet?.userErrors?.length) {
setError("Failed to save changes.");
} else {
setInitialPickedQuantities(pickedQuantities);
}
} else {
setError("Failed to save changes.");
}
setSaving(false);
}, [orderId, pickedQuantities]);
const initialKeys = Object.keys(initialPickedQuantities);
const currentKeys = Object.keys(pickedQuantities);
let hasChanges = initialKeys.length !== currentKeys.length;
if (!hasChanges) {
for (const key of initialKeys) {
if ((initialPickedQuantities[key] || 0) !== (pickedQuantities[key] || 0)) {
hasChanges = true;
break;
}
}
}
const unpickedQty = lineItems.reduce((total, item) => {
const picked = pickedQuantities[item.id] || 0;
return total + (item.quantity - picked);
}, 0);
const allItemsPicked = unpickedQty <= 0 && lineItems.length > 0;
let bannerTitle = ${unpickedQty} total unit(s) need to be picked.
;
let bannerTone = ‘warning’;
if (allItemsPicked) {
bannerTitle = ‘All items picked’;
bannerTone = ‘success’;
}
return (
<AdminAction
title=“Picklist Status”
primaryAction={
hasChanges ? (
Confirm Changes
) : (
<Button variant=“primary” onPress={() => close()}>
Close
)
}
secondaryAction={
Cancel
}
>
{loading && Loading…}
{error && {error}}
{!loading && !error && (
<Banner title={bannerTitle} tone={hasChanges ? ‘critical’ : bannerTone} />
{lineItems.length > 0 ? (
lineItems.map((item, index) => {
const { name, quantity, variant, product } = item;
const pickedQty = pickedQuantities[item.id] || 0;
let badgeTone = 'warning';
let badgeText = 'Not Picked';
if (pickedQty > 0 && pickedQty < quantity) {
badgeTone = 'info';
badgeText = 'Partial';
} else if (pickedQty >= quantity) {
badgeTone = 'success';
badgeText = 'Picked';
}
const originalUrl = variant?.image?.url || product?.featuredImage?.url;
let thumbnailUrl = '';
if (originalUrl) {
const parts = originalUrl.split('?');
const urlWithoutParams = parts[0];
const params = parts.length > 1 ? `?${parts[1]}` : '';
const extensionIndex = urlWithoutParams.lastIndexOf('.');
if (extensionIndex !== -1) {
const preExtension = urlWithoutParams.substring(0, extensionIndex);
const extension = urlWithoutParams.substring(extensionIndex);
thumbnailUrl = `${preExtension}_50x50${extension}${params}`;
}
}
return (
<React.Fragment key={item.id}>
{index > 0 && <Divider />}
<Box padding="base">
<InlineStack blockAlignment="center" inlineAlignment="space-between" inlineSize="100%">
<InlineStack gap="base" blockAlignment="center">
{thumbnailUrl ? (
<Image source={thumbnailUrl} alt={name} />
) : (
<Image source="https://cdn.shopify.com/s/files/1/0533/2089/files/placeholder-images-image_small.png?format=webp&v=1530129081" />
)}
<BlockStack gap="none">
<Text fontWeight="bold">{name}</Text>
{variant?.sku && <Text>SKU: {variant.sku}</Text>}
</BlockStack>
</InlineStack>
<InlineStack gap="base" blockAlignment="center">
<Box maxInlineSize={85}>
<NumberField
labelHidden
value={pickedQty}
onChange={(newValue) => handleQuantityChange(item.id, newValue)}
min={0}
max={quantity}
suffix={`/${quantity}`}
/>
</Box>
<Badge tone={badgeTone}>{badgeText}</Badge>
</InlineStack>
</InlineStack>
</Box>
</React.Fragment>
);
})
) : (
<Box padding="base">
<Text>All items in this order have been fulfilled.</Text>
</Box>
)}
</BlockStack>
</BlockStack>
)}
</AdminAction>
);
}