Updated dependencies. Moved email assets from base64 to attachments to improve support in Gmail. Implemented custom email branding support. Implemented custom print branding support. Moved image assets to email/kiosk directory. Implemented kiosk dark mode logo support. Updated README.md

This commit is contained in:
Glenn de Haan
2025-12-02 18:42:35 +01:00
parent 4e3ca28f7a
commit 24bfbedc42
10 changed files with 107 additions and 16 deletions

View File

@@ -617,6 +617,41 @@ Heres what each variable represents:
> PRINTERS: 'pdf,192.168.1.10,192.168.1.11' > PRINTERS: 'pdf,192.168.1.10,192.168.1.11'
> ``` > ```
### Custom Branding (Logo)
You can customize the appearance of the pdf and ESC/POS print layout by providing your own branding assets, including:
* `logo.png` — Logo used in the print header
To do this, use Docker volume mappings to mount your custom assets into the `/print` directory inside the container.
The application will use these files (if present) instead of the default ones.
> **If you provide custom branding for printed vouchers, make sure your logo closely match the original application logos size, aspect ratio, and color space.** This helps ensure the layout prints correctly and avoids color shifts or misalignment in the final output.
#### Example
Suppose you have your custom images in a local directory called `branding/`:
```
branding/
└── logo.png
```
You can configure this using Docker Compose:
```yaml
services:
unifi-voucher-site:
image: glenndehaan/unifi-voucher-site:latest
ports:
- "3000:3000"
environment:
KIOSK_ENABLED: 'true'
KIOSK_VOUCHER_TYPES: '480,1,,,;'
volumes:
- ./branding:/print
```
### Usage ### Usage
#### PDF #### PDF
@@ -677,6 +712,41 @@ Heres what each variable represents:
These settings allow the application to connect to your SMTP server and send emails on your behalf. These settings allow the application to connect to your SMTP server and send emails on your behalf.
### Custom Branding (Logo)
You can customize the appearance of the email by providing your own branding assets, including:
* `logo.png` — Logo used in the email header
To do this, use Docker volume mappings to mount your custom assets into the `/email` directory inside the container.
The application will use these files (if present) instead of the default ones.
> We recommend to keep the logo at a maximum height of 75px, to prevent layout shifts
#### Example
Suppose you have your custom images in a local directory called `branding/`:
```
branding/
└── logo.png
```
You can configure this using Docker Compose:
```yaml
services:
unifi-voucher-site:
image: glenndehaan/unifi-voucher-site:latest
ports:
- "3000:3000"
environment:
KIOSK_ENABLED: 'true'
KIOSK_VOUCHER_TYPES: '480,1,,,;'
volumes:
- ./branding:/email
```
### Usage ### Usage
Once the SMTP environment variables are configured, the email feature will be available within the UniFi Voucher Site interface. After generating a voucher, you will see an option to send the voucher via email. Enter the recipient's email address, and the application will send the voucher details directly to their inbox. Once the SMTP environment variables are configured, the email feature will be available within the UniFi Voucher Site interface. After generating a voucher, you will see an option to send the voucher via email. Enter the recipient's email address, and the application will send the voucher details directly to their inbox.
@@ -760,9 +830,14 @@ KIOSK_VOUCHER_TYPES: '480,1,,,;'
### Custom Branding (Logo and Background) ### Custom Branding (Logo and Background)
You can customize the appearance of the kiosk page by providing your own `logo.png` and `bg.jpg` images. You can customize the appearance of the kiosk page by providing your own branding assets, including:
To do this, use Docker volume mappings to mount your custom assets to the `/kiosk` directory inside the container. The application will use these files (if present) instead of the default ones. * `logo.png` — Logo used in light mode
* `logo_dark.png` — Logo used in dark mode
* `bg.jpg` — Background image
To do this, use Docker volume mappings to mount your custom assets into the `/kiosk` directory inside the container.
The application will use these files (if present) instead of the default ones.
#### Example #### Example
@@ -771,6 +846,7 @@ Suppose you have your custom images in a local directory called `branding/`:
``` ```
branding/ branding/
├── logo.png ├── logo.png
├── logo_dark.png
└── bg.jpg └── bg.jpg
``` ```
@@ -789,7 +865,8 @@ services:
- ./branding:/kiosk - ./branding:/kiosk
``` ```
> **Note:** Ensure `logo.png` and `bg.jpg` are valid image files. Both are optional — only override the ones you want to customize. > **Note:** All branding files are optional — provide only the ones you want to override.
> Ensure `logo.png`, `logo_dark.png`, and `bg.jpg` are valid image files.
### Usage ### Usage

View File

@@ -75,10 +75,21 @@ module.exports = {
voucher, voucher,
unifiSsid: variables.unifiSsid, unifiSsid: variables.unifiSsid,
unifiSsidPassword: variables.unifiSsidPassword, unifiSsidPassword: variables.unifiSsidPassword,
qr: await qr(),
timeConvert: time, timeConvert: time,
bytesConvert: bytes bytesConvert: bytes
}) }),
attachments: [
{
filename: 'logo.png',
content: fs.existsSync('/email/logo.png') ? fs.readFileSync(`/email/logo.png`) : fs.readFileSync(`${process.cwd()}/public/images/email/logo.png`),
cid: 'logo@unifi-voucher-site.com'
},
{
filename: 'qr.png',
content: await qr(true),
cid: 'qr@unifi-voucher-site.com'
}
]
}).catch((e) => { }).catch((e) => {
log.error(`[Mail] Error when sending mail`); log.error(`[Mail] Error when sending mail`);
log.error(e); log.error(e);

View File

@@ -1,6 +1,7 @@
/** /**
* Import base packages * Import vendor modules
*/ */
const fs = require('fs');
const PDFDocument = require('pdfkit'); const PDFDocument = require('pdfkit');
const ThermalPrinter = require('node-thermal-printer').printer; const ThermalPrinter = require('node-thermal-printer').printer;
const PrinterTypes = require('node-thermal-printer').types; const PrinterTypes = require('node-thermal-printer').types;
@@ -82,7 +83,7 @@ module.exports = {
doc.moveDown(1); doc.moveDown(1);
} }
doc.image('public/images/logo_grayscale_dark.png', 75, 15, { doc.image(fs.existsSync('/print/logo.png') ? '/print/logo.png' : `${process.cwd()}/public/images/print/logo.png`, 75, 15, {
fit: [75, 75], fit: [75, 75],
align: 'center', align: 'center',
valign: 'center' valign: 'center'
@@ -248,7 +249,7 @@ module.exports = {
printer.setTypeFontB(); printer.setTypeFontB();
printer.alignCenter(); printer.alignCenter();
printer.newLine(); printer.newLine();
await printer.printImage(`${process.cwd()}/public/images/logo_grayscale_dark.png`); await printer.printImage(fs.existsSync('/print/logo.png') ? '/print/logo.png' : `${process.cwd()}/public/images/logo_grayscale_dark.png`);
printer.newLine(); printer.newLine();
printer.alignCenter(); printer.alignCenter();

8
package-lock.json generated
View File

@@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"express": "^5.2.0", "express": "^5.2.1",
"express-locale": "^2.0.2", "express-locale": "^2.0.2",
"express-openid-connect": "^2.19.3", "express-openid-connect": "^2.19.3",
"js-logger": "^1.6.1", "js-logger": "^1.6.1",
@@ -1837,9 +1837,9 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "5.2.0", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.2.0.tgz", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-XdpJDLxfztVY59X0zPI6sibRiGcxhTPXRD3IhJmjKf2jwMvkRGV1j7loB8U+heeamoU3XvihAaGRTR4aXXUN3A==", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"accepts": "^2.0.0", "accepts": "^2.0.0",

View File

@@ -17,7 +17,7 @@
"dependencies": { "dependencies": {
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"express": "^5.2.0", "express": "^5.2.1",
"express-locale": "^2.0.2", "express-locale": "^2.0.2",
"express-openid-connect": "^2.19.3", "express-openid-connect": "^2.19.3",
"js-logger": "^1.6.1", "js-logger": "^1.6.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -114,7 +114,7 @@
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> <td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">
<p> <p>
<center> <center>
<img src="https://github.com/glenndehaan/unifi-voucher-site/blob/master/public/images/icon/logo_192x192.png?raw=true" height="75px"/> <img src="cid:logo@unifi-voucher-site.com" height="75px" alt="Logo" style="height: 75px;"/>
<br/> <br/>
<h1 style="font-weight: 400; font-size: 1.75rem; line-height: 1.2;"><%= t('title') %></h1> <h1 style="font-weight: 400; font-size: 1.75rem; line-height: 1.2;"><%= t('title') %></h1>
</center> </center>
@@ -134,7 +134,7 @@
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><%= t('connect') %>: <span style="font-weight: bold;"><%= unifiSsid %></span> <%= t('or') %>,</p> <p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><%= t('connect') %>: <span style="font-weight: bold;"><%= unifiSsid %></span> <%= t('or') %>,</p>
<% } %> <% } %>
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><%= t('scan') %>:</p> <p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0;"><%= t('scan') %>:</p>
<img src="<%= qr %>" /> <img src="cid:qr@unifi-voucher-site.com" alt="Scan to Connect QR Code" />
</center> </center>
<p style="font-family: sans-serif; font-size: 5px; margin: 0; margin-bottom: 5px;">&nbsp;</p> <p style="font-family: sans-serif; font-size: 5px; margin: 0; margin-bottom: 5px;">&nbsp;</p>
<% } %> <% } %>

View File

@@ -24,6 +24,7 @@
<meta name="theme-color" content="#139CDA"> <meta name="theme-color" content="#139CDA">
<link rel="preload" href="<%= baseUrl %>/images/kiosk/logo.png" as="image"> <link rel="preload" href="<%= baseUrl %>/images/kiosk/logo.png" as="image">
<link rel="preload" href="<%= baseUrl %>/images/kiosk/logo_dark.png" as="image">
<link rel="preload" href="<%= baseUrl %>/images/kiosk/bg.jpg" as="image"> <link rel="preload" href="<%= baseUrl %>/images/kiosk/bg.jpg" as="image">
<link rel="preload" href="<%= baseUrl %>/dist/style.css" as="style"> <link rel="preload" href="<%= baseUrl %>/dist/style.css" as="style">
<link href="<%= baseUrl %>/dist/style.css" rel="stylesheet"> <link href="<%= baseUrl %>/dist/style.css" rel="stylesheet">
@@ -60,7 +61,8 @@
<div class="w-full max-w-md bg-white dark:bg-gray-800 rounded-lg border border-black/5 dark:border-white/5 shadow-sm z-10 relative"> <div class="w-full max-w-md bg-white dark:bg-gray-800 rounded-lg border border-black/5 dark:border-white/5 shadow-sm z-10 relative">
<div class="p-4 border-b border-black/5 dark:border-white/5"> <div class="p-4 border-b border-black/5 dark:border-white/5">
<img class="mx-auto h-24 w-auto" width="48" height="48" alt="UniFi Voucher Site Logo" src="<%= baseUrl %>/images/kiosk/logo.png"> <img class="dark:hidden block mx-auto h-24 w-auto" width="48" height="48" alt="UniFi Voucher Site Logo" src="<%= baseUrl %>/images/kiosk/logo.png">
<img class="dark:block hidden mx-auto h-24 w-auto" width="48" height="48" alt="UniFi Voucher Site Logo" src="<%= baseUrl %>/images/kiosk/logo_dark.png">
<h1 class="mt-4 text-2xl font-semibold text-center text-gray-900 dark:text-white"><%= t('title') %></h1> <h1 class="mt-4 text-2xl font-semibold text-center text-gray-900 dark:text-white"><%= t('title') %></h1>
<% if(error) { %> <% if(error) { %>