From d396cbb2219da0fc0c0ad21b87d51debe8fc4c15 Mon Sep 17 00:00:00 2001 From: AyrisAI Date: Thu, 20 Aug 2026 18:16:49 +0300 Subject: [PATCH] feat: implement Tabletop QR Stand Generator with high-res PNG and print-ready A5 PDF export --- apps/api/tsconfig.json | 5 +- apps/mobile/src/app/menu/qr.tsx | 35 ++ apps/web/package.json | 3 + apps/web/src/app/qr/[slug]/page.tsx | 71 ++++ apps/web/src/app/qr/page.tsx | 19 ++ .../src/components/QrStandGeneratorClient.tsx | 302 +++++++++++++++++ docs/qr-example.md | 308 ++++++++++++++++++ packages/shared/package.json | 4 + packages/shared/src/index.ts | 7 +- packages/shared/src/qr-stands.ts | 105 ++++++ pnpm-lock.yaml | 184 +++++++++++ pnpm-workspace.yaml | 1 + 12 files changed, 1039 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/app/qr/[slug]/page.tsx create mode 100644 apps/web/src/app/qr/page.tsx create mode 100644 apps/web/src/components/QrStandGeneratorClient.tsx create mode 100644 docs/qr-example.md create mode 100644 packages/shared/src/qr-stands.ts diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index 0ff12c7..c360d45 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -3,8 +3,9 @@ "compilerOptions": { "outDir": "dist", "rootDir": "src", - "module": "NodeNext", - "moduleResolution": "NodeNext" + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true }, "include": ["src"] } diff --git a/apps/mobile/src/app/menu/qr.tsx b/apps/mobile/src/app/menu/qr.tsx index 3ac1ba3..4163ee4 100644 --- a/apps/mobile/src/app/menu/qr.tsx +++ b/apps/mobile/src/app/menu/qr.tsx @@ -2,7 +2,9 @@ import { useEffect, useState } from "react"; import { router } from "expo-router"; import { ActivityIndicator, + Alert, Image, + Linking, Pressable, ScrollView, Share, @@ -87,6 +89,14 @@ export default function QrScreen() { ); } + function handleOpenStandGenerator() { + if (!active?.slug) return; + const standUrl = `https://menul.io/qr/${active.slug}`; + Linking.openURL(standUrl).catch(() => { + Alert.alert("Bilgi", `Stant Jeneratörü adresi: ${standUrl}`); + }); + } + return ( @@ -158,6 +168,31 @@ export default function QrScreen() { {/* Action Buttons */} + {/* Stand Generator PDF / PNG Button */} + ({ + backgroundColor: "#C8A96B", + borderRadius: 14, + padding: 16, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 10, + opacity: pressed ? 0.9 : 1, + shadowColor: "#C8A96B", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.2, + shadowRadius: 10, + elevation: 4, + })} + > + + + Masa Stant Tasarımları & İndir (PDF / PNG) + + + {/* Share Button */} ; +}; + +export async function generateMetadata({ params }: PageProps): Promise { + const { slug } = await params; + return { + title: `${slug} | Masa Stant QR Kod Jeneratörü | Menulio`, + description: "Restoranınız için yüksek kalitede baskıya hazır A5 masa kartı ve QR stant tasarımları oluşturun.", + }; +} + +export default async function RestaurantQrPage({ params }: PageProps) { + const { slug } = await params; + + if (slug === "demo" || slug === "gusto-brasserie") { + return ( + c.name)} + /> + ); + } + + const { data: restaurant } = await supabase + .from("restaurants") + .select("id, name, slug, logo_url") + .eq("slug", slug) + .maybeSingle(); + + if (!restaurant) { + notFound(); + } + + const { data: menu } = await supabase + .from("menus") + .select("id, locations!inner(restaurant_id)") + .eq("locations.restaurant_id", restaurant.id) + .maybeSingle(); + + let categoryNames: string[] = []; + if (menu) { + const { data: categories } = await supabase + .from("menu_categories") + .select("name") + .eq("menu_id", menu.id) + .eq("is_active", true) + .order("sort_order", { ascending: true }); + + if (categories) { + categoryNames = categories.map((c) => c.name); + } + } + + return ( + + ); +} diff --git a/apps/web/src/app/qr/page.tsx b/apps/web/src/app/qr/page.tsx new file mode 100644 index 0000000..57da493 --- /dev/null +++ b/apps/web/src/app/qr/page.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; +import { QrStandGeneratorClient } from "@/components/QrStandGeneratorClient"; +import { DEMO_RESTAURANT, DEMO_CATEGORIES } from "@/lib/demo-data"; + +export const metadata: Metadata = { + title: "Masa Stant QR Kod Tasarım Jeneratörü | Menulio", + description: "Restoranınız için 5 farklı lüks tema seçeneğiyle yüksek çözünürlüklü PNG ve A5 PDF masa kartı stant çıktıları alın.", +}; + +export default function QrStandPage() { + return ( + c.name)} + /> + ); +} diff --git a/apps/web/src/components/QrStandGeneratorClient.tsx b/apps/web/src/components/QrStandGeneratorClient.tsx new file mode 100644 index 0000000..651c50a --- /dev/null +++ b/apps/web/src/components/QrStandGeneratorClient.tsx @@ -0,0 +1,302 @@ +"use client"; + +import { useRef, useState } from "react"; +import Link from "next/link"; +import { QRCodeCanvas } from "qrcode.react"; +import { toPng } from "html-to-image"; +import { jsPDF } from "jspdf"; +import { QR_STAND_PRESETS, type QrStandPreset } from "@menulio/shared"; + +interface QrStandGeneratorClientProps { + restaurantName?: string; + logoUrl?: string | null; + slug?: string; + categoriesList?: string[]; +} + +export function QrStandGeneratorClient({ + restaurantName = "THE URBAN BURGER", + logoUrl = null, + slug = "kebapci-sinan-3", + categoriesList = [], +}: QrStandGeneratorClientProps) { + const [selectedKey, setSelectedKey] = useState("urban"); + const [downloadingPng, setDownloadingPng] = useState(false); + const [downloadingPdf, setDownloadingPdf] = useState(false); + const standRef = useRef(null); + + const activePreset: QrStandPreset = (QR_STAND_PRESETS[selectedKey] || QR_STAND_PRESETS.urban)!; + const targetUrl = `https://menul.io/${slug}`; + + const displayCategories = + categoriesList.length > 0 ? categoriesList.slice(0, 4) : activePreset.categories; + + async function handleDownloadPng() { + if (!standRef.current) return; + setDownloadingPng(true); + try { + const dataUrl = await toPng(standRef.current, { + cacheBust: true, + pixelRatio: 3, // 300 DPI high resolution + }); + const link = document.createElement("a"); + link.download = `${slug}-masa-stant-qr.png`; + link.href = dataUrl; + link.click(); + } catch (err) { + console.error("PNG indirme hatası:", err); + alert("PNG indirilemedi. Lütfen tekrar deneyin."); + } finally { + setDownloadingPng(false); + } + } + + async function handleDownloadPdf() { + if (!standRef.current) return; + setDownloadingPdf(true); + try { + const dataUrl = await toPng(standRef.current, { + cacheBust: true, + pixelRatio: 3, + }); + + // A5 dimensions in mm: 148 x 210 + const pdf = new jsPDF({ + orientation: "portrait", + unit: "mm", + format: "a5", + }); + + const pdfWidth = pdf.internal.pageSize.getWidth(); + const pdfHeight = pdf.internal.pageSize.getHeight(); + + pdf.addImage(dataUrl, "PNG", 0, 0, pdfWidth, pdfHeight); + pdf.save(`${slug}-masa-stant-a5.pdf`); + } catch (err) { + console.error("PDF indirme hatası:", err); + alert("PDF indirilemedi. Lütfen tekrar deneyin."); + } finally { + setDownloadingPdf(false); + } + } + + return ( +
+ {/* Top Navbar */} +
+
+
+ + MENULIO + + / + + Masa Stant QR Jeneratörü + +
+ + + Ana Sayfa → + +
+
+ + {/* Main Content Area */} +
+ {/* Left Panel: Preset Selectors & Download Controls */} +
+
+
+ + 1. Stant Teması Seçin + +

+ Masa Kartı Şablonları +

+

+ Restoranınızın konseptine uygun masa stant tasarımını seçin. +

+
+ + {/* Presets List */} +
+ {Object.values(QR_STAND_PRESETS).map((preset) => { + const isSelected = preset.key === selectedKey; + return ( + + ); + })} +
+
+ + {/* Action Download Buttons */} +
+ + 2. Baskı Çıktısı Alın + + + {/* PNG Download Button */} + + + {/* PDF Download Button */} + +
+
+ + {/* Right Panel: Stand Live Visual Canvas Preview */} +
+
+ {/* Tabletop Plaque Container */} +
+ {/* Inner Line Accent */} +
+ + {/* 01. Brand Badge */} +
+
+ {logoUrl ? ( + {restaurantName} + ) : null} + + {restaurantName} + +
+ + {/* Scan Action Text */} +

+ DİJİTAL MENÜ
+ KAMERANIZLA TARATIN +

+
+ + {/* 02. Centered Custom QR Code Box */} +
+ +
+ + {/* 03. Category Highlights Footer */} +
+

+ Telefonunuzun kamerasını doğrultun +

+ + {/* Category Text Stack */} +
+ {displayCategories.map((cat, idx) => ( +
+
{cat}
+ {idx < displayCategories.length - 1 && ( +
+ )} +
+ ))} +
+
+
+
+
+
+
+ ); +} diff --git a/docs/qr-example.md b/docs/qr-example.md new file mode 100644 index 0000000..d56af76 --- /dev/null +++ b/docs/qr-example.md @@ -0,0 +1,308 @@ +# The Urban Burger - Tabletop QR Menu Stand Design System + +## Color Palette +- **Forest Green Background**: `#1c3e2b` / `#163323` (ambient tabletop / environment backdrop) +- **Rust / Terracotta Leather Plaque**: `#a64d2a` / `#b35431` (primary card & stand body) +- **Cream / Warm Alabaster QR Inset**: `#f8f3eb` (QR code container background) +- **Rust QR Pattern**: `#a64d2a` / `#944222` (custom branded QR pixel matrix) +- **Warm Golden Wheat**: `#f5be72` / `#e9b366` (logo outline, flame accents, hand-drawn icons) +- **Pure White / Cream UI Text**: `#ffffff` / `#fef8f0` (display headers, scan instructions, category list) +- **Subtle Divider / Border Lines**: `#c96b46` / `rgba(255, 255, 255, 0.2)` (category separation rules) + +## Typography +- **Display / Headers**: `Montserrat`, `Plus Jakarta Sans`, or `Oswald` (weights 800-900, uppercase, clean geometric sans-serif with tight line-height) +- **Logo Typography**: Stylized bold outline font with flame icon ligature (or stylized custom geometric sans) +- **Instructional & Body**: `Inter`, `Plus Jakarta Sans`, or `DM Sans` (weights 500-700, generous tracking `0.1em` to `0.2em`) + +Font imports: +```css +@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@700;800;900&family=Plus+Jakarta+Sans:wght@500;600;700;800&display=swap'); +``` + +## Spacing & Layout +- **Stand Plaque Dimensions**: `360px` – `400px` width, `~700px` height (aspect ratio ~1:1.8 portrait tabletop stand) +- **Border Radius**: `28px` (`rounded-[2rem]`) on outer card with physical drop shadow +- **QR Plate Corner Radius**: `24px` (`rounded-3xl`) with inset shadow +- **Hierarchy Order**: + 1. Brand Logo (Top) + 2. Action Callout (`DIGITAL MENU / SCAN TO ORDER`) + 3. Centered Large QR Code Box with custom rounded finder patterns + 4. Camera Prompt (`Use your phone's camera`) + 5. Category Quick-View List (Burgers, Fries, Shakes, Vegan Options) flanked by illustrative line icons + +--- + +## Reusable Components + +### 1. Urban Burger Brand Badge & Header +```html +
+ +
+
+ THE URBAN + + + + +
+ + BURGER + +
+ + +

+ DIGITAL MENU
+ SCAN TO ORDER +

+
+``` + +### 2. Custom Branded QR Code Box +```html +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+``` + +### 3. Menu Quick-Categories Section with Floating Icons +```html +
+

+ Use your phone's camera +

+ + +
+ + + + + +
+ + +
+ + + + + + +
+ + +
+
BURGERS
+
+
FRIES
+
+
SHAKES
+
+
VEGAN OPTIONS
+
+
+``` + +--- + +## Full Responsive Standalone HTML / CSS Implementation + +```html + + + + + + The Urban Burger - Tabletop QR Menu + + + + + + + + + + + + + + +
+ + +
+ + +
+
+
+ THE URBAN + + + + +
+ + BURGER + +
+ + +

+ DIGITAL MENU
+ SCAN TO ORDER +

+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+

+ Use your phone's camera +

+ + +
+ + + + + +
+ + +
+ + + + + + +
+ + +
+
BURGERS
+
+
FRIES
+
+
SHAKES
+
+
VEGAN OPTIONS
+
+
+ +
+ + +``` + +## Design Philosophy +- **Tactile Leather Plaque Aesthetic**: Earthy rust-terracotta background paired with an ambient forest green setting to replicate physical wooden/leather restaurant table stands. +- **High-Scanability Contrast**: Clean high-contrast cream QR card embedded with rust custom rounded pixels for instant mobile scanning. +- **Bold Urban Typography**: Punchy condensed uppercase headings paired with clean line-art food icons for quick menu category orientation. \ No newline at end of file diff --git a/packages/shared/package.json b/packages/shared/package.json index 787523d..ca7e731 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -4,7 +4,11 @@ "version": "0.0.1", "type": "module", "main": "./src/index.ts", + "module": "./src/index.ts", "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, "scripts": { "typecheck": "tsc --noEmit" }, diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 69439e7..566c53f 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,3 +1,4 @@ -export * from "./slug.js"; -export * from "./theme.js"; -export * from "./types.js"; +export * from "./slug"; +export * from "./theme"; +export * from "./types"; +export * from "./qr-stands"; diff --git a/packages/shared/src/qr-stands.ts b/packages/shared/src/qr-stands.ts new file mode 100644 index 0000000..1954f1a --- /dev/null +++ b/packages/shared/src/qr-stands.ts @@ -0,0 +1,105 @@ +export interface QrStandPreset { + key: string; + name: string; + categoryName: string; + bgGradient: string; + plaqueBg: string; + borderAccent: string; + qrBg: string; + qrColor: string; + logoBorder: string; + logoBg: string; + accentText: string; + headerText: string; + subText: string; + dividerColor: string; + categories: string[]; +} + +export const QR_STAND_PRESETS: Record = { + urban: { + key: "urban", + name: "Urban Terracotta", + categoryName: "Burger & Fast Casual", + bgGradient: "radial-gradient(circle at 50% 50%, #1a3a29 0%, #0e2016 100%)", + plaqueBg: "linear-gradient(135deg, #a64d2a 0%, #8e3f20 100%)", + borderAccent: "rgba(255, 255, 255, 0.15)", + qrBg: "#f8f3eb", + qrColor: "#a64d2a", + logoBorder: "#f5be72", + logoBg: "rgba(166, 77, 42, 0.8)", + accentText: "#f5be72", + headerText: "#ffffff", + subText: "rgba(255, 255, 255, 0.9)", + dividerColor: "rgba(255, 255, 255, 0.25)", + categories: ["BURGERS", "FRIES", "SHAKES", "VEGAN OPTIONS"], + }, + "la-maison": { + key: "la-maison", + name: "La Maison Gold", + categoryName: "Fine Dining & Steakhouse", + bgGradient: "radial-gradient(circle at 50% 50%, #18181b 0%, #09090b 100%)", + plaqueBg: "linear-gradient(135deg, #1c1917 0%, #0c0a09 100%)", + borderAccent: "rgba(200, 169, 107, 0.4)", + qrBg: "#faf8f5", + qrColor: "#1c1917", + logoBorder: "#c8a96b", + logoBg: "rgba(28, 25, 23, 0.9)", + accentText: "#c8a96b", + headerText: "#ffffff", + subText: "#d6d3d1", + dividerColor: "rgba(200, 169, 107, 0.3)", + categories: ["BAŞLANGIÇLAR", "ANA YEMEKLER", "ŞARAPLAR", "TATLILAR"], + }, + nordic: { + key: "nordic", + name: "Nordic Minimal", + categoryName: "Kafe, Fırın & Kahve", + bgGradient: "radial-gradient(circle at 50% 50%, #e7e5e4 0%, #d6d3d1 100%)", + plaqueBg: "linear-gradient(135deg, #ffffff 0%, #f5f5f4 100%)", + borderAccent: "rgba(0, 0, 0, 0.08)", + qrBg: "#18181b", + qrColor: "#ffffff", + logoBorder: "#18181b", + logoBg: "#ffffff", + accentText: "#18181b", + headerText: "#18181b", + subText: "#78716c", + dividerColor: "rgba(0, 0, 0, 0.12)", + categories: ["ESPRESSO BARI", "FROZEN & İÇECEK", "KRUVASAN", "TATLILAR"], + }, + bistro: { + key: "bistro", + name: "Classic Bistro", + categoryName: "Pizzeria & İtalyan", + bgGradient: "radial-gradient(circle at 50% 50%, #3f1212 0%, #240a0a 100%)", + plaqueBg: "linear-gradient(135deg, #8b1e1e 0%, #6e1616 100%)", + borderAccent: "rgba(255, 235, 200, 0.2)", + qrBg: "#fffbeb", + qrColor: "#8b1e1e", + logoBorder: "#f59e0b", + logoBg: "rgba(139, 30, 30, 0.85)", + accentText: "#fbbf24", + headerText: "#fffbeb", + subText: "#fef3c7", + dividerColor: "rgba(255, 235, 200, 0.3)", + categories: ["ODUN ATEŞİ PİZZA", "MAKARNALAR", "SALATALAR", "TİRAMİSU"], + }, + neon: { + key: "neon", + name: "Neon Sapphire", + categoryName: "Bar, Pub & Nightlife", + bgGradient: "radial-gradient(circle at 50% 50%, #0f172a 0%, #020617 100%)", + plaqueBg: "linear-gradient(135deg, #1e1b4b 0%, #0f172a 100%)", + borderAccent: "rgba(99, 102, 241, 0.4)", + qrBg: "#e0e7ff", + qrColor: "#312e81", + logoBorder: "#818cf8", + logoBg: "rgba(30, 27, 75, 0.9)", + accentText: "#818cf8", + headerText: "#ffffff", + subText: "#c7d2fe", + dividerColor: "rgba(99, 102, 241, 0.3)", + categories: ["İMKAN KOKTEYLLER", "BİRALAR", "ATIŞTIRMALIKLAR", "MÜZİK & ETKİNLİK"], + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 491fdaa..a4ceabb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,9 +144,18 @@ importers: '@swc/helpers': specifier: ^0.5.15 version: 0.5.23 + html-to-image: + specifier: ^1.11.13 + version: 1.11.13 + jspdf: + specifier: ^4.2.1 + version: 4.2.1 next: specifier: ^15.2.1 version: 15.5.23(@babel/core@7.29.7(supports-color@8.1.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + qrcode.react: + specifier: ^4.2.0 + version: 4.2.0(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -2001,12 +2010,18 @@ packages: '@types/node@22.20.1': resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + '@types/pako@2.0.4': + resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} + '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} '@types/qrcode@1.5.6': resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + '@types/raf@3.4.3': + resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==} + '@types/react-dom@18.3.7': resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} peerDependencies: @@ -2018,6 +2033,9 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2247,6 +2265,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -2363,6 +2385,10 @@ packages: caniuse-lite@1.0.30001809: resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + canvg@3.0.11: + resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==} + engines: {node: '>=10.0.0'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2509,6 +2535,9 @@ packages: resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} engines: {node: '>=6.4.0'} + core-js@3.50.0: + resolution: {integrity: sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==} + cosmiconfig@5.2.1: resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==} engines: {node: '>=4'} @@ -2531,6 +2560,9 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} + css-line-break@2.1.0: + resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -2633,6 +2665,9 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dompurify@3.4.14: + resolution: {integrity: sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==} + dotenv-expand@11.0.7: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} @@ -2887,6 +2922,9 @@ packages: fast-json-stringify@7.0.1: resolution: {integrity: sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==} + fast-png@6.4.0: + resolution: {integrity: sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==} + fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} @@ -2929,6 +2967,9 @@ packages: fetch-retry@4.1.1: resolution: {integrity: sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -3124,6 +3165,13 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} + html-to-image@1.11.13: + resolution: {integrity: sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==} + + html2canvas@1.4.1: + resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} + engines: {node: '>=8.0.0'} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -3177,6 +3225,9 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + iobuffer@5.4.0: + resolution: {integrity: sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==} + ip-regex@2.1.0: resolution: {integrity: sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==} engines: {node: '>=4'} @@ -3375,6 +3426,9 @@ packages: jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + jspdf@4.2.1: + resolution: {integrity: sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -3867,6 +3921,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@2.2.0: + resolution: {integrity: sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==} + parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -3913,6 +3970,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -4066,6 +4126,11 @@ packages: resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} hasBin: true + qrcode.react@4.2.0: + resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + qrcode@1.5.4: resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} engines: {node: '>=10.13.0'} @@ -4084,6 +4149,9 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + raf@3.4.1: + resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -4279,6 +4347,10 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rgbcolor@1.0.1: + resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==} + engines: {node: '>= 0.8.15'} + rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -4474,6 +4546,10 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + stackblur-canvas@2.7.0: + resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==} + engines: {node: '>=0.1.14'} + stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -4575,6 +4651,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svg-pathdata@6.0.3: + resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==} + engines: {node: '>=12.0.0'} + tailwindcss@3.4.19: resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} engines: {node: '>=14.0.0'} @@ -4610,6 +4690,9 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + text-segmentation@1.0.3: + resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -4785,6 +4868,9 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + utrie@1.0.2: + resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + uuid@7.0.3: resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). @@ -7014,12 +7100,17 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/pako@2.0.4': {} + '@types/prop-types@15.7.15': {} '@types/qrcode@1.5.6': dependencies: '@types/node': 22.20.1 + '@types/raf@3.4.3': + optional: true + '@types/react-dom@18.3.7(@types/react@18.3.31)': dependencies: '@types/react': 18.3.31 @@ -7031,6 +7122,9 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/trusted-types@2.0.7': + optional: true + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.35': @@ -7285,6 +7379,9 @@ snapshots: balanced-match@1.0.2: {} + base64-arraybuffer@1.0.2: + optional: true + base64-js@1.5.1: {} baseline-browser-mapping@2.11.15: {} @@ -7401,6 +7498,18 @@ snapshots: caniuse-lite@1.0.30001809: {} + canvg@3.0.11: + dependencies: + '@babel/runtime': 7.29.7 + '@types/raf': 3.4.3 + core-js: 3.50.0 + raf: 3.4.1 + regenerator-runtime: 0.13.11 + rgbcolor: 1.0.1 + stackblur-canvas: 2.7.0 + svg-pathdata: 6.0.3 + optional: true + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -7563,6 +7672,9 @@ snapshots: dependencies: browserslist: 4.28.8 + core-js@3.50.0: + optional: true + cosmiconfig@5.2.1: dependencies: import-fresh: 2.0.0 @@ -7594,6 +7706,11 @@ snapshots: crypto-random-string@2.0.0: {} + css-line-break@2.1.0: + dependencies: + utrie: 1.0.2 + optional: true + cssesc@3.0.0: {} csstype@3.2.3: {} @@ -7669,6 +7786,11 @@ snapshots: dlv@1.1.3: {} + dompurify@3.4.14: + optionalDependencies: + '@types/trusted-types': 2.0.7 + optional: true + dotenv-expand@11.0.7: dependencies: dotenv: 16.4.7 @@ -8005,6 +8127,12 @@ snapshots: json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 + fast-png@6.4.0: + dependencies: + '@types/pako': 2.0.4 + iobuffer: 5.4.0 + pako: 2.2.0 + fast-querystring@1.1.2: dependencies: fast-decode-uri-component: 1.0.1 @@ -8067,6 +8195,8 @@ snapshots: fetch-retry@4.1.1: {} + fflate@0.8.3: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -8282,6 +8412,14 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-to-image@1.11.13: {} + + html2canvas@1.4.1: + dependencies: + css-line-break: 2.1.0 + text-segmentation: 1.0.3 + optional: true + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -8329,6 +8467,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + iobuffer@5.4.0: {} + ip-regex@2.1.0: {} ipaddr.js@1.9.1: {} @@ -8545,6 +8685,17 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jspdf@4.2.1: + dependencies: + '@babel/runtime': 7.29.7 + fast-png: 6.4.0 + fflate: 0.8.3 + optionalDependencies: + canvg: 3.0.11 + core-js: 3.50.0 + dompurify: 3.4.14 + html2canvas: 1.4.1 + kind-of@6.0.3: {} kleur@3.0.3: {} @@ -9088,6 +9239,8 @@ snapshots: package-json-from-dist@1.0.1: {} + pako@2.2.0: {} + parse-json@4.0.0: dependencies: error-ex: 1.3.4 @@ -9120,6 +9273,9 @@ snapshots: pathe@2.0.3: {} + performance-now@2.1.0: + optional: true + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -9262,6 +9418,10 @@ snapshots: qrcode-terminal@0.11.0: {} + qrcode.react@4.2.0(react@18.3.1): + dependencies: + react: 18.3.1 + qrcode@1.5.4: dependencies: dijkstrajs: 1.0.3 @@ -9283,6 +9443,11 @@ snapshots: quick-format-unescaped@4.0.4: {} + raf@3.4.1: + dependencies: + performance-now: 2.1.0 + optional: true + range-parser@1.2.1: {} rc@1.2.8: @@ -9511,6 +9676,9 @@ snapshots: rfdc@1.4.1: {} + rgbcolor@1.0.1: + optional: true + rimraf@2.6.3: dependencies: glob: 7.2.3 @@ -9739,6 +9907,9 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + stackblur-canvas@2.7.0: + optional: true + stackframe@1.3.4: {} stacktrace-parser@0.1.11: @@ -9825,6 +9996,9 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svg-pathdata@6.0.3: + optional: true + tailwindcss@3.4.19(tsx@4.23.12): dependencies: '@alloc/quick-lru': 5.2.0 @@ -9894,6 +10068,11 @@ snapshots: glob: 7.2.3 minimatch: 3.1.5 + text-segmentation@1.0.3: + dependencies: + utrie: 1.0.2 + optional: true + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -10034,6 +10213,11 @@ snapshots: utils-merge@1.0.1: {} + utrie@1.0.2: + dependencies: + base64-arraybuffer: 1.0.2 + optional: true + uuid@7.0.3: {} uuid@8.3.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index da62fde..4036e2f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,5 +2,6 @@ packages: - "apps/*" - "packages/*" allowBuilds: + core-js: set this to true or false esbuild: true sharp: true