diff --git a/locales/en.yml b/locales/en.yml index d03d80d3..55216d14 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -257,6 +257,10 @@ tools: title: WiFi QR Code generator 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: title: XML formatter description: Prettify your XML string into a friendly, human-readable format. diff --git a/src/tools/index.ts b/src/tools/index.ts index c4be5a1e..5887456e 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -142,7 +142,7 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Images and videos', - components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder], + components: [qrCodeGenerator, wifiQrCodeGenerator, qrContactInfoGenerator, svgPlaceholderGenerator, cameraRecorder], }, { name: 'Development', diff --git a/src/tools/qr-contact-info-generator/index.ts b/src/tools/qr-contact-info-generator/index.ts index ce087fad..9e9dccc7 100644 --- a/src/tools/qr-contact-info-generator/index.ts +++ b/src/tools/qr-contact-info-generator/index.ts @@ -1,13 +1,13 @@ -import { Qrcode } from '@vicons/tabler'; -import { defineTool } from '../tool'; -import { translate } from '@/plugins/i18n.plugin'; +import { Qrcode } from "@vicons/tabler"; +import { defineTool } from "../tool"; +import { translate } from "@/plugins/i18n.plugin"; export const tool = defineTool({ - name: translate('tools.qr-contact-info-generator.title'), - path: '/qr-contact-info-generator', - description: translate('tools.qr-contact-info-generator.description'), - keywords: ['qr', 'contact', 'info', 'generator'], - component: () => import('./qr-contact-info-generator.vue'), + name: translate("tools.qr-contact-info-generator.title"), + path: "/qr-contact-info-generator", + description: translate("tools.qr-contact-info-generator.description"), + keywords: ["qr", "contact", "vcard", "generator", "business", "networking"], + component: () => import("./qr-contact-info-generator.vue"), icon: Qrcode, - createdAt: new Date('2025-02-17'), -}); \ No newline at end of file + createdAt: new Date(), +}); diff --git a/src/tools/qr-contact-info-generator/qr-contact-info-generator.ts b/src/tools/qr-contact-info-generator/qr-contact-info-generator.ts deleted file mode 100644 index 9ce894e4..00000000 --- a/src/tools/qr-contact-info-generator/qr-contact-info-generator.ts +++ /dev/null @@ -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, options: MaybeRef = {}): Promise { - 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; -} \ No newline at end of file diff --git a/src/tools/qr-contact-info-generator/qr-contact-info-generator.vue b/src/tools/qr-contact-info-generator/qr-contact-info-generator.vue index b61dfe61..23143a9f 100644 --- a/src/tools/qr-contact-info-generator/qr-contact-info-generator.vue +++ b/src/tools/qr-contact-info-generator/qr-contact-info-generator.vue @@ -1,77 +1,61 @@ - + - \ No newline at end of file +
+
+ contact-info-qrcode + Download QR Code +
+
+ + + diff --git a/src/tools/qr-contact-info-generator/useContactQRCode.ts b/src/tools/qr-contact-info-generator/useContactQRCode.ts new file mode 100644 index 00000000..0d4cbfda --- /dev/null +++ b/src/tools/qr-contact-info-generator/useContactQRCode.ts @@ -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; + jobRole: MaybeRef; + phoneNumber: MaybeRef; + email: MaybeRef; + website: MaybeRef; + address: MaybeRef; + color: { foreground: MaybeRef; background: MaybeRef }; + 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 }; +}