Skip to content

Commit a2d8c0a

Browse files
committed
Refactor Labelary preview to support custom hosts
Update the Labelary preview functionality to allow for custom API hosts and keys, supporting private Labelary instances. This includes changes to the store, locale strings, and the addition of a `isDefaultLabelaryHost` utility function. The privacy notice link has also been updated to point to Labelary's service and pricing information, rather than just privacy.
1 parent ae23855 commit a2d8c0a

38 files changed

Lines changed: 98 additions & 52 deletions

‎src/components/AppShell.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
SunIcon,
3232
MoonIcon,
3333
} from "@heroicons/react/16/solid";
34-
import { useLabelStore, useHistory } from "../store/labelStore";
34+
import { useLabelStore, useHistory, selectLabelaryNoticeRequired } from "../store/labelStore";
3535
import { LabelaryNoticeModal } from "./Output/LabelaryNoticeModal";
3636
import { localeNames } from "../locales";
3737
import type { LocaleCode } from "../locales";
@@ -54,7 +54,7 @@ export function AppShell() {
5454
const theme = useLabelStore((s) => s.theme);
5555
const setTheme = useLabelStore((s) => s.setTheme);
5656
const labelaryEnabled = useLabelStore((s) => s.thirdParty.labelary);
57-
const noticeAcknowledged = useLabelStore((s) => s.labelaryNoticeAcknowledged);
57+
const noticeRequired = useLabelStore(selectLabelaryNoticeRequired);
5858
const [showPrintNotice, setShowPrintNotice] = useState(false);
5959

6060
// Bridge the theme preference to <html data-theme> so the CSS variables in
@@ -218,7 +218,7 @@ export function AppShell() {
218218
{labelaryEnabled && (
219219
<DropdownItem
220220
icon={PrinterIcon}
221-
onClick={() => (noticeAcknowledged ? handlePrint() : setShowPrintNotice(true))}
221+
onClick={() => (noticeRequired ? setShowPrintNotice(true) : handlePrint())}
222222
disabled={!hasObjects}
223223
>
224224
{t.app.print}

‎src/components/Output/LabelPreview.tsx‎

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
22
import { XMarkIcon, ArrowDownTrayIcon } from '@heroicons/react/16/solid';
33
import { useLabelStore, useCurrentObjects } from '../../store/labelStore';
44
import { generateZPL } from '../../lib/zplGenerator';
5-
import { fetchPreview, labelaryErrorMessage } from '../../lib/labelary';
5+
import { fetchPreview, labelaryErrorMessage, isDefaultLabelaryHost } from '../../lib/labelary';
66
import { triggerDownload } from '../../lib/triggerDownload';
77
import { useT } from '../../lib/useT';
88
import { DialogShell } from '../ui/DialogShell';
@@ -101,16 +101,18 @@ export function LabelPreviewModal({ onClose }: Props) {
101101
</div>
102102
</div>
103103

104-
<div className="px-3 py-1 border-t border-border-2 shrink-0 text-center">
105-
<a
106-
href="https://labelary.com/"
107-
target="_blank"
108-
rel="noreferrer"
109-
className="font-mono text-[9px] text-muted hover:text-accent transition-colors"
110-
>
111-
{t.output.previewProvider}
112-
</a>
113-
</div>
104+
{isDefaultLabelaryHost() && (
105+
<div className="px-3 py-1 border-t border-border-2 shrink-0 text-center">
106+
<a
107+
href="https://labelary.com/"
108+
target="_blank"
109+
rel="noreferrer"
110+
className="font-mono text-[9px] text-muted hover:text-accent transition-colors"
111+
>
112+
{t.output.previewProvider}
113+
</a>
114+
</div>
115+
)}
114116
</DialogShell>
115117
);
116118
}

‎src/components/Output/LabelaryNoticeModal.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function LabelaryNoticeModal({ onContinue, onClose }: Props) {
4747
<div className="flex flex-col gap-3 p-6 max-w-80 text-center font-mono text-[10px] text-muted leading-relaxed">
4848
<span>{t.output.previewNoticeBody}</span>
4949
<a
50-
href="https://labelary.com/privacy.html"
50+
href="https://labelary.com/service.html#pricing"
5151
target="_blank"
5252
rel="noreferrer"
5353
className="text-accent hover:underline"

‎src/components/Output/ZPLOutput.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState } from 'react';
22
import { CheckIcon, ClipboardDocumentIcon, ChevronDownIcon, ChevronUpIcon, EyeIcon } from '@heroicons/react/16/solid';
3-
import { useLabelStore } from '../../store/labelStore';
3+
import { useLabelStore, selectLabelaryNoticeRequired } from '../../store/labelStore';
44
import { generateMultiPageZPL } from '../../lib/zplGenerator';
55
import { useT } from '../../lib/useT';
66
import { LabelPreviewModal } from './LabelPreview';
@@ -17,7 +17,7 @@ export function ZPLOutput({ collapsed, onCollapse, onExpand }: Props) {
1717
const label = useLabelStore((s) => s.label);
1818
const pages = useLabelStore((s) => s.pages);
1919
const labelaryEnabled = useLabelStore((s) => s.thirdParty.labelary);
20-
const noticeAcknowledged = useLabelStore((s) => s.labelaryNoticeAcknowledged);
20+
const noticeRequired = useLabelStore(selectLabelaryNoticeRequired);
2121
const [copied, setCopied] = useState(false);
2222
const [showPreview, setShowPreview] = useState(false);
2323
const [showNotice, setShowNotice] = useState(false);
@@ -50,7 +50,7 @@ export function ZPLOutput({ collapsed, onCollapse, onExpand }: Props) {
5050
<div className="flex items-center gap-3">
5151
{labelaryEnabled && (
5252
<button
53-
onClick={() => (noticeAcknowledged ? setShowPreview(true) : setShowNotice(true))}
53+
onClick={() => (noticeRequired ? setShowNotice(true) : setShowPreview(true))}
5454
disabled={!zpl}
5555
title={t.output.previewHeading}
5656
className="flex items-center gap-1 font-mono text-[10px] text-muted hover:text-accent disabled:opacity-25 disabled:cursor-not-allowed transition-colors"

‎src/lib/labelary.ts‎

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,39 @@
11
import type { LabelConfig } from '../types/ObjectType';
22

33
const TIMEOUT_MS = 10_000;
4+
const DEFAULT_HOST = 'https://api.labelary.com';
5+
6+
// Build-time Labelary endpoint configuration. Labelary's premium plans
7+
// (Plus, Business, On-Premise) hand out a private hostname — and for the
8+
// metered plans also an API key — via email upon sign-up. Operators set:
9+
// - VITE_LABELARY_API_URL – e.g. https://acme.labelary.com
10+
// - VITE_LABELARY_API_KEY – the key value, if the plan requires one
11+
// The key is sent as X-API-Key, matching Labelary's own viewer
12+
// (https://labelary.com/viewer.html source).
13+
14+
function trimmed(raw: unknown): string | undefined {
15+
if (typeof raw !== 'string') return undefined;
16+
const t = raw.trim();
17+
return t || undefined;
18+
}
19+
20+
function host(): string {
21+
const configured = trimmed(import.meta.env.VITE_LABELARY_API_URL);
22+
if (!configured) return DEFAULT_HOST;
23+
return configured.replace(/\/+$/, '');
24+
}
25+
26+
function apiKey(): string | undefined {
27+
return trimmed(import.meta.env.VITE_LABELARY_API_KEY);
28+
}
29+
30+
/** True when the build targets the public api.labelary.com service. UI uses
31+
* this to decide whether to surface the third-party-data-leaves-this-app
32+
* privacy notice; a custom host implies the operator already controls the
33+
* endpoint. */
34+
export function isDefaultLabelaryHost(): boolean {
35+
return host() === DEFAULT_HOST;
36+
}
437

538
export class LabelaryError extends Error {
639
readonly kind: 'api' | 'timeout' | 'network';
@@ -15,13 +48,17 @@ export async function fetchPreview(zpl: string, label: LabelConfig): Promise<str
1548
const { dpmm, widthMm, heightMm } = label;
1649
const widthIn = (widthMm / 25.4).toFixed(3);
1750
const heightIn = (heightMm / 25.4).toFixed(3);
18-
const url = `https://api.labelary.com/v1/printers/${dpmm}dpmm/labels/${widthIn}x${heightIn}/0/`;
51+
const url = `${host()}/v1/printers/${dpmm}dpmm/labels/${widthIn}x${heightIn}/0/`;
52+
53+
const headers: Record<string, string> = { 'Content-Type': 'application/x-www-form-urlencoded' };
54+
const key = apiKey();
55+
if (key) headers['X-API-Key'] = key;
1956

2057
let res: Response;
2158
try {
2259
res = await fetch(url, {
2360
method: 'POST',
24-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
61+
headers,
2562
body: zpl,
2663
signal: AbortSignal.timeout(TIMEOUT_MS),
2764
});

‎src/locales/ar.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ const ar = {
132132
previewProvider: 'معاينة عبر api.labelary.com',
133133
previewNoticeTitle: 'إشعار الخصوصية',
134134
previewNoticeBody: 'يتم إنشاء المعاينة والطباعة بواسطة الخدمة الخارجية api.labelary.com. يتم إرسال ZPL الكامل للملصق، بما في ذلك أي بيانات حساسة، عبر الشبكة.',
135-
previewNoticePrivacyLink: 'معلومات خصوصية Labelary',
135+
previewNoticePrivacyLink: 'خطط Labelary والاحتفاظ بالبيانات',
136136
previewNoticeAcknowledge: 'فهمت، تابع',
137137
},
138138

‎src/locales/bg.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ const bg = {
132132
previewProvider: 'Преглед чрез api.labelary.com',
133133
previewNoticeTitle: 'Известие за поверителност',
134134
previewNoticeBody: 'Прегледът и отпечатването се изобразяват от външната услуга api.labelary.com. Целият ZPL на етикета, включително чувствителните данни, се изпраща през мрежата.',
135-
previewNoticePrivacyLink: 'Информация за поверителност на Labelary',
135+
previewNoticePrivacyLink: 'Планове и съхранение на данни на Labelary',
136136
previewNoticeAcknowledge: 'Разбрах, продължи',
137137
},
138138

‎src/locales/cs.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ const cs = {
132132
previewProvider: 'Náhled přes api.labelary.com',
133133
previewNoticeTitle: 'Upozornění na ochranu údajů',
134134
previewNoticeBody: 'Náhled a tisk se generují pomocí externí služby api.labelary.com. Celý ZPL štítku, včetně citlivých údajů, je odesílán přes síť.',
135-
previewNoticePrivacyLink: 'Informace o ochraně údajů Labelary',
135+
previewNoticePrivacyLink: 'Tarify a uchovávání dat Labelary',
136136
previewNoticeAcknowledge: 'Rozumím, pokračovat',
137137
},
138138

‎src/locales/da.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ const da = {
132132
previewProvider: 'Forhåndsvisning via api.labelary.com',
133133
previewNoticeTitle: 'Privatlivsmeddelelse',
134134
previewNoticeBody: 'Forhåndsvisning og udskrivning genereres af den eksterne tjeneste api.labelary.com. Hele etikettens ZPL, inklusive følsomme data, sendes over netværket.',
135-
previewNoticePrivacyLink: 'Labelary-privatlivsoplysninger',
135+
previewNoticePrivacyLink: 'Labelary-abonnementer og dataopbevaring',
136136
previewNoticeAcknowledge: 'Forstået, fortsæt',
137137
},
138138

‎src/locales/de.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ const de = {
153153
previewProvider: 'Vorschau über api.labelary.com',
154154
previewNoticeTitle: 'Datenschutzhinweis',
155155
previewNoticeBody: 'Vorschau und Druck werden vom externen Dienst api.labelary.com erstellt. Dabei wird das vollständige Label-ZPL inklusive sensibler Daten übertragen.',
156-
previewNoticePrivacyLink: 'Labelary-Datenschutzhinweise',
156+
previewNoticePrivacyLink: 'Labelary-Tarife & Datenaufbewahrung',
157157
previewNoticeAcknowledge: 'Verstanden, fortfahren',
158158
},
159159

0 commit comments

Comments
 (0)