Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add POS UI ext Print API tutorial #2449

Merged
merged 1 commit into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,30 +1,46 @@
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
import {generateCodeBlock} from '../helpers/generateCodeBlock';
import {
generateCodeBlock,
generateTSXCodeBlock,
} from '../helpers/generateCodeBlock';

const generateCodeBlockForPrintApi = (title: string, fileName: string) =>
generateCodeBlock(title, 'print-api', fileName);

const data: ReferenceEntityTemplateSchema = {
name: 'Print API',
description: `
The Print API provides access to printing functionality.
`,
description: `The Print API enables document printing functionality in your point of sale extension. Use this API to trigger the native print dialog for your documents.

The \`print()\` method accepts either:
- A relative path that will be appended to your app's [application_url](/docs/apps/build/cli-for-apps/app-configuration#application_url)
- A full URL to your app's backend that will be used to return the document to print

Supported document types:
- HTML documents (recommended for best printing experience)
- Text files
- Image files (PNG, JPEG, etc.)
- PDF files (Note: On Android devices, PDFs will be downloaded and must be printed using an external application)`,
isVisualComponent: false,
type: 'APIs',
definitions: [
{
title: 'PrintApi',
description: '',
description: 'Interface for handling print operations',
type: 'PrintApiContent',
},
],
category: 'APIs',
related: [
{
name: 'PrintPreview Component',
subtitle: 'See how to use the Print API with a PrintPreview.',
subtitle: 'Preview documents before printing',
url: '/api/pos-ui-extensions/components/printpreview',
},
{
name: 'Build a Print Extension',
subtitle: 'Learn how to implement printing',
url: '/docs/api/pos-ui-extensions/examples/print-extension',
},
],
examples: {
description: 'Examples of using the Print API',
Expand All @@ -35,6 +51,20 @@ The Print API provides access to printing functionality.
'print',
),
},
{
codeblock: generateTSXCodeBlock(
'Print with relative path',
'print-api',
'print-relative',
),
},
{
codeblock: generateTSXCodeBlock(
'Print with full URL',
'print-api',
'print-full-url',
),
},
],
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,45 @@ const generateCodeBlockForPrintPreview = (title: string, fileName: string) =>

const data: ReferenceEntityTemplateSchema = {
name: 'PrintPreview',
description: 'Renders a `PrintPreview`.',
description: `A component that displays a preview of a printable document. Use this component to let users review documents before printing.

The \`src\` prop accepts either:
- A relative path that will be appended to your app's [application_url](/docs/apps/build/cli-for-apps/app-configuration#application_url)
- A full URL to your document endpoint

Supported document types:
- HTML documents (recommended for best preview experience)
- Text files
- Image files (PNG, JPEG, etc.)
- PDF files (Note: On Android devices, PDFs will be downloaded and must be printed using an external application)`,
isVisualComponent: true,
type: 'component',
thumbnail: 'print-preview-thumbnail.png',
definitions: [
{
title: 'PrintPreview',
description: '',
description: 'Renders a preview of a printable document',
type: 'PrintPreviewProps',
},
],
category: 'Components',
related: [
{
name: 'Print API',
subtitle: 'See how to use the Print API.',
subtitle: 'Handle print operations',
url: '/api/pos-ui-extensions/apis/print-api',
},
{
name: 'Build a Print Extension',
subtitle: 'Learn how to implement printing',
url: '/docs/api/pos-ui-extensions/examples/print-extension',
},
],
defaultExample: {
image: 'print-preview-default.png',
description: 'Basic usage with relative and full URLs:',
codeblock: generateCodeBlockForPrintPreview(
'Render a PrintPreview and a button for printing',
'Basic PrintPreview',
'default.example',
),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Using a full URL directly
await print.print('https://my-print-service.com/api/print/document');
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// If your application_url is "https://my-app.com"
// This will resolve to "https://my-app.com/api/print/document"
await print.print('/api/print/document');
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ export default extension(
Button,
{
title: 'Print',
onPress: () => api.print.print(),
onPress: () =>
vctrchu marked this conversation as resolved.
Show resolved Hide resolved
api.print.print(
'/documents/test-print',
),
},
);

Expand All @@ -32,6 +35,7 @@ export default extension(
);

homeScreen.append(printPreview);
homeScreen.append(printButton);

root.append(homeScreen);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const Modal = () => {
<Screen name="Home" title="Home">
<Button
title="Print"
onPress={() => api.print.print()}
onPress={() =>
api.print.print('/documents/test-print')
}
/>
<PrintPreview src="/documents/test-print" />
</Screen>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,20 @@ export const generateCodeBlock = (
],
};
};

export const generateTSXCodeBlock = (
title: string,
functionality: string,
filename: string,
) => {
return {
title,
tabs: [
{
title: 'React',
code: `../examples/${functionality}/${filename}.tsx`,
language: 'tsx',
},
],
};
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React, {useState, useEffect} from 'react';
import type {ListRow} from '@shopify/ui-extensions-react/point-of-sale';
import {
Button,
Navigator,
PrintPreview,
Stack,
reactExtension,
Screen,
useApi,
List,
SectionHeader,
} from '@shopify/ui-extensions-react/point-of-sale';

interface DocumentOption {
id: string;
label: string;
subtitle: string;
selected: boolean;
}

const Modal = () => {
// Step 1: Create state for managing document selection and loading
const api = useApi();
const [isLoading, setIsLoading] = useState(false);
const [src, setSrc] = useState<string | null>(null);
const [documents, setDocuments] = useState<DocumentOption[]>([
{
id: 'invoice',
label: 'Receipt / Invoice',
subtitle:
'Print a detailed sales receipt with tax and payment information',
selected: true,
},
{
id: 'packing-slip',
label: 'Packing Slip',
subtitle: 'Print shipping details and item list for order fulfillment',
selected: false,
},
{
id: 'returns-form',
label: 'Returns Form',
subtitle: 'Print return authorization form with shipping labels',
selected: false,
},
{
id: 'draft-orders-quote',
label: 'Draft Orders Quote',
subtitle: 'Print price quotes and custom order details for customers',
selected: false,
},
{
id: 'refund-credit-note',
label: 'Refund / Credit Note',
subtitle: 'Print refund documentation with returned items and amounts',
selected: false,
},
]);

// Step 2: Set up document selection with List component
const listData: ListRow[] = documents.map((doc) => ({
id: doc.id,
onPress: () => handleSelection(doc.id),
leftSide: {
label: doc.label,
subtitle: [doc.subtitle],
},
rightSide: {
toggleSwitch: {
value: doc.selected,
disabled: false,
},
},
}));

// Step 3: Handle document selection and URL updates
const handleSelection = (selectedId: string) => {
setDocuments((prevDocs) =>
prevDocs.map((doc) => ({
...doc,
selected: doc.id === selectedId ? !doc.selected : doc.selected,
})),
);
};

useEffect(() => {
const selectedDocs = documents.filter((doc) => doc.selected);
if (selectedDocs.length) {
const params = new URLSearchParams({
printTypes: selectedDocs.map((doc) => doc.id).join(','),
});
const fullSrc = `/print?${params.toString()}`;
setSrc(fullSrc);
} else {
setSrc(null);
}
}, [documents]);

// Step 4: Implement print functionality with error handling
const handlePrint = async () => {
if (!src) return;
setIsLoading(true);
try {
await api.print.print(src);
} catch (error) {
console.error('Print failed:', error);
} finally {
setIsLoading(false);
}
};

// Return Navigator component with document selection and preview screens
return (
<Navigator>
{/* Document selection screen */}
<Screen name="print-selection" title="Print Tutorial">
<List
listHeaderComponent={<SectionHeader title="Templates" />}
data={listData}
/>
<Stack
direction="vertical"
paddingHorizontal="Small"
paddingVertical="Small"
>
<Button
isDisabled={isLoading || !src}
isLoading={isLoading}
onPress={() => api.navigation.navigate('print-preview')}
title="Next"
/>
</Stack>
</Screen>

{/* Preview and print screen */}
<Screen name="print-preview" title="Print Tutorial">
{src && <PrintPreview src={src} />}
<Stack
direction="vertical"
paddingHorizontal="Small"
paddingVertical="Small"
>
<Button
isDisabled={isLoading || !src}
isLoading={isLoading}
onPress={handlePrint}
title="Print"
/>
</Stack>
</Screen>
</Navigator>
);
};

export default reactExtension('pos.home.modal.render', () => <Modal />);
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import {
Tile,
reactExtension,
useApi,
} from '@shopify/ui-extensions-react/point-of-sale';

const HomeTile = () => {
const api = useApi();
return (
<Tile
title="Print Tutorial"
onPress={() => {
api.action.presentModal('print-tutorial');
}}
enabled
/>
);
};

export default reactExtension('pos.home.tile.render', () => {
return <HomeTile />;
});
Loading
Loading