mirror of
https://github.com/vercel/commerce.git
synced 2025-05-11 20:27:51 +00:00
301 lines
9.3 KiB
TypeScript
301 lines
9.3 KiB
TypeScript
import Markdown from 'markdown-to-jsx';
|
|
import { Document, Image, Page, Text, StyleSheet, View, Link } from '@react-pdf/renderer';
|
|
import { Order, OrderConfirmationContent } from 'lib/shopify/types';
|
|
import { toPrintDate } from 'lib/utils';
|
|
|
|
const PDFPrice = ({
|
|
style,
|
|
amount,
|
|
currencyCode = 'USD'
|
|
}: {
|
|
style?: any;
|
|
amount: string;
|
|
currencyCode: string;
|
|
}) => {
|
|
const price = parseFloat(amount);
|
|
|
|
// Return 'Included' if price is 0
|
|
if (price === 0) {
|
|
return <Text style={style}>Free</Text>;
|
|
}
|
|
|
|
return (
|
|
<Text style={style}>
|
|
{new Intl.NumberFormat(undefined, {
|
|
style: 'currency',
|
|
currency: currencyCode,
|
|
currencyDisplay: 'narrowSymbol'
|
|
}).format(price)}
|
|
</Text>
|
|
);
|
|
};
|
|
|
|
export default function OrderConfirmationPdf({
|
|
content,
|
|
order,
|
|
signature1,
|
|
signature2,
|
|
date
|
|
}: {
|
|
content: OrderConfirmationContent;
|
|
order: Order;
|
|
signature1: string;
|
|
signature2: string;
|
|
date: string;
|
|
}) {
|
|
const styles = StyleSheet.create({
|
|
logo: {
|
|
width: 300,
|
|
marginHorizontal: 'auto',
|
|
marginBottom: 24
|
|
},
|
|
page: {
|
|
padding: 48,
|
|
paddingVertical: 64
|
|
},
|
|
h1: {
|
|
fontSize: 18,
|
|
fontWeight: 700,
|
|
marginBottom: 12,
|
|
color: content.color
|
|
},
|
|
h2: {
|
|
fontSize: 14,
|
|
fontWeight: 700,
|
|
marginBottom: 12,
|
|
color: content.color
|
|
},
|
|
h3: {
|
|
fontSize: 12,
|
|
fontWeight: 700,
|
|
marginBottom: 12,
|
|
color: content.color
|
|
},
|
|
p: {
|
|
fontSize: 10,
|
|
marginBottom: 12
|
|
},
|
|
span: {
|
|
fontSize: 10
|
|
},
|
|
strong: {
|
|
fontWeight: 700,
|
|
fontSize: 10
|
|
},
|
|
a: {
|
|
color: content.color,
|
|
fontSize: 10,
|
|
textDecoration: 'underline'
|
|
},
|
|
label: {
|
|
fontSize: 10,
|
|
fontWeight: 'bold',
|
|
color: '#555'
|
|
},
|
|
tableRow: {
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
gap: 8
|
|
},
|
|
tableCell: {
|
|
textAlign: 'left',
|
|
fontSize: 10,
|
|
paddingVertical: 12
|
|
}
|
|
});
|
|
|
|
return (
|
|
<Document>
|
|
<Page size="A4" style={styles.page}>
|
|
<View>
|
|
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
|
<Image src={content?.logo?.url} style={styles.logo} />
|
|
<Text style={styles.h3}>ORDER INFORMATION:</Text>
|
|
<View>
|
|
<Text style={styles.span}>Order number: {order.name}</Text>
|
|
<Text style={styles.span}>Email: {order.customer?.emailAddress}</Text>
|
|
<Text style={styles.p}>Date: {toPrintDate(order.processedAt)}</Text>
|
|
</View>
|
|
<View style={{ display: 'flex', flexDirection: 'row' }}>
|
|
<View style={{ flex: 1 }}>
|
|
<Text style={[styles.label, { marginBottom: 6 }]}>Shipping Address</Text>
|
|
<View>
|
|
<Text style={styles.span}>
|
|
{order.shippingAddress!.firstName} {order.shippingAddress!.lastName}
|
|
</Text>
|
|
<Text style={styles.span}>{order.shippingAddress!.address1}</Text>
|
|
{order.shippingAddress!.address2 && (
|
|
<Text style={styles.p}>{order.shippingAddress!.address2}</Text>
|
|
)}
|
|
<Text style={styles.span}>
|
|
{order.shippingAddress!.city} {order.shippingAddress!.provinceCode}{' '}
|
|
{order.shippingAddress!.zip}
|
|
</Text>
|
|
<Text style={styles.p}>{order.shippingAddress!.country}</Text>
|
|
</View>
|
|
</View>
|
|
<View style={{ flex: 1 }}>
|
|
<Text style={[styles.label, { marginBottom: 6 }]}>Billing Address</Text>
|
|
<View>
|
|
<Text style={styles.span}>
|
|
{order.billingAddress!.firstName} {order.billingAddress!.lastName}
|
|
</Text>
|
|
<Text style={styles.span}>{order.billingAddress!.address1}</Text>
|
|
{order.billingAddress!.address2 && (
|
|
<Text style={styles.span}>{order.billingAddress!.address2}</Text>
|
|
)}
|
|
<Text style={styles.span}>
|
|
{order.billingAddress!.city} {order.billingAddress!.provinceCode}{' '}
|
|
{order.billingAddress!.zip}
|
|
</Text>
|
|
<Text style={styles.p}>{order.billingAddress!.country}</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
<View>
|
|
<Text style={styles.label}>Payment</Text>
|
|
<View>
|
|
<Text style={styles.p}>
|
|
{order.transactions[0]?.paymentDetails
|
|
? `Ending with ${order.transactions[0]!.paymentDetails.last4} - `
|
|
: 'Manual - '}
|
|
<PDFPrice
|
|
amount={order.transactions[0]!.transactionAmount.amount}
|
|
currencyCode={order.transactions[0]!.transactionAmount.currencyCode}
|
|
/>
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<View>
|
|
<View style={styles.tableRow}>
|
|
<Text style={[styles.label, { width: '70%' }]}>Products</Text>
|
|
<Text style={[styles.label, { width: '15%' }]}>Quantity</Text>
|
|
<Text style={[styles.label, { width: '15%' }]}>Price</Text>
|
|
</View>
|
|
<View
|
|
style={{
|
|
width: '100%',
|
|
borderBottom: '1px solid #333',
|
|
marginTop: 6,
|
|
marginBottom: 8
|
|
}}
|
|
/>
|
|
{order.lineItems.map((lineItem, index) => (
|
|
<View key={index} style={styles.tableRow}>
|
|
<Text style={[styles.tableCell, { width: '70%' }]}>{lineItem.title}</Text>
|
|
<Text style={[styles.tableCell, { width: '15%' }]}>{lineItem.quantity}</Text>
|
|
<PDFPrice
|
|
style={[styles.tableCell, { width: '15%' }]}
|
|
amount={lineItem.totalPrice!.amount}
|
|
currencyCode={lineItem.totalPrice!.currencyCode}
|
|
/>
|
|
</View>
|
|
))}
|
|
<View
|
|
style={{
|
|
width: '100%',
|
|
borderBottom: '1px solid black',
|
|
marginTop: 6,
|
|
marginBottom: 8
|
|
}}
|
|
/>
|
|
<View style={{ width: '150px', marginLeft: 'auto', marginRight: '20' }}>
|
|
<View
|
|
style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}
|
|
>
|
|
<Text style={styles.span}>Subtotal</Text>
|
|
<PDFPrice
|
|
style={styles.span}
|
|
amount={order.subtotal!.amount}
|
|
currencyCode={order.subtotal!.currencyCode}
|
|
/>
|
|
</View>
|
|
<View
|
|
style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}
|
|
>
|
|
<Text style={styles.span}>Shipping</Text>
|
|
<PDFPrice
|
|
style={styles.span}
|
|
amount={order.shippingMethod!.price.amount}
|
|
currencyCode={order.shippingMethod!.price.currencyCode}
|
|
/>
|
|
</View>
|
|
<View
|
|
style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}
|
|
>
|
|
<Text style={styles.span}>Total</Text>
|
|
<PDFPrice
|
|
style={styles.span}
|
|
amount={order.totalPrice!.amount}
|
|
currencyCode={order.totalPrice!.currencyCode}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</Page>
|
|
<Page size="A4" style={styles.page}>
|
|
<Markdown
|
|
options={{
|
|
wrapper: View,
|
|
overrides: {
|
|
h1: {
|
|
component: Text,
|
|
props: {
|
|
style: styles.h1
|
|
}
|
|
},
|
|
h2: {
|
|
component: Text,
|
|
props: {
|
|
style: styles.h2
|
|
}
|
|
},
|
|
h3: {
|
|
component: Text,
|
|
props: {
|
|
style: styles.h3
|
|
}
|
|
},
|
|
p: {
|
|
component: Text,
|
|
props: {
|
|
style: styles.p
|
|
}
|
|
},
|
|
strong: {
|
|
component: Text,
|
|
props: {
|
|
style: styles.strong
|
|
}
|
|
},
|
|
a: {
|
|
component: Link,
|
|
props: {
|
|
style: styles.a
|
|
}
|
|
}
|
|
}
|
|
}}
|
|
>
|
|
{content.body}
|
|
</Markdown>
|
|
<View style={{ display: 'flex', flexDirection: 'row' }}>
|
|
<Text style={[styles.p, { flex: 1 }]}>Date:</Text>
|
|
<Text style={[styles.p, { flex: 1 }]}>{toPrintDate(date)}</Text>
|
|
</View>
|
|
<View style={{ display: 'flex', flexDirection: 'row' }}>
|
|
<Text style={[styles.p, { flex: 1 }]}>Print your name to sign:</Text>
|
|
<Text style={[styles.p, { flex: 1 }]}>{signature1}</Text>
|
|
</View>
|
|
<View style={{ display: 'flex', flexDirection: 'row' }}>
|
|
<Text style={[styles.p, { flex: 1 }]}>
|
|
Credit card holder's electronic signature
|
|
</Text>
|
|
<Text style={[styles.p, { flex: 1 }]}>{signature2}</Text>
|
|
</View>
|
|
</Page>
|
|
</Document>
|
|
);
|
|
}
|