first version
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"admin.order-details.block.render": {
|
||||
"main": "dist/invoice-order-block.js"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "invoice-order-block",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@shopify/ui-extensions": "^2026.1.0",
|
||||
"preact": "^10.22.0",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import '@shopify/ui-extensions';
|
||||
|
||||
//@ts-ignore
|
||||
declare module './src/BlockExtension.tsx' {
|
||||
const shopify: import('@shopify/ui-extensions/admin.order-details.block.render').Api;
|
||||
const globalThis: { shopify: typeof shopify };
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
api_version = "2026-01"
|
||||
|
||||
[[extensions]]
|
||||
name = "Invoice details"
|
||||
handle = "invoice-order-block"
|
||||
type = "ui_extension"
|
||||
uid = "linumiq-invoice-order-block"
|
||||
|
||||
[[extensions.targeting]]
|
||||
target = "admin.order-details.block.render"
|
||||
module = "./src/BlockExtension.tsx"
|
||||
@@ -0,0 +1,79 @@
|
||||
import { render } from "preact";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
interface InvoiceRow {
|
||||
id: string;
|
||||
invoiceNumber: string;
|
||||
version: number;
|
||||
kind: string;
|
||||
issuedAt: string;
|
||||
status: string;
|
||||
pdfUrl: string;
|
||||
cancelledAt: string | null;
|
||||
sentAt: string | null;
|
||||
}
|
||||
|
||||
interface Payload {
|
||||
latest: InvoiceRow | null;
|
||||
history: InvoiceRow[];
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
render(<Extension />, document.body);
|
||||
};
|
||||
|
||||
function Extension() {
|
||||
const { data } = (globalThis as any).shopify;
|
||||
const orderGid: string | undefined = data?.selected?.[0]?.id;
|
||||
const orderId = orderGid ? orderGid.split("/").pop() : undefined;
|
||||
|
||||
const [payload, setPayload] = useState<Payload | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!orderId) return;
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/orders/${orderId}/invoice`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const json: Payload = await res.json();
|
||||
if (!cancelled) setPayload(json);
|
||||
} catch (e: any) {
|
||||
if (!cancelled) setError(e?.message ?? "Failed to load");
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [orderId]);
|
||||
|
||||
return (
|
||||
<s-admin-block heading="Invoice">
|
||||
{loading ? (
|
||||
<s-text>Loading…</s-text>
|
||||
) : error ? (
|
||||
<s-banner tone="critical">{error}</s-banner>
|
||||
) : !payload?.latest ? (
|
||||
<s-text>No invoice yet for this order.</s-text>
|
||||
) : (
|
||||
<s-stack gap="200">
|
||||
<s-text weight="bold">{payload.latest.invoiceNumber} (v{payload.latest.version})</s-text>
|
||||
<s-text>Issued {new Date(payload.latest.issuedAt).toLocaleDateString()}</s-text>
|
||||
<s-badge tone={payload.latest.sentAt ? "success" : "info"}>
|
||||
{payload.latest.sentAt ? "Sent" : "Not sent"}
|
||||
</s-badge>
|
||||
{payload.latest.pdfUrl ? (
|
||||
<s-link href={payload.latest.pdfUrl} target="_blank">View PDF</s-link>
|
||||
) : null}
|
||||
{payload.history.length > 1 ? (
|
||||
<s-text tone="subdued">{payload.history.length} versions in history</s-text>
|
||||
) : null}
|
||||
</s-stack>
|
||||
)}
|
||||
</s-admin-block>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user