From a9fd52488fe281a970501362eac93edfddd04ec5 Mon Sep 17 00:00:00 2001 From: Kyohei Fukuda Date: Thu, 13 Mar 2025 11:19:17 +0900 Subject: [PATCH] feat: update quotes and invoice templates with new dimensions and add quotes to featured templates --- playground/.gitignore | 1 + playground/public/imgs/devin.svg | 9 + playground/public/template-assets/index.json | 70 ----- .../template-assets/invoice/template.json | 2 +- .../template-assets/quotes/template.json | 2 +- .../scripts/generate-templates-list-json.mjs | 2 +- playground/src/routes/Templates.tsx | 282 ++++++++++-------- 7 files changed, 177 insertions(+), 191 deletions(-) create mode 100644 playground/public/imgs/devin.svg delete mode 100644 playground/public/template-assets/index.json diff --git a/playground/.gitignore b/playground/.gitignore index 4ca02d54..cbb0f3c2 100644 --- a/playground/.gitignore +++ b/playground/.gitignore @@ -26,5 +26,6 @@ dist-ssr node-playground/test-generate.pdf node-playground/test-merge.pdf public/template-assets/**/thumbnail.png +public/template-assets/index.json scripts/thumbnail-hash-map.json e2e-error-screenshot.png \ No newline at end of file diff --git a/playground/public/imgs/devin.svg b/playground/public/imgs/devin.svg new file mode 100644 index 00000000..742b83cf --- /dev/null +++ b/playground/public/imgs/devin.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/playground/public/template-assets/index.json b/playground/public/template-assets/index.json deleted file mode 100644 index 81edb2f7..00000000 --- a/playground/public/template-assets/index.json +++ /dev/null @@ -1,70 +0,0 @@ -[ - { - "name": "invoice", - "author": "hand-dot" - }, - { - "name": "pedigree", - "author": "hand-dot" - }, - { - "name": "certificate-black", - "author": "pdfme" - }, - { - "name": "a4-blank", - "author": "hand-dot" - }, - { - "name": "address-label-10", - "author": "pdfme" - }, - { - "name": "address-label-30", - "author": "pdfme" - }, - { - "name": "address-label-6", - "author": "pdfme" - }, - { - "name": "certificate-blue", - "author": "pdfme" - }, - { - "name": "certificate-gold", - "author": "pdfme" - }, - { - "name": "certificate-white", - "author": "pdfme" - }, - { - "name": "invoice-blue", - "author": "pdfme" - }, - { - "name": "invoice-green", - "author": "pdfme" - }, - { - "name": "invoice-white", - "author": "pdfme" - }, - { - "name": "location-arrow", - "author": "pdfme" - }, - { - "name": "location-number", - "author": "pdfme" - }, - { - "name": "qr-lines", - "author": "pdfme" - }, - { - "name": "qr-title", - "author": "pdfme" - } -] \ No newline at end of file diff --git a/playground/public/template-assets/invoice/template.json b/playground/public/template-assets/invoice/template.json index 566448ad..da3faa6b 100644 --- a/playground/public/template-assets/invoice/template.json +++ b/playground/public/template-assets/invoice/template.json @@ -379,7 +379,7 @@ "y": 191.58 }, "content": "Thank you!", - "width": 52.67, + "width": 170, "height": 20, "rotate": 0, "alignment": "left", diff --git a/playground/public/template-assets/quotes/template.json b/playground/public/template-assets/quotes/template.json index c034edd0..f55f804b 100644 --- a/playground/public/template-assets/quotes/template.json +++ b/playground/public/template-assets/quotes/template.json @@ -380,7 +380,7 @@ "y": 191.58 }, "content": "Thank you for your interest!", - "width": 52.67, + "width": 170, "height": 20, "rotate": 0, "alignment": "left", diff --git a/playground/scripts/generate-templates-list-json.mjs b/playground/scripts/generate-templates-list-json.mjs index 33818113..9337ab61 100644 --- a/playground/scripts/generate-templates-list-json.mjs +++ b/playground/scripts/generate-templates-list-json.mjs @@ -6,7 +6,7 @@ const __dirname = path.dirname(new URL(import.meta.url).pathname); const templatesDir = path.join(__dirname, '..', 'public', 'template-assets'); const indexFilePath = path.join(templatesDir, 'index.json'); -const featuredTemplates = ['invoice', 'pedigree', 'certificate-black', 'a4-blank', 'QR-lines']; +const featuredTemplates = ['invoice', 'quotes','pedigree', 'certificate-black', 'a4-blank', 'QR-lines']; function generateTemplatesListJson() { const items = fs.readdirSync(templatesDir, { withFileTypes: true }); diff --git a/playground/src/routes/Templates.tsx b/playground/src/routes/Templates.tsx index 9b0d1718..83e1c692 100644 --- a/playground/src/routes/Templates.tsx +++ b/playground/src/routes/Templates.tsx @@ -14,7 +14,18 @@ declare global { } } -const CopyButton = ({ ui, name }: { ui: 'designer' | 'form-viewer', name: string }) => { +type TemplateData = { + name: string; + author: string; +}; + +type UIType = 'designer' | 'form-viewer'; + +// Constants +const DEVIN_AI_AUTHOR = "Devin AI"; +const DEVIN_INVITE_URL = "https://app.devin.ai/invite/KyOTXVPrlFl2TjcT"; + +const CopyButton = ({ ui, name }: { ui: UIType, name: string }) => { const handleCopy = async () => { const shareableUrl = `https://pdfme.com/template-design?ui=${ui}&template=${name}`; try { @@ -50,34 +61,69 @@ const CopyButton = ({ ui, name }: { ui: 'designer' | 'form-viewer', name: string ); }; - +// Author link component to avoid duplication +const AuthorLink = ({ author }: { author: string }) => { + if (author === DEVIN_AI_AUTHOR) { + return ( + + {author} + + ); + } + + return ( + + {author} + + ); +}; function TemplatesApp({ isEmbedded }: { isEmbedded: boolean }) { const navigate = useNavigate(); - const [templates, setTemplates] = useState<{ name: string; author: string }[]>([]); + const [templates, setTemplates] = useState([]); const [avatarUrlMap, setAvatarUrlMap] = useState<{ [key: string]: string }>({}); + + // Fetch templates and author avatars useEffect(() => { fetch('/template-assets/index.json') .then((response) => response.json()) - .then((data: { name: string; author: string }[]) => { + .then((data: TemplateData[]) => { setTemplates(data); + const authors = new Set(data.map(({ author }) => author)); const avatarUrlMap: { [key: string]: string } = {}; + Promise.all( - Array.from(authors).map((author) => - fetch(`https://api.github.com/users/${author}`) - .then((response) => response.json()) - .then((data) => { - avatarUrlMap[author] = data.avatar_url; - }) - ) + Array.from(authors).map((author) => { + if (author === DEVIN_AI_AUTHOR) { + avatarUrlMap[author] = "/imgs/devin.svg"; + return Promise.resolve(); + } else { + return fetch(`https://api.github.com/users/${author}`) + .then((res) => res.json()) + .then((ghData) => { + avatarUrlMap[author] = ghData.avatar_url; + }); + } + }) ).then(() => { setAvatarUrlMap(avatarUrlMap); }); }); }, []); + // Load ethical ads useEffect(() => { if (window.ethicalads && typeof window.ethicalads.load === "function") { window.ethicalads.load(); @@ -86,22 +132,112 @@ function TemplatesApp({ isEmbedded }: { isEmbedded: boolean }) { } }, [templates]); - const navigateToDesigner = (name: string) => { + // Unified navigation function + const navigateTo = (name: string, ui: UIType) => { if (isEmbedded) { - window.parent.postMessage({ type: 'navigate', payload: { name, ui: 'designer' } }, '*'); + window.parent.postMessage({ type: 'navigate', payload: { name, ui } }, '*'); } else { - navigate(`/?template=${name}`) + const path = ui === 'designer' ? '/' : '/form-viewer'; + navigate(`${path}?template=${name}`); } - } + }; + // Template card component + const TemplateCard = ({ name, author, index }: TemplateData & { index: number }) => { + return ( + + {index === 3 && ( +
+ )} +
+
+
+ navigateTo(name, 'designer')} + alt={fromKebabCase(name)} + src={`/template-assets/${name}/thumbnail.png`} + className="border border-gray-100 size-full object-contain cursor-pointer" + /> +
+
+

+ {fromKebabCase(name)} +

+

+ by{" "} + {avatarUrlMap[author] && ( + {author} + )} + +

+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + ); + }; - const navigateToFormViewer = (name: string) => { - if (isEmbedded) { - window.parent.postMessage({ type: 'navigate', payload: { name, ui: 'form-viewer' } }, '*'); - } else { - navigate(`/form-viewer?template=${name}`) - } - } + // Contribution card component + const ContributionCard = () => ( +
+
+
+ + Contribute Your Template ❤️ + +

+ Share the templates you've created! Contributing your templates is extremely beneficial for other users. +

+
+ +
+
+ ); return (
@@ -109,8 +245,7 @@ function TemplatesApp({ isEmbedded }: { isEmbedded: boolean }) {

Sample Templates

- If you can’t find the template you need, you can request it on Github. - + If you can't find the template you need, you can request it on Github.

{React.createElement(ExternalButton, { @@ -120,103 +255,14 @@ function TemplatesApp({ isEmbedded }: { isEmbedded: boolean }) {
- {templates.map(({ name, author }, index) => - {index === 3 &&
} -
-
-
- { navigateToDesigner(name) }} - alt={fromKebabCase(name)} - src={`/template-assets/${name}/thumbnail.png`} - className="border border-gray-100 size-full object-contain cursor-pointer" - /> -
-
-

- {fromKebabCase(name)} -

-

- by{" "} - {avatarUrlMap[author] && {author}} - {" "} - - {author} - -

-
-
-
- - -
-
-
-
- - -
-
-
-
- )} -
-
-
- - Contribute Your Template ❤️ - -

- Share the templates you've created! Contributing your templates is extremely beneficial for other users. -

-
- -
-
+ {templates.map((template, index) => ( + + ))} +
- ) + ); } export default TemplatesApp;