added Contact Info QR Code generator
This commit is contained in:
		
							parent
							
								
									a6fc0b5448
								
							
						
					
					
						commit
						2a5fa4bab1
					
				| @ -257,6 +257,10 @@ tools: | |||||||
|     title: WiFi QR Code generator |     title: WiFi QR Code generator | ||||||
|     description: Generate and download QR codes for quick connections to WiFi networks. |     description: Generate and download QR codes for quick connections to WiFi networks. | ||||||
| 
 | 
 | ||||||
|  |   qr-contact-info-generator: | ||||||
|  |     title: QR Contact Info generator | ||||||
|  |     description: Generate and download QR codes for Contact information (vCard), and customize the background and foreground colors. | ||||||
|  | 
 | ||||||
|   xml-formatter: |   xml-formatter: | ||||||
|     title: XML formatter |     title: XML formatter | ||||||
|     description: Prettify your XML string into a friendly, human-readable format. |     description: Prettify your XML string into a friendly, human-readable format. | ||||||
|  | |||||||
| @ -142,7 +142,7 @@ export const toolsByCategory: ToolCategory[] = [ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Images and videos', |     name: 'Images and videos', | ||||||
|     components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder], |     components: [qrCodeGenerator, wifiQrCodeGenerator, qrContactInfoGenerator, svgPlaceholderGenerator, cameraRecorder], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Development', |     name: 'Development', | ||||||
|  | |||||||
| @ -1,13 +1,13 @@ | |||||||
| import { Qrcode } from '@vicons/tabler'; | import { Qrcode } from "@vicons/tabler"; | ||||||
| import { defineTool } from '../tool'; | import { defineTool } from "../tool"; | ||||||
| import { translate } from '@/plugins/i18n.plugin'; | import { translate } from "@/plugins/i18n.plugin"; | ||||||
| 
 | 
 | ||||||
| export const tool = defineTool({ | export const tool = defineTool({ | ||||||
|   name: translate('tools.qr-contact-info-generator.title'), |   name: translate("tools.qr-contact-info-generator.title"), | ||||||
|   path: '/qr-contact-info-generator', |   path: "/qr-contact-info-generator", | ||||||
|   description: translate('tools.qr-contact-info-generator.description'), |   description: translate("tools.qr-contact-info-generator.description"), | ||||||
|   keywords: ['qr', 'contact', 'info', 'generator'], |   keywords: ["qr", "contact", "vcard", "generator", "business", "networking"], | ||||||
|   component: () => import('./qr-contact-info-generator.vue'), |   component: () => import("./qr-contact-info-generator.vue"), | ||||||
|   icon: Qrcode, |   icon: Qrcode, | ||||||
|   createdAt: new Date('2025-02-17'), |   createdAt: new Date(), | ||||||
| }); | }); | ||||||
| @ -1,56 +0,0 @@ | |||||||
| import { type MaybeRef, get } from '@vueuse/core'; |  | ||||||
| import QRCode, { type QRCodeErrorCorrectionLevel, type QRCodeToDataURLOptions } from 'qrcode'; |  | ||||||
| import { isRef, ref, watch } from 'vue'; |  | ||||||
| 
 |  | ||||||
| interface ContactInfo { |  | ||||||
|   firstName: string; |  | ||||||
|   lastName: string; |  | ||||||
|   jobRole: string; |  | ||||||
|   phoneNumber: string; |  | ||||||
|   emailAddress: string; |  | ||||||
|   companyName: string; |  | ||||||
|   companyWebsite: string; |  | ||||||
|   companyAddress: string; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| interface QRCodeOptions { |  | ||||||
|   foregroundColor?: string; |  | ||||||
|   backgroundColor?: string; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function generateContactQRCode(contact: MaybeRef<ContactInfo>, options: MaybeRef<QRCodeOptions> = {}): Promise<string> { |  | ||||||
|   const vCard = ref(` |  | ||||||
| BEGIN:VCARD |  | ||||||
| VERSION:3.0 |  | ||||||
| FN:${get(contact).firstName} ${get(contact).lastName} |  | ||||||
| ORG:${get(contact).companyName} |  | ||||||
| TITLE:${get(contact).jobRole} |  | ||||||
| TEL:${get(contact).phoneNumber} |  | ||||||
| EMAIL:${get(contact).emailAddress} |  | ||||||
| URL:${get(contact).companyWebsite} |  | ||||||
| ADR:${get(contact).companyAddress} |  | ||||||
| END:VCARD |  | ||||||
|   `);
 |  | ||||||
| 
 |  | ||||||
|   const qrOptions: QRCodeToDataURLOptions = { |  | ||||||
|     errorCorrectionLevel: 'H' as QRCodeErrorCorrectionLevel, |  | ||||||
|     type: 'image/png', |  | ||||||
|     margin: 1, |  | ||||||
|     color: { |  | ||||||
|       dark: get(options).foregroundColor || '#000000ff', |  | ||||||
|       light: get(options).backgroundColor || '#ffffffff', |  | ||||||
|     }, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   const qrCodeDataUrl = ref(''); |  | ||||||
| 
 |  | ||||||
|   watch( |  | ||||||
|     [vCard, options].filter(isRef), |  | ||||||
|     async () => { |  | ||||||
|       qrCodeDataUrl.value = await QRCode.toDataURL(get(vCard), qrOptions); |  | ||||||
|     }, |  | ||||||
|     { immediate: true }, |  | ||||||
|   ); |  | ||||||
| 
 |  | ||||||
|   return qrCodeDataUrl.value; |  | ||||||
| } |  | ||||||
| @ -1,77 +1,61 @@ | |||||||
| <template> | <script setup lang="ts"> | ||||||
|   <div> | import { useContactQRCode } from "./useContactQRCode"; | ||||||
|     <h1>Contact QR Code Generator</h1> | import { useDownloadFileFromBase64 } from "@/composable/downloadBase64"; | ||||||
|     <form @submit.prevent="generateQRCode"> |  | ||||||
|       <input v-model="contact.firstName" placeholder="First Name" required /> |  | ||||||
|       <input v-model="contact.lastName" placeholder="Last Name" required /> |  | ||||||
|       <input v-model="contact.jobRole" placeholder="Job Role" required /> |  | ||||||
|       <input v-model="contact.phoneNumber" placeholder="Phone Number" required /> |  | ||||||
|       <input v-model="contact.emailAddress" placeholder="Email Address" required /> |  | ||||||
|       <input v-model="contact.companyName" placeholder="Company Name" required /> |  | ||||||
|       <input v-model="contact.companyWebsite" placeholder="Company Website" required /> |  | ||||||
|       <input v-model="contact.companyAddress" placeholder="Company Address" required /> |  | ||||||
|       <input v-model="foregroundColor" placeholder="Foreground Color" /> |  | ||||||
|       <input v-model="backgroundColor" placeholder="Background Color" /> |  | ||||||
|       <button type="submit">Generate QR Code</button> |  | ||||||
|     </form> |  | ||||||
|     <div v-if="qrCodeDataUrl"> |  | ||||||
|       <img :src="qrCodeDataUrl" alt="Contact QR Code" /> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| </template> |  | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | const fullName = ref(""); | ||||||
| import { defineComponent, ref } from 'vue'; | const jobRole = ref(""); | ||||||
| import { generateContactQRCode } from './qr-contact-info-generator'; | const phoneNumber = ref(""); | ||||||
|  | const email = ref(""); | ||||||
|  | const website = ref(""); | ||||||
|  | const address = ref(""); | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | const foreground = ref("#000000ff"); | ||||||
|   name: 'ContactQRCodeGenerator', | const background = ref("#ffffffff"); | ||||||
|   setup() { | 
 | ||||||
|     const contact = ref({ | const { qrcode } = useContactQRCode({ | ||||||
|       firstName: '', |   fullName, | ||||||
|       lastName: '', |   jobRole, | ||||||
|       jobRole: '', |   phoneNumber, | ||||||
|       phoneNumber: '', |   email, | ||||||
|       emailAddress: '', |   website, | ||||||
|       companyName: '', |   address, | ||||||
|       companyWebsite: '', |   color: { foreground, background }, | ||||||
|       companyAddress: '' |   options: { width: 1024 }, | ||||||
| }); | }); | ||||||
|     const foregroundColor = ref('#000000ff'); |  | ||||||
|     const backgroundColor = ref('#ffffffff'); |  | ||||||
|     const qrCodeDataUrl = ref<string | null>(null); |  | ||||||
| 
 | 
 | ||||||
|     const generateQRCode = async () => { | const { download } = useDownloadFileFromBase64({ | ||||||
|       qrCodeDataUrl.value = await generateContactQRCode(contact.value, { |   source: qrcode, | ||||||
|         foregroundColor: foregroundColor.value, |   filename: "contact-info-qr.png", | ||||||
|         backgroundColor: backgroundColor.value |  | ||||||
|       }); |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     return { |  | ||||||
|       contact, |  | ||||||
|       foregroundColor, |  | ||||||
|       backgroundColor, |  | ||||||
|       qrCodeDataUrl, |  | ||||||
|       generateQRCode |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <template> | ||||||
| form { |   <c-card> | ||||||
|   display: flex; |     <div grid grid-cols-1 gap-12> | ||||||
|   flex-direction: column; |       <div> | ||||||
|   gap: 10px; |         <c-input-text v-model:value="fullName" label="Full Name" placeholder="John Doe" mb-4 /> | ||||||
| } |         <c-input-text v-model:value="jobRole" label="Job Role" placeholder="Software Engineer" mb-4 /> | ||||||
|  |         <c-input-text v-model:value="phoneNumber" label="Phone Number" placeholder="+1 234 567 8901" mb-4 /> | ||||||
|  |         <c-input-text v-model:value="email" label="Email Address" placeholder="john.doe@example.com" mb-4 /> | ||||||
|  |         <c-input-text v-model:value="website" label="Website" placeholder="https://acme.com" mb-4 /> | ||||||
|  |         <c-input-text v-model:value="address" label="Company Address" placeholder="123 Main St, City" mb-4 /> | ||||||
| 
 | 
 | ||||||
| button { |         <n-form label-width="130" label-placement="left"> | ||||||
|   margin-top: 10px; |           <n-form-item label="Foreground color:"> | ||||||
| } |             <n-color-picker v-model:value="foreground" :modes="['hex']" /> | ||||||
|  |           </n-form-item> | ||||||
|  |           <n-form-item label="Background color:"> | ||||||
|  |             <n-color-picker v-model:value="background" :modes="['hex']" /> | ||||||
|  |           </n-form-item> | ||||||
|  |         </n-form> | ||||||
|  |       </div> | ||||||
| 
 | 
 | ||||||
| img { |       <div v-if="qrcode"> | ||||||
|   margin-top: 20px; |         <div flex flex-col items-center gap-3> | ||||||
|   max-width: 100%; |           <img alt="contact-info-qrcode" :src="qrcode" width="200" /> | ||||||
| } |           <c-button @click="download">Download QR Code</c-button> | ||||||
| </style> |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </c-card> | ||||||
|  | </template> | ||||||
|  | |||||||
							
								
								
									
										49
									
								
								src/tools/qr-contact-info-generator/useContactQRCode.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/tools/qr-contact-info-generator/useContactQRCode.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | import { computed, ref, watchEffect } from "vue"; | ||||||
|  | import QRCode, { type QRCodeToDataURLOptions } from "qrcode"; | ||||||
|  | import { get, MaybeRef } from "@vueuse/core"; | ||||||
|  | 
 | ||||||
|  | interface IContactQRCodeOptions { | ||||||
|  |   fullName: MaybeRef<string>; | ||||||
|  |   jobRole: MaybeRef<string>; | ||||||
|  |   phoneNumber: MaybeRef<string>; | ||||||
|  |   email: MaybeRef<string>; | ||||||
|  |   website: MaybeRef<string>; | ||||||
|  |   address: MaybeRef<string>; | ||||||
|  |   color: { foreground: MaybeRef<string>; background: MaybeRef<string> }; | ||||||
|  |   options?: QRCodeToDataURLOptions; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Computed vCard to ensure reactivity works correctly
 | ||||||
|  | const useVCard = (options: IContactQRCodeOptions) => { | ||||||
|  |   return computed(() => { | ||||||
|  |     return `BEGIN:VCARD
 | ||||||
|  | VERSION:3.0 | ||||||
|  | ${get(options.fullName) ? `FN:${get(options.fullName)}` : ""} | ||||||
|  | ${get(options.jobRole) ? `TITLE:${get(options.jobRole)}` : ""} | ||||||
|  | ${get(options.phoneNumber) ? `TEL:${get(options.phoneNumber)}` : ""} | ||||||
|  | ${get(options.email) ? `EMAIL:${get(options.email)}` : ""} | ||||||
|  | ${get(options.website) ? `URL:${get(options.website)}` : ""} | ||||||
|  | ${get(options.address) ? `ADR:${get(options.address)}` : ""} | ||||||
|  | END:VCARD`.trim();
 | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function useContactQRCode(options: IContactQRCodeOptions) { | ||||||
|  |   const qrcode = ref(""); | ||||||
|  |   const vCardText = useVCard(options); // Computed property for reactivity
 | ||||||
|  | 
 | ||||||
|  |   watchEffect(async () => { | ||||||
|  |     if (vCardText.value) { | ||||||
|  |       qrcode.value = await QRCode.toDataURL(vCardText.value, { | ||||||
|  |         color: { | ||||||
|  |           dark: get(options.color.foreground), | ||||||
|  |           light: get(options.color.background), | ||||||
|  |         }, | ||||||
|  |         errorCorrectionLevel: "M", | ||||||
|  |         ...options.options, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return { qrcode }; | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user