@antontranelis/money-printer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Fritz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # Money Generator
2
+
3
+ Create personalized time vouchers that look like real currency. Design your own "Zeitgutscheine" (time vouchers) with custom portraits, names, and descriptions - perfect as unique gifts.
4
+
5
+ ![Money Generator Preview](docs/preview.png)
6
+
7
+ ## Features
8
+
9
+ - **Custom Portrait**: Upload your photo and optionally enhance it with AI for a vintage currency look
10
+ - **Personalization**: Add your name, email, and phone number
11
+ - **Multiple Denominations**: Choose between 1, 5, or 10 hour vouchers
12
+ - **Bilingual**: Full support for German and English
13
+ - **High-Quality Export**: Download as print-ready PDF (A4 landscape)
14
+ - **Portrait Zoom**: Adjust the portrait size with an intuitive zoom slider
15
+
16
+ ## Demo
17
+
18
+ Try it live: [Money Generator Demo](https://yourusername.github.io/money-generator/)
19
+
20
+ ## Tech Stack
21
+
22
+ - **React 19** - UI Framework
23
+ - **TypeScript** - Type Safety
24
+ - **Zustand** - State Management
25
+ - **jsPDF** - PDF Generation
26
+ - **Tailwind CSS + DaisyUI** - Styling
27
+ - **Vite** - Build Tool
28
+
29
+ ## Getting Started
30
+
31
+ ### Prerequisites
32
+
33
+ - Node.js 18+
34
+ - npm or yarn
35
+
36
+ ### Installation
37
+
38
+ ```bash
39
+ # Clone the repository
40
+ git clone https://github.com/yourusername/money-generator.git
41
+ cd money-generator
42
+
43
+ # Install dependencies
44
+ npm install
45
+
46
+ # Start development server
47
+ npm run dev
48
+ ```
49
+
50
+ ### Build
51
+
52
+ ```bash
53
+ # Build for production
54
+ npm run build
55
+
56
+ # Preview production build
57
+ npm run preview
58
+ ```
59
+
60
+ ## Usage
61
+
62
+ 1. **Upload a Portrait**: Click the upload area or drag & drop an image
63
+ 2. **Adjust Zoom**: Use the slider to zoom in/out on your portrait
64
+ 3. **Enter Details**: Fill in your name and contact information
65
+ 4. **Choose Hours**: Select 1, 5, or 10 hours for your voucher
66
+ 5. **Add Description**: Optionally add a custom description
67
+ 6. **Download PDF**: Click the download button to get your print-ready voucher
68
+
69
+ ## AI Portrait Enhancement (Optional)
70
+
71
+ The app includes optional AI-powered portrait enhancement using Stability AI. This transforms your photo into a vintage currency engraving style.
72
+
73
+ To enable:
74
+ 1. Get an API key from [Stability AI](https://stability.ai/)
75
+ 2. Click "Add API Key for better results" in the app
76
+ 3. Or set the environment variable: `VITE_STABILITY_API_KEY=your-key`
77
+
78
+ Without an API key, a basic canvas-based enhancement is available.
79
+
80
+ ## Customization
81
+
82
+ ### Templates
83
+
84
+ Templates are located in `public/templates/`. The app supports two template sets:
85
+
86
+ - **German (DE)**: High-DPI templates (6144x4096px)
87
+ - **English (EN)**: Standard-DPI templates (1536x1024px)
88
+
89
+ To customize, replace the template images and update the layout coordinates in `src/constants/templates.ts`.
90
+
91
+ ### Translations
92
+
93
+ All text is internationalized. Add or modify translations in `src/constants/translations.ts`.
94
+
95
+ ## Project Structure
96
+
97
+ ```
98
+ src/
99
+ ├── components/ # React components
100
+ │ ├── BillForm.tsx # Main form container
101
+ │ ├── BillPreview.tsx # Canvas preview
102
+ │ ├── ExportButton.tsx # PDF download
103
+ │ ├── PortraitUpload.tsx
104
+ │ └── ...
105
+ ├── services/ # Core services
106
+ │ ├── pdfGenerator.ts # PDF creation
107
+ │ ├── canvasRenderer.ts # Canvas drawing
108
+ │ └── stabilityAI.ts # AI enhancement
109
+ ├── stores/ # Zustand stores
110
+ │ └── billStore.ts # App state
111
+ ├── types/ # TypeScript types
112
+ ├── constants/ # Templates & translations
113
+ └── hooks/ # Custom React hooks
114
+ ```
115
+
116
+ ## Contributing
117
+
118
+ Contributions are welcome! Please feel free to submit a Pull Request.
119
+
120
+ 1. Fork the repository
121
+ 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
122
+ 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
123
+ 4. Push to the branch (`git push origin feature/AmazingFeature`)
124
+ 5. Open a Pull Request
125
+
126
+ ## License
127
+
128
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
129
+
130
+ ## Acknowledgments
131
+
132
+ - Inspired by the concept of "Zeitgutscheine" (time vouchers) as meaningful gifts
133
+ - Built with modern React patterns and best practices
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react/jsx-runtime"),ge=require("zustand"),fe=require("zustand/middleware"),b=require("react"),be=require("jspdf");var T=typeof document<"u"?document.currentScript:null;const J={personalInfo:{name:"",email:"",phone:""},voucherConfig:{hours:1,description:"",language:"de"},portrait:{original:null,enhanced:null,useEnhanced:!1,zoom:1},currentSide:"front",isEnhancing:!1,isExporting:!1},m=ge.create()(fe.persist(e=>({...J,setPersonalInfo:t=>e(a=>({personalInfo:{...a.personalInfo,...t}})),setVoucherConfig:t=>e(a=>({voucherConfig:{...a.voucherConfig,...t}})),setPortrait:(t,a=null)=>e({portrait:{original:t,enhanced:a,useEnhanced:!1,zoom:1}}),setEnhancedPortrait:t=>e(a=>({portrait:{...a.portrait,enhanced:t,useEnhanced:t!==null}})),toggleUseEnhanced:()=>e(t=>({portrait:{...t.portrait,useEnhanced:t.portrait.enhanced?!t.portrait.useEnhanced:!1}})),setPortraitZoom:t=>e(a=>({portrait:{...a.portrait,zoom:t}})),setCurrentSide:t=>e({currentSide:t}),flipSide:()=>e(t=>({currentSide:t.currentSide==="front"?"back":"front"})),setIsEnhancing:t=>e({isEnhancing:t}),setIsExporting:t=>e({isExporting:t}),setLanguage:t=>e(a=>({voucherConfig:{...a.voucherConfig,language:t}})),setHours:t=>e(a=>({voucherConfig:{...a.voucherConfig,hours:t}})),reset:()=>e(J)}),{name:"money-generator-storage",partialize:e=>({personalInfo:e.personalInfo,voucherConfig:e.voucherConfig})})),xe={header:{title:"Money Generator",subtitle:"Erstelle deinen persönlichen Zeitgutschein"},form:{personalInfo:{title:"Persönliche Daten",name:"Name",namePlaceholder:"Dein Name",email:"E-Mail",emailPlaceholder:"deine@email.de",phone:"Telefon",phonePlaceholder:"+49 123 456789"},portrait:{title:"Portrait",upload:"Bild hochladen",dragDrop:"oder hierher ziehen",enhance:"Mit AI verbessern",enhancing:"Wird verbessert...",useOriginal:"Original verwenden",useEnhanced:"Verbessertes verwenden",zoom:"Zoom"},voucher:{title:"Gutschein",hours:"Stunden",hourLabel:"Stunde",hoursLabel:"Stunden",description:"Beschreibung",descriptionPlaceholder:"Was kann mit diesem Gutschein eingelöst werden?"}},preview:{front:"Vorderseite",back:"Rückseite",flip:"Umdrehen"},export:{button:"Als PDF herunterladen",exporting:"PDF wird erstellt...",success:"Download gestartet!"},bill:{descriptionText:"Für diesen Schein erhältst du {hours} {hourLabel} meiner Zeit oder ein gleichwertiges Dankeschön"}},ye={header:{title:"Money Generator",subtitle:"Create your personal time voucher"},form:{personalInfo:{title:"Personal Information",name:"Name",namePlaceholder:"Your name",email:"Email",emailPlaceholder:"your@email.com",phone:"Phone",phonePlaceholder:"+1 234 567890"},portrait:{title:"Portrait",upload:"Upload image",dragDrop:"or drag and drop",enhance:"Enhance with AI",enhancing:"Enhancing...",useOriginal:"Use original",useEnhanced:"Use enhanced",zoom:"Zoom"},voucher:{title:"Voucher",hours:"Hours",hourLabel:"hour",hoursLabel:"hours",description:"Description",descriptionPlaceholder:"What can be redeemed with this voucher?"}},preview:{front:"Front",back:"Back",flip:"Flip"},export:{button:"Download as PDF",exporting:"Creating PDF...",success:"Download started!"},bill:{descriptionText:"This voucher entitles you to {hours} {hourLabel} of my time or an equivalent thank you"}},ve={de:xe,en:ye};function I(e){return ve[e]}function K(e,t,a){if(a&&a.trim())return a;const r=I(e),o=t===1?r.form.voucher.hourLabel:r.form.voucher.hoursLabel;return r.bill.descriptionText.replace("{hours}",t.toString()).replace("{hourLabel}",o)}function we(){const e=m(o=>o.voucherConfig.language),t=m(o=>o.personalInfo),a=m(o=>o.setPersonalInfo),r=I(e);return n.jsxs("div",{className:"space-y-4",children:[n.jsxs("div",{className:"form-control",children:[n.jsx("label",{className:"label",children:n.jsx("span",{className:"label-text font-medium",children:r.form.personalInfo.name})}),n.jsx("input",{type:"text",placeholder:r.form.personalInfo.namePlaceholder,className:"input input-bordered w-full",value:t.name,onChange:o=>a({name:o.target.value})})]}),n.jsxs("div",{className:"form-control",children:[n.jsx("label",{className:"label",children:n.jsx("span",{className:"label-text font-medium",children:r.form.personalInfo.email})}),n.jsx("input",{type:"email",placeholder:r.form.personalInfo.emailPlaceholder,className:"input input-bordered w-full",value:t.email,onChange:o=>a({email:o.target.value})})]}),n.jsxs("div",{className:"form-control",children:[n.jsx("label",{className:"label",children:n.jsx("span",{className:"label-text font-medium",children:r.form.personalInfo.phone})}),n.jsx("input",{type:"tel",placeholder:r.form.personalInfo.phonePlaceholder,className:"input input-bordered w-full",value:t.phone,onChange:o=>a({phone:o.target.value})})]})]})}const H={},je="https://api.stability.ai/v1/generation",ke={vintage:"portrait in the style of vintage currency engraving, fine line work, crosshatching, sepia tones, detailed stippling, classic bank note portrait style",engraved:"portrait as detailed intaglio engraving, currency bill style, fine parallel lines, high contrast, official government portrait",currency:"portrait rendered as US dollar bill engraving, official currency portrait style, green tint, fine line engraving technique"};function Pe(e){var s;const t=e.split(","),a=((s=t[0].match(/:(.*?);/))==null?void 0:s[1])||"image/png",r=atob(t[1]),o=r.length,l=new Uint8Array(o);for(let d=0;d<o;d++)l[d]=r.charCodeAt(d);return new Blob([l],{type:a})}function O(){var t;const e=typeof{url:typeof document>"u"?require("url").pathToFileURL(__filename).href:T&&T.tagName.toUpperCase()==="SCRIPT"&&T.src||new URL("index.cjs",document.baseURI).href}<"u"&&(H==null?void 0:H.VITE_STABILITY_API_KEY)||typeof process<"u"&&((t=process.env)==null?void 0:t.NEXT_PUBLIC_STABILITY_API_KEY);return e&&e!=="your-api-key-here"?e:typeof localStorage<"u"?localStorage.getItem("stability_api_key"):null}function ee(e){localStorage.setItem("stability_api_key",e)}function z(){return O()!==null}async function te(e){const t=O();if(!t)throw new Error("No Stability AI API key configured");const{imageDataUrl:a,style:r,strength:o=.35}=e,l=Pe(a),s=new FormData;s.append("init_image",l,"portrait.png"),s.append("init_image_mode","IMAGE_STRENGTH"),s.append("image_strength",String(1-o)),s.append("text_prompts[0][text]",ke[r]),s.append("text_prompts[0][weight]","1"),s.append("cfg_scale","7"),s.append("samples","1"),s.append("steps","30");const i=await fetch(`${je}/stable-diffusion-xl-1024-v1-0/image-to-image`,{method:"POST",headers:{Authorization:`Bearer ${t}`,Accept:"application/json"},body:s});if(!i.ok){const g=await i.text();throw console.error("Stability AI error:",g),i.status===401?new Error("Invalid API key"):i.status===402?new Error("Insufficient credits"):i.status===429?new Error("Rate limit exceeded. Please try again later."):new Error(`API error: ${i.status}`)}const c=await i.json();if(!c.artifacts||c.artifacts.length===0)throw new Error("No image generated");return`data:image/png;base64,${c.artifacts[0].base64}`}async function M(e){return new Promise((t,a)=>{const r=new Image;r.onload=()=>{const o=document.createElement("canvas"),l=o.getContext("2d");if(!l){a(new Error("Failed to get canvas context"));return}o.width=r.width,o.height=r.height,l.filter="sepia(0.3) contrast(1.1) saturate(0.9)",l.drawImage(r,0,0),t(o.toDataURL("image/png"))},r.onerror=()=>a(new Error("Failed to load image")),r.src=e})}function Ie(){const[e,t]=b.useState(!1),[a,r]=b.useState(null),[o,l]=b.useState(z()),s=b.useCallback(()=>r(null),[]),d=b.useCallback(c=>{ee(c),l(!0),r(null)},[]);return{enhance:b.useCallback(async(c,g="vintage")=>{t(!0),r(null);try{return z()?await te({imageDataUrl:c,style:g,strength:.35}):await M(c)}catch(h){const f=h instanceof Error?h.message:"Enhancement failed";if(r(f),f.includes("API")||f.includes("key"))try{return await M(c)}catch{throw h}throw h}finally{t(!1)}},[]),isEnhancing:e,error:a,hasKey:o,setApiKey:d,clearError:s}}function ne({isOpen:e,onClose:t,onSubmit:a}){const r=m(c=>c.voucherConfig.language),[o,l]=b.useState("");if(!e)return null;const s=c=>{c.preventDefault(),o.trim()&&(a(o.trim()),l(""),t())},i={de:{title:"Stability AI API Key",description:"Um die AI-Bildverbesserung zu nutzen, benötigst du einen Stability AI API Key. Du kannst ihn auf platform.stability.ai erhalten.",placeholder:"sk-...",submit:"Speichern",cancel:"Abbrechen",hint:"Der Key wird lokal in deinem Browser gespeichert."},en:{title:"Stability AI API Key",description:"To use AI image enhancement, you need a Stability AI API key. You can get one at platform.stability.ai.",placeholder:"sk-...",submit:"Save",cancel:"Cancel",hint:"The key is stored locally in your browser."}}[r];return n.jsxs("dialog",{className:"modal modal-open",children:[n.jsxs("div",{className:"modal-box",children:[n.jsx("h3",{className:"font-bold text-lg",children:i.title}),n.jsx("p",{className:"py-4 text-sm opacity-80",children:i.description}),n.jsxs("form",{onSubmit:s,children:[n.jsxs("div",{className:"form-control",children:[n.jsx("input",{type:"password",placeholder:i.placeholder,className:"input input-bordered w-full",value:o,onChange:c=>l(c.target.value),autoFocus:!0}),n.jsx("label",{className:"label",children:n.jsx("span",{className:"label-text-alt",children:i.hint})})]}),n.jsxs("div",{className:"modal-action",children:[n.jsx("button",{type:"button",className:"btn btn-ghost",onClick:t,children:i.cancel}),n.jsx("button",{type:"submit",className:"btn btn-primary",disabled:!o.trim(),children:i.submit})]})]})]}),n.jsx("form",{method:"dialog",className:"modal-backdrop",children:n.jsx("button",{onClick:t,children:"close"})})]})}function Ne(){const e=m(u=>u.voucherConfig.language),t=m(u=>u.portrait),a=m(u=>u.setPortrait),r=m(u=>u.setEnhancedPortrait),o=m(u=>u.toggleUseEnhanced),l=m(u=>u.setPortraitZoom),{enhance:s,isEnhancing:d,error:i,hasKey:c,setApiKey:g}=Ie(),h=I(e),f=b.useRef(null),[x,p]=b.useState(!1),[N,k]=b.useState(!1),j=b.useCallback(u=>{if(!u.type.startsWith("image/"))return;const v=new FileReader;v.onload=_=>{var X;const me=(X=_.target)==null?void 0:X.result;a(me)},v.readAsDataURL(u)},[a]),S=b.useCallback(u=>{u.preventDefault(),p(!1);const v=u.dataTransfer.files[0];v&&j(v)},[j]),y=b.useCallback(u=>{u.preventDefault(),p(!0)},[]),P=b.useCallback(u=>{u.preventDefault(),p(!1)},[]),L=()=>{var u;(u=f.current)==null||u.click()},A=u=>{var _;const v=(_=u.target.files)==null?void 0:_[0];v&&j(v)},$=async()=>{if(t.original){if(!c){k(!0);return}try{const u=await s(t.original,"vintage");r(u)}catch(u){console.error("Enhancement failed:",u)}}},U=async u=>{if(g(u),t.original)try{const v=await s(t.original,"vintage");r(v)}catch(v){console.error("Enhancement failed:",v)}},he=()=>{a(null)},pe=t.useEnhanced&&t.enhanced?t.enhanced:t.original;return n.jsxs("div",{className:"space-y-4",children:[t.original?n.jsxs("div",{className:"space-y-4",children:[n.jsx("div",{className:"flex justify-center",children:n.jsxs("div",{className:"relative",children:[n.jsx("div",{className:"w-32 h-32 rounded-full overflow-hidden border-4 border-currency-gold shadow-lg",children:n.jsx("img",{src:pe||"",alt:"Portrait",className:"w-full h-full object-cover",style:{transform:`scale(${t.zoom})`}})}),n.jsx("button",{className:"btn btn-circle btn-xs btn-error absolute -top-1 -right-1",onClick:he,children:n.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-4 w-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:n.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})}),n.jsxs("div",{className:"form-control",children:[n.jsxs("label",{className:"label",children:[n.jsx("span",{className:"label-text",children:h.form.portrait.zoom}),n.jsxs("span",{className:"label-text-alt",children:[Math.round(t.zoom*100),"%"]})]}),n.jsx("input",{type:"range",min:"0.5",max:"2",step:"0.05",value:t.zoom,onChange:u=>l(parseFloat(u.target.value)),className:"range range-primary range-sm"})]}),c&&n.jsxs("div",{className:"flex flex-col gap-2",children:[n.jsx("button",{className:`btn btn-secondary ${d?"loading":""}`,onClick:$,disabled:d,children:d?n.jsxs(n.Fragment,{children:[n.jsx("span",{className:"loading loading-spinner loading-sm"}),h.form.portrait.enhancing]}):n.jsxs(n.Fragment,{children:[n.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-5 w-5 mr-1",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:n.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"})}),h.form.portrait.enhance]})}),i&&n.jsxs("div",{className:"alert alert-warning text-sm py-2",children:[n.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"stroke-current shrink-0 h-5 w-5",fill:"none",viewBox:"0 0 24 24",children:n.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})}),n.jsx("span",{children:i})]}),t.enhanced&&n.jsx("div",{className:"form-control",children:n.jsxs("label",{className:"label cursor-pointer justify-start gap-3",children:[n.jsx("input",{type:"checkbox",className:"toggle toggle-primary",checked:t.useEnhanced,onChange:o}),n.jsx("span",{className:"label-text",children:t.useEnhanced?h.form.portrait.useEnhanced:h.form.portrait.useOriginal})]})})]})]}):n.jsxs("div",{className:`border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors ${x?"border-primary bg-primary/10":"border-base-300 hover:border-primary hover:bg-base-200"}`,onDrop:S,onDragOver:y,onDragLeave:P,onClick:L,children:[n.jsx("input",{ref:f,type:"file",accept:"image/*",className:"hidden",onChange:A}),n.jsxs("div",{className:"flex flex-col items-center gap-2",children:[n.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-12 w-12 text-base-content/50",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:n.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"})}),n.jsx("p",{className:"font-medium",children:h.form.portrait.upload}),n.jsx("p",{className:"text-sm text-base-content/60",children:h.form.portrait.dragDrop})]})]}),n.jsx(ne,{isOpen:N,onClose:()=>k(!1),onSubmit:U})]})}const Ce=[1,5,10];function Ee(){const e=m(s=>s.voucherConfig.language),t=m(s=>s.voucherConfig),a=m(s=>s.setHours),r=m(s=>s.setVoucherConfig),o=I(e),l=s=>s===1?o.form.voucher.hourLabel:o.form.voucher.hoursLabel;return n.jsxs("div",{className:"space-y-4",children:[n.jsxs("div",{className:"form-control",children:[n.jsx("label",{className:"label",children:n.jsx("span",{className:"label-text font-medium",children:o.form.voucher.hours})}),n.jsx("div",{className:"join w-full",children:Ce.map(s=>n.jsxs("button",{className:`join-item btn flex-1 ${t.hours===s?"btn-primary":"btn-outline"}`,onClick:()=>a(s),children:[s," ",l(s)]},s))})]}),n.jsxs("div",{className:"form-control",children:[n.jsx("label",{className:"label",children:n.jsx("span",{className:"label-text font-medium",children:o.form.voucher.description})}),n.jsx("textarea",{className:"textarea textarea-bordered h-24 resize-none w-full",placeholder:o.form.voucher.descriptionPlaceholder,value:t.description,onChange:s=>r({description:s.target.value}),maxLength:200}),n.jsxs("label",{className:"label",children:[n.jsx("span",{className:"label-text-alt"}),n.jsxs("span",{className:"label-text-alt",children:[t.description.length,"/200"]})]})]})]})}const w=typeof{url:typeof document>"u"?require("url").pathToFileURL(__filename).href:T&&T.tagName.toUpperCase()==="SCRIPT"&&T.src||new URL("index.cjs",document.baseURI).href}<"u"&&"/"||"/",ae={en:{1:{front:`${w}templates/front_ldpi_en.png`,back:`${w}templates/back_ldpi_en.png`,width:1536,height:1024},5:{front:`${w}templates/front_ldpi_en.png`,back:`${w}templates/back_ldpi_en.png`,width:1536,height:1024},10:{front:`${w}templates/front_ldpi_en.png`,back:`${w}templates/back_ldpi_en.png`,width:1536,height:1024}},de:{1:{front:`${w}templates/front_hdpi_de.webp`,back:`${w}templates/back_hdpi_de.webp`,width:6144,height:3200},5:{front:`${w}templates/front_hdpi_de.webp`,back:`${w}templates/back_hdpi_de.webp`,width:6144,height:3200},10:{front:`${w}templates/front_hdpi_de.webp`,back:`${w}templates/back_hdpi_de.webp`,width:6144,height:3200}}},C={front:{portrait:{x:768,y:490,radiusX:236,radiusY:258},namePlate:{x:768,y:848,fontSize:36,maxWidth:380,align:"center"}},back:{portrait:{x:0,y:0,radiusX:0,radiusY:0},namePlate:{x:768,y:832,fontSize:36,maxWidth:380,align:"center"},contactInfo:{x:380,y:500,fontSize:38,lineHeight:55,align:"center"},description:{x:1150,y:500,fontSize:38,maxWidth:480,lineHeight:42,align:"center"}}},E={front:{portrait:{x:3074,y:1530,radiusX:942,radiusY:1020},namePlate:{x:3072,y:2950,fontSize:144,maxWidth:1520,align:"center"}},back:{portrait:{x:0,y:0,radiusX:0,radiusY:0},namePlate:{x:3072,y:2930,fontSize:144,maxWidth:1520,align:"center"},contactInfo:{x:1520,y:1680,fontSize:160,lineHeight:280,align:"center"},description:{x:4600,y:1680,fontSize:145,maxWidth:2e3,lineHeight:210,align:"center"}}};function W(e){return e==="de"?E:C}function q(e,t){return ae[e][t]}const F=new Map;async function D(e){return F.has(e)?F.get(e):new Promise((t,a)=>{const r=new Image;r.crossOrigin="anonymous",r.onload=()=>{F.set(e,r),t(r)},r.onerror=a,r.src=e})}function Y(e,t,a,r){e.drawImage(t,0,0,a,r)}function re(e,t,a,r,o,l,s=1){e.save(),e.beginPath(),e.ellipse(a,r,o,l,0,0,Math.PI*2),e.closePath(),e.clip();const d=t.width/t.height,i=o/l,c=o*2,g=l*2;let h,f;d>i?(f=g,h=g*d):(h=c,f=c/d),h*=s,f*=s;const x=a-h/2,p=r-f/2;e.drawImage(t,x,p,h,f),e.restore()}function V(e,t,a,r="#2a3a2a"){e.save(),e.font=`${a.fontSize}px "Times New Roman", serif`,e.textAlign=a.align||"center",e.textBaseline="middle",e.fillStyle=r,a.maxWidth?e.fillText(t,a.x,a.y,a.maxWidth):e.fillText(t,a.x,a.y),e.restore()}function oe(e,t,a,r="#2a3a2a"){e.save(),e.font=`${a.fontSize}px "Times New Roman", serif`,e.textAlign=a.align||"center",e.textBaseline="top",e.fillStyle=r;const o=a.maxWidth||400,l=a.lineHeight||a.fontSize*1.4,s=t.split(" "),d=[];let i="";for(const h of s){const f=i?`${i} ${h}`:h;e.measureText(f).width>o&&i?(d.push(i),i=h):i=f}i&&d.push(i);const c=d.length*l;let g=a.y-c/2;for(const h of d)e.fillText(h,a.x,g),g+=l;e.restore()}function se(e,t,a,r,o,l="#2a3a2a"){e.save(),e.font=`${o.fontSize}px "Times New Roman", serif`,e.textAlign=o.align||"center",e.textBaseline="middle",e.fillStyle=l;const s=o.lineHeight||o.fontSize*1.8,d=[t,a,r].filter(Boolean),i=(d.length-1)*s;let c=o.y-i/2;for(const g of d)g&&(e.fillText(g,o.x,c),c+=s);e.restore()}async function Z(e,t,a,r,o,l,s,d=1){const i=e.getContext("2d");if(!i)return;e.width=l,e.height=s,i.clearRect(0,0,l,s);const c=await D(t);if(Y(i,c,l,s),a)try{const g=await D(a);re(i,g,o.portrait.x,o.portrait.y,o.portrait.radiusX,o.portrait.radiusY,d)}catch(g){console.error("Failed to load portrait:",g)}r&&V(i,r,o.namePlate)}async function G(e,t,a,r,o,l,s,d,i){const c=e.getContext("2d");if(!c)return;e.width=d,e.height=i,c.clearRect(0,0,d,i);const g=await D(t);Y(c,g,d,i),s.contactInfo&&(a||r||o)&&se(c,a,r,o,s.contactInfo),s.description&&l&&oe(c,l,s.description),a&&V(c,a,s.namePlate)}function Se(){const e=m(y=>y.voucherConfig.language),t=m(y=>y.voucherConfig.hours),a=m(y=>y.voucherConfig.description),r=m(y=>y.personalInfo),o=m(y=>y.portrait),l=m(y=>y.currentSide),s=m(y=>y.flipSide),d=I(e),i=b.useRef(null),c=b.useRef(null),g=b.useRef(null),[h,f]=b.useState(!1),x=q(e,t),p=W(e),N=o.useEnhanced&&o.enhanced?o.enhanced:o.original,k=K(e,t,a);b.useEffect(()=>{i.current&&Z(i.current,x.front,N,r.name,p.front,x.width,x.height,o.zoom)},[x,N,r.name,p,o.zoom]),b.useEffect(()=>{c.current&&G(c.current,x.back,r.name,r.email,r.phone,k,p.back,x.width,x.height)},[x,r,k,p]);const j=()=>{f(!0),setTimeout(()=>{s(),f(!1)},150)},S=x.width/x.height;return n.jsxs("div",{className:"space-y-4",children:[n.jsxs("div",{className:"flex justify-between items-center",children:[n.jsxs("div",{className:"tabs tabs-boxed bg-base-200",children:[n.jsx("button",{className:`tab ${l==="front"?"tab-active bg-primary text-primary-content font-semibold":""}`,onClick:()=>l!=="front"&&j(),children:d.preview.front}),n.jsx("button",{className:`tab ${l==="back"?"tab-active bg-primary text-primary-content font-semibold":""}`,onClick:()=>l!=="back"&&j(),children:d.preview.back})]}),n.jsxs("button",{className:"btn btn-ghost btn-sm",onClick:j,children:[n.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-5 w-5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:n.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})}),d.preview.flip]})]}),n.jsxs("div",{ref:g,className:"relative w-full overflow-hidden shadow-lg",style:{aspectRatio:S},children:[n.jsx("canvas",{ref:i,className:`absolute inset-0 w-full h-full transition-all duration-300 ${l==="front"?h?"opacity-0 scale-95":"opacity-100 scale-100":"opacity-0 scale-95 pointer-events-none"}`}),n.jsx("canvas",{ref:c,className:`absolute inset-0 w-full h-full transition-all duration-300 ${l==="back"?h?"opacity-0 scale-95":"opacity-100 scale-100":"opacity-0 scale-95 pointer-events-none"}`})]})]})}function Te(){const e=b.useRef(null),t=b.useRef(null);return{frontCanvasRef:e,backCanvasRef:t}}async function ie(e){const{frontTemplateSrc:t,backTemplateSrc:a,templateWidth:r,templateHeight:o,layout:l,portrait:s,portraitZoom:d=1,name:i,email:c,phone:g,description:h}=e,f=document.createElement("canvas"),x=document.createElement("canvas");await Promise.all([Z(f,t,s,i,l.front,r,o,d),G(x,a,i,c,g,h,l.back,r,o)]);const p=new be({orientation:"landscape",unit:"mm",format:"a4"}),N=297,k=210,j=10,S=r/o;let y=N-j*2,P=y/S;P>k-j*2&&(P=k-j*2,y=P*S);const L=(N-y)/2,A=(k-P)/2,$=f.toDataURL("image/jpeg",.95);p.addImage($,"JPEG",L,A,y,P),p.addPage();const U=x.toDataURL("image/jpeg",.95);return p.addImage(U,"JPEG",L,A,y,P),p.output("blob")}function le(e,t){const a=URL.createObjectURL(e),r=document.createElement("a");r.href=a,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(a)}async function ce(e){const t=await ie(e);le(t,e.filename)}function Le(){const e=m(p=>p.voucherConfig.language),t=m(p=>p.voucherConfig.hours),a=m(p=>p.voucherConfig.description),r=m(p=>p.personalInfo),o=m(p=>p.portrait),l=m(p=>p.isExporting),s=m(p=>p.setIsExporting),d=I(e),i=q(e,t),c=W(e),g=o.useEnhanced&&o.enhanced?o.enhanced:o.original,h=K(e,t,a),f=r.name.trim().length>0,x=async()=>{if(!(!f||l)){s(!0);try{const p=`zeitgutschein-${t}h-${r.name.replace(/\s+/g,"-").toLowerCase()}.pdf`;await ce({frontTemplateSrc:i.front,backTemplateSrc:i.back,templateWidth:i.width,templateHeight:i.height,layout:c,portrait:g,portraitZoom:o.zoom,name:r.name,email:r.email,phone:r.phone,description:h,filename:p})}catch(p){console.error("PDF export failed:",p)}finally{s(!1)}}};return n.jsx("button",{className:`btn btn-primary flex-1 ${l?"loading":""}`,onClick:x,disabled:!f||l,children:l?n.jsxs(n.Fragment,{children:[n.jsx("span",{className:"loading loading-spinner loading-sm"}),d.export.exporting]}):n.jsxs(n.Fragment,{children:[n.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-5 w-5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:n.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"})}),d.export.button]})})}function de(){const e=m(r=>r.voucherConfig.language),t=m(r=>r.setLanguage),a=r=>{t(r)};return n.jsxs("div",{className:"join",children:[n.jsx("button",{className:`join-item btn btn-sm ${e==="de"?"btn-active btn-primary":"btn-ghost"}`,onClick:()=>a("de"),children:"DE"}),n.jsx("button",{className:`join-item btn btn-sm ${e==="en"?"btn-active btn-primary":"btn-ghost"}`,onClick:()=>a("en"),children:"EN"})]})}function Ae(){const e=m(a=>a.voucherConfig.language),t=I(e);return n.jsxs("div",{className:"navbar bg-currency-green text-currency-cream shadow-lg",children:[n.jsx("div",{className:"navbar-start",children:n.jsx("a",{className:"btn btn-ghost text-xl font-currency font-bold",children:t.header.title})}),n.jsx("div",{className:"navbar-center hidden sm:flex",children:n.jsx("span",{className:"text-sm opacity-80",children:t.header.subtitle})}),n.jsx("div",{className:"navbar-end",children:n.jsx(de,{})})]})}const R="/",_e={id:"time-voucher-classic-de",name:"Zeitgutschein Classic",type:"time-voucher",category:"classic",images:{front:`${R}templates/front_hdpi_de.jpg`,back:`${R}templates/back_hdpi_de.jpg`,width:6144,height:4096},fields:[{id:"name",type:"text",label:{de:"Name",en:"Name"},required:!0,validation:{minLength:1,maxLength:50}},{id:"hours",type:"select",label:{de:"Stunden",en:"Hours"},required:!0,options:["1","5","10"]},{id:"portrait",type:"image",label:{de:"Portrait",en:"Portrait"},required:!1},{id:"email",type:"text",label:{de:"E-Mail",en:"Email"},required:!1},{id:"phone",type:"text",label:{de:"Telefon",en:"Phone"},required:!1},{id:"description",type:"textarea",label:{de:"Beschreibung",en:"Description"},required:!1,validation:{maxLength:200}}],layout:{front:{portrait:E.front.portrait,name:E.front.namePlate},back:{name:E.back.namePlate,contactInfo:E.back.contactInfo,description:E.back.description}},languages:["de"]},De={id:"time-voucher-classic-en",name:"Time Voucher Classic",type:"time-voucher",category:"classic",images:{front:`${R}templates/front_ldpi_en.png`,back:`${R}templates/back_ldpi_en.png`,width:1536,height:1024},fields:[{id:"name",type:"text",label:{de:"Name",en:"Name"},required:!0,validation:{minLength:1,maxLength:50}},{id:"hours",type:"select",label:{de:"Stunden",en:"Hours"},required:!0,options:["1","5","10"]},{id:"portrait",type:"image",label:{de:"Portrait",en:"Portrait"},required:!1},{id:"email",type:"text",label:{de:"E-Mail",en:"Email"},required:!1},{id:"phone",type:"text",label:{de:"Telefon",en:"Phone"},required:!1},{id:"description",type:"textarea",label:{de:"Beschreibung",en:"Description"},required:!1,validation:{maxLength:200}}],layout:{front:{portrait:C.front.portrait,name:C.front.namePlate},back:{name:C.back.namePlate,contactInfo:C.back.contactInfo,description:C.back.description}},languages:["en"]},Q=[_e,De],ue={async listTemplates(e){let t=[...Q];return e!=null&&e.type&&(t=t.filter(a=>a.type===e.type)),e!=null&&e.category&&(t=t.filter(a=>a.category===e.category)),e!=null&&e.language&&(t=t.filter(a=>a.languages.includes(e.language))),t},async getTemplate(e){const t=Q.find(a=>a.id===e);if(!t)throw new Error(`Template not found: ${e}`);return t}};function Re(e){return e==="de"?"time-voucher-classic-de":"time-voucher-classic-en"}let B=ue;function Be(e){B=e}function $e(){return B}async function Ue(e){return B.listTemplates(e)}async function He(e){return B.getTemplate(e)}exports.ApiKeyModal=ne;exports.BillPreview=Se;exports.ExportButton=Le;exports.Header=Ae;exports.LAYOUT_HDPI=E;exports.LAYOUT_LDPI=C;exports.LanguageToggle=de;exports.PersonalInfoForm=we;exports.PortraitUpload=Ne;exports.TEMPLATES=ae;exports.VoucherConfig=Ee;exports.downloadBlob=le;exports.drawContactInfo=se;exports.drawMultilineText=oe;exports.drawOvalPortrait=re;exports.drawTemplate=Y;exports.drawText=V;exports.enhancePortrait=te;exports.enhancePortraitFallback=M;exports.exportBillAsPDF=ce;exports.formatDescription=K;exports.generateBillPDF=ie;exports.getApiKey=O;exports.getDefaultTemplateId=Re;exports.getLayout=W;exports.getTemplate=q;exports.getTemplateById=He;exports.getTemplateProvider=$e;exports.hasApiKey=z;exports.listTemplates=Ue;exports.loadImage=D;exports.renderBackSide=G;exports.renderFrontSide=Z;exports.setApiKey=ee;exports.setTemplateProvider=Be;exports.staticTemplateProvider=ue;exports.t=I;exports.useBillCanvasRefs=Te;exports.useBillStore=m;
@@ -0,0 +1,385 @@
1
+ import { JSX } from 'react/jsx-runtime';
2
+ import { PersistOptions } from 'zustand/middleware';
3
+ import { RefObject } from 'react';
4
+ import { StoreApi } from 'zustand';
5
+ import { UseBoundStore } from 'zustand';
6
+
7
+ export declare function ApiKeyModal({ isOpen, onClose, onSubmit }: ApiKeyModalProps): JSX.Element | null;
8
+
9
+ declare interface ApiKeyModalProps {
10
+ isOpen: boolean;
11
+ onClose: () => void;
12
+ onSubmit: (key: string) => void;
13
+ }
14
+
15
+ declare interface BillActions {
16
+ setPersonalInfo: (info: Partial<PersonalInfo>) => void;
17
+ setVoucherConfig: (config: Partial<VoucherConfigType>) => void;
18
+ setPortrait: (original: string | null, enhanced?: string | null) => void;
19
+ setEnhancedPortrait: (enhanced: string | null) => void;
20
+ toggleUseEnhanced: () => void;
21
+ setPortraitZoom: (zoom: number) => void;
22
+ setCurrentSide: (side: BillSide) => void;
23
+ flipSide: () => void;
24
+ setIsEnhancing: (value: boolean) => void;
25
+ setIsExporting: (value: boolean) => void;
26
+ setLanguage: (language: Language) => void;
27
+ setHours: (hours: HourValue) => void;
28
+ reset: () => void;
29
+ }
30
+
31
+ export declare function BillPreview(): JSX.Element;
32
+
33
+ export declare type BillSide = 'front' | 'back';
34
+
35
+ export declare interface BillState {
36
+ personalInfo: PersonalInfo;
37
+ voucherConfig: VoucherConfigType;
38
+ portrait: PortraitState;
39
+ currentSide: BillSide;
40
+ isEnhancing: boolean;
41
+ isExporting: boolean;
42
+ }
43
+
44
+ export declare interface CanvasPosition {
45
+ x: number;
46
+ y: number;
47
+ }
48
+
49
+ export declare function downloadBlob(blob: Blob, filename: string): void;
50
+
51
+ export declare function drawContactInfo(ctx: CanvasRenderingContext2D, name: string, email: string, phone: string, config: TextConfig, color?: string): void;
52
+
53
+ export declare function drawMultilineText(ctx: CanvasRenderingContext2D, text: string, config: TextConfig, color?: string): void;
54
+
55
+ export declare function drawOvalPortrait(ctx: CanvasRenderingContext2D, portrait: HTMLImageElement, centerX: number, centerY: number, radiusX: number, radiusY: number, zoom?: number): void;
56
+
57
+ export declare function drawTemplate(ctx: CanvasRenderingContext2D, template: HTMLImageElement, width: number, height: number): void;
58
+
59
+ export declare function drawText(ctx: CanvasRenderingContext2D, text: string, config: TextConfig, color?: string): void;
60
+
61
+ declare interface EnhanceOptions {
62
+ imageDataUrl: string;
63
+ style: EnhanceStyle;
64
+ strength?: number;
65
+ }
66
+
67
+ export declare function enhancePortrait(options: EnhanceOptions): Promise<string>;
68
+
69
+ export declare function enhancePortraitFallback(imageDataUrl: string): Promise<string>;
70
+
71
+ export declare type EnhanceStyle = 'vintage' | 'engraved' | 'currency';
72
+
73
+ export declare function exportBillAsPDF(options: PDFGeneratorOptions): Promise<void>;
74
+
75
+ export declare function ExportButton(): JSX.Element;
76
+
77
+ /**
78
+ * Position of a field on the template canvas
79
+ */
80
+ export declare interface FieldPosition {
81
+ x: number;
82
+ y: number;
83
+ fontSize?: number;
84
+ maxWidth?: number;
85
+ lineHeight?: number;
86
+ align?: CanvasTextAlign;
87
+ radiusX?: number;
88
+ radiusY?: number;
89
+ }
90
+
91
+ export declare function formatDescription(language: Language, hours: number, customDescription?: string): string;
92
+
93
+ export declare function generateBillPDF(options: PDFGeneratorOptions): Promise<Blob>;
94
+
95
+ export declare function getApiKey(): string | null;
96
+
97
+ /**
98
+ * Get the default template for a given language
99
+ */
100
+ export declare function getDefaultTemplateId(language: 'de' | 'en'): string;
101
+
102
+ export declare function getLayout(language: Language): {
103
+ front: TemplateLayout;
104
+ back: TemplateLayout;
105
+ };
106
+
107
+ export declare function getTemplate(language: Language, hours: HourValue): TemplateConfig;
108
+
109
+ /**
110
+ * Get a specific template by ID
111
+ */
112
+ export declare function getTemplateById(templateId: string): Promise<Template>;
113
+
114
+ /**
115
+ * Get the current template provider
116
+ */
117
+ export declare function getTemplateProvider(): TemplateProvider;
118
+
119
+ export declare function hasApiKey(): boolean;
120
+
121
+ export declare function Header(): JSX.Element;
122
+
123
+ export declare type HourValue = 1 | 5 | 10;
124
+
125
+ export declare type Language = 'de' | 'en';
126
+
127
+ export declare function LanguageToggle(): JSX.Element;
128
+
129
+ export declare const LAYOUT_HDPI: {
130
+ front: TemplateLayout;
131
+ back: TemplateLayout;
132
+ };
133
+
134
+ export declare const LAYOUT_LDPI: {
135
+ front: TemplateLayout;
136
+ back: TemplateLayout;
137
+ };
138
+
139
+ /**
140
+ * List available templates
141
+ */
142
+ export declare function listTemplates(filter?: {
143
+ type?: string;
144
+ category?: string;
145
+ language?: 'de' | 'en';
146
+ }): Promise<Template[]>;
147
+
148
+ export declare function loadImage(src: string): Promise<HTMLImageElement>;
149
+
150
+ declare interface PDFGeneratorOptions {
151
+ frontTemplateSrc: string;
152
+ backTemplateSrc: string;
153
+ templateWidth: number;
154
+ templateHeight: number;
155
+ layout: {
156
+ front: TemplateLayout;
157
+ back: TemplateLayout;
158
+ };
159
+ portrait: string | null;
160
+ portraitZoom?: number;
161
+ name: string;
162
+ email: string;
163
+ phone: string;
164
+ description: string;
165
+ filename: string;
166
+ }
167
+
168
+ export declare interface PersonalInfo {
169
+ name: string;
170
+ email: string;
171
+ phone: string;
172
+ }
173
+
174
+ export declare function PersonalInfoForm(): JSX.Element;
175
+
176
+ export declare interface PortraitConfig extends CanvasPosition {
177
+ radiusX: number;
178
+ radiusY: number;
179
+ }
180
+
181
+ export declare interface PortraitState {
182
+ original: string | null;
183
+ enhanced: string | null;
184
+ useEnhanced: boolean;
185
+ zoom: number;
186
+ }
187
+
188
+ export declare function PortraitUpload(): JSX.Element;
189
+
190
+ export declare function renderBackSide(canvas: HTMLCanvasElement, templateSrc: string, name: string, email: string, phone: string, description: string, layout: TemplateLayout, width: number, height: number): Promise<void>;
191
+
192
+ export declare function renderFrontSide(canvas: HTMLCanvasElement, templateSrc: string, portraitSrc: string | null, name: string, layout: TemplateLayout, width: number, height: number, portraitZoom?: number): Promise<void>;
193
+
194
+ export declare function setApiKey(key: string): void;
195
+
196
+ /**
197
+ * Set a custom template provider
198
+ * Use this to switch to a CMS-based provider or other source
199
+ *
200
+ * @example
201
+ * // Switch to a CMS provider
202
+ * import { createCmsProvider, setTemplateProvider } from 'money-printer/templates';
203
+ * setTemplateProvider(createCmsProvider('https://cms.example.com/api'));
204
+ */
205
+ export declare function setTemplateProvider(provider: TemplateProvider): void;
206
+
207
+ /**
208
+ * Static template provider that uses bundled templates
209
+ * This is the default provider for standalone app usage
210
+ */
211
+ export declare const staticTemplateProvider: TemplateProvider;
212
+
213
+ export declare function t(language: Language): Translations;
214
+
215
+ /**
216
+ * Complete template definition
217
+ * Templates define both the visual design and the data fields
218
+ */
219
+ export declare interface Template {
220
+ id: string;
221
+ name: string;
222
+ type: string;
223
+ category: string;
224
+ images: {
225
+ front: string;
226
+ back: string;
227
+ width: number;
228
+ height: number;
229
+ thumbnail?: string;
230
+ };
231
+ fields: TemplateField[];
232
+ layout: {
233
+ front: Record<string, FieldPosition>;
234
+ back: Record<string, FieldPosition>;
235
+ };
236
+ languages: Language[];
237
+ pricing?: {
238
+ basePrice: number;
239
+ bulkDiscounts?: boolean;
240
+ };
241
+ }
242
+
243
+ export declare interface TemplateConfig {
244
+ front: string;
245
+ back: string;
246
+ width: number;
247
+ height: number;
248
+ }
249
+
250
+ /**
251
+ * Definition of a field that can be filled in on a template
252
+ */
253
+ export declare interface TemplateField {
254
+ id: string;
255
+ type: 'text' | 'number' | 'image' | 'select' | 'textarea';
256
+ label: Partial<Record<Language, string>>;
257
+ required: boolean;
258
+ options?: string[];
259
+ validation?: {
260
+ minLength?: number;
261
+ maxLength?: number;
262
+ pattern?: string;
263
+ };
264
+ }
265
+
266
+ export declare interface TemplateLayout {
267
+ portrait: PortraitConfig;
268
+ namePlate: TextConfig;
269
+ contactInfo?: TextConfig;
270
+ description?: TextConfig;
271
+ }
272
+
273
+ declare type TemplateMap = Record<Language, Record<HourValue, TemplateConfig>>;
274
+
275
+ /**
276
+ * Provider interface for template sources
277
+ * Implementations can load from static files, CMS, or other sources
278
+ */
279
+ export declare interface TemplateProvider {
280
+ /**
281
+ * List all available templates, optionally filtered
282
+ */
283
+ listTemplates(filter?: {
284
+ type?: string;
285
+ category?: string;
286
+ language?: Language;
287
+ }): Promise<Template[]>;
288
+ /**
289
+ * Get a specific template by ID
290
+ */
291
+ getTemplate(templateId: string): Promise<Template>;
292
+ }
293
+
294
+ export declare const TEMPLATES: TemplateMap;
295
+
296
+ export declare interface TextConfig extends CanvasPosition {
297
+ fontSize: number;
298
+ maxWidth?: number;
299
+ lineHeight?: number;
300
+ align?: CanvasTextAlign;
301
+ }
302
+
303
+ declare interface Translations {
304
+ header: {
305
+ title: string;
306
+ subtitle: string;
307
+ };
308
+ form: {
309
+ personalInfo: {
310
+ title: string;
311
+ name: string;
312
+ namePlaceholder: string;
313
+ email: string;
314
+ emailPlaceholder: string;
315
+ phone: string;
316
+ phonePlaceholder: string;
317
+ };
318
+ portrait: {
319
+ title: string;
320
+ upload: string;
321
+ dragDrop: string;
322
+ enhance: string;
323
+ enhancing: string;
324
+ useOriginal: string;
325
+ useEnhanced: string;
326
+ zoom: string;
327
+ };
328
+ voucher: {
329
+ title: string;
330
+ hours: string;
331
+ hourLabel: string;
332
+ hoursLabel: string;
333
+ description: string;
334
+ descriptionPlaceholder: string;
335
+ };
336
+ };
337
+ preview: {
338
+ front: string;
339
+ back: string;
340
+ flip: string;
341
+ };
342
+ export: {
343
+ button: string;
344
+ exporting: string;
345
+ success: string;
346
+ };
347
+ bill: {
348
+ descriptionText: string;
349
+ };
350
+ }
351
+
352
+ export declare function useBillCanvasRefs(): {
353
+ frontCanvasRef: RefObject<HTMLCanvasElement | null>;
354
+ backCanvasRef: RefObject<HTMLCanvasElement | null>;
355
+ };
356
+
357
+ export declare const useBillStore: UseBoundStore<Omit<StoreApi<BillState & BillActions>, "setState" | "persist"> & {
358
+ setState(partial: (BillState & BillActions) | Partial<BillState & BillActions> | ((state: BillState & BillActions) => (BillState & BillActions) | Partial<BillState & BillActions>), replace?: false | undefined): unknown;
359
+ setState(state: (BillState & BillActions) | ((state: BillState & BillActions) => BillState & BillActions), replace: true): unknown;
360
+ persist: {
361
+ setOptions: (options: Partial<PersistOptions<BillState & BillActions, {
362
+ personalInfo: PersonalInfo;
363
+ voucherConfig: VoucherConfigType;
364
+ }, unknown>>) => void;
365
+ clearStorage: () => void;
366
+ rehydrate: () => Promise<void> | void;
367
+ hasHydrated: () => boolean;
368
+ onHydrate: (fn: (state: BillState & BillActions) => void) => () => void;
369
+ onFinishHydration: (fn: (state: BillState & BillActions) => void) => () => void;
370
+ getOptions: () => Partial<PersistOptions<BillState & BillActions, {
371
+ personalInfo: PersonalInfo;
372
+ voucherConfig: VoucherConfigType;
373
+ }, unknown>>;
374
+ };
375
+ }>;
376
+
377
+ export declare function VoucherConfig(): JSX.Element;
378
+
379
+ export declare interface VoucherConfigType {
380
+ hours: HourValue;
381
+ description: string;
382
+ language: Language;
383
+ }
384
+
385
+ export { }