mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(qr-generate): add Zustand store for QR generator state
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
export type ContentType = "url" | "text" | "wifi" | "vcard" | "email" | "phone" | "sms";
|
||||
|
||||
export type DotType = "square" | "rounded" | "dots" | "classy" | "classy-rounded" | "extra-rounded";
|
||||
export type CornerSquareType = "square" | "rounded" | "dot" | "extra-rounded";
|
||||
export type CornerDotType = "square" | "rounded" | "dot";
|
||||
export type ErrorCorrectionLevel = "L" | "M" | "Q" | "H";
|
||||
export type DownloadFormat = "png" | "svg" | "jpeg" | "webp";
|
||||
|
||||
export interface WifiData {
|
||||
ssid: string;
|
||||
password: string;
|
||||
encryption: "WPA" | "WEP" | "nopass";
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
export interface VCardData {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
organization: string;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface EmailData {
|
||||
to: string;
|
||||
subject: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface SmsData {
|
||||
phone: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface QrState {
|
||||
// Content
|
||||
contentType: ContentType;
|
||||
textData: string;
|
||||
wifiData: WifiData;
|
||||
vcardData: VCardData;
|
||||
emailData: EmailData;
|
||||
phoneData: string;
|
||||
smsData: SmsData;
|
||||
|
||||
// Style
|
||||
dotType: DotType;
|
||||
cornerSquareType: CornerSquareType;
|
||||
cornerDotType: CornerDotType;
|
||||
|
||||
// Colors
|
||||
dotColor: string;
|
||||
dotGradientEnabled: boolean;
|
||||
dotGradientType: "linear" | "radial";
|
||||
dotGradientColor1: string;
|
||||
dotGradientColor2: string;
|
||||
dotGradientRotation: number;
|
||||
bgColor: string;
|
||||
bgTransparent: boolean;
|
||||
cornerSquareColor: string;
|
||||
cornerDotColor: string;
|
||||
useCustomCornerColors: boolean;
|
||||
|
||||
// Logo
|
||||
logoFile: File | null;
|
||||
logoDataUrl: string | null;
|
||||
logoSize: number;
|
||||
logoMargin: number;
|
||||
hideBackgroundDots: boolean;
|
||||
|
||||
// Output
|
||||
size: number;
|
||||
errorCorrection: ErrorCorrectionLevel;
|
||||
downloadFormat: DownloadFormat;
|
||||
|
||||
// Actions
|
||||
setContentType: (t: ContentType) => void;
|
||||
setTextData: (v: string) => void;
|
||||
setWifiData: (v: Partial<WifiData>) => void;
|
||||
setVcardData: (v: Partial<VCardData>) => void;
|
||||
setEmailData: (v: Partial<EmailData>) => void;
|
||||
setPhoneData: (v: string) => void;
|
||||
setSmsData: (v: Partial<SmsData>) => void;
|
||||
setDotType: (v: DotType) => void;
|
||||
setCornerSquareType: (v: CornerSquareType) => void;
|
||||
setCornerDotType: (v: CornerDotType) => void;
|
||||
setDotColor: (v: string) => void;
|
||||
setDotGradientEnabled: (v: boolean) => void;
|
||||
setDotGradientType: (v: "linear" | "radial") => void;
|
||||
setDotGradientColor1: (v: string) => void;
|
||||
setDotGradientColor2: (v: string) => void;
|
||||
setDotGradientRotation: (v: number) => void;
|
||||
setBgColor: (v: string) => void;
|
||||
setBgTransparent: (v: boolean) => void;
|
||||
setCornerSquareColor: (v: string) => void;
|
||||
setCornerDotColor: (v: string) => void;
|
||||
setUseCustomCornerColors: (v: boolean) => void;
|
||||
setLogoFile: (f: File | null) => void;
|
||||
setLogoSize: (v: number) => void;
|
||||
setLogoMargin: (v: number) => void;
|
||||
setHideBackgroundDots: (v: boolean) => void;
|
||||
setSize: (v: number) => void;
|
||||
setErrorCorrection: (v: ErrorCorrectionLevel) => void;
|
||||
setDownloadFormat: (v: DownloadFormat) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
const initialWifi: WifiData = {
|
||||
ssid: "",
|
||||
password: "",
|
||||
encryption: "WPA",
|
||||
hidden: false,
|
||||
};
|
||||
const initialVcard: VCardData = {
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
organization: "",
|
||||
title: "",
|
||||
url: "",
|
||||
};
|
||||
const initialEmail: EmailData = { to: "", subject: "", body: "" };
|
||||
const initialSms: SmsData = { phone: "", message: "" };
|
||||
|
||||
const DEFAULTS = {
|
||||
contentType: "url" as ContentType,
|
||||
textData: "",
|
||||
wifiData: { ...initialWifi },
|
||||
vcardData: { ...initialVcard },
|
||||
emailData: { ...initialEmail },
|
||||
phoneData: "",
|
||||
smsData: { ...initialSms },
|
||||
dotType: "rounded" as DotType,
|
||||
cornerSquareType: "rounded" as CornerSquareType,
|
||||
cornerDotType: "rounded" as CornerDotType,
|
||||
dotColor: "#000000",
|
||||
dotGradientEnabled: false,
|
||||
dotGradientType: "linear" as const,
|
||||
dotGradientColor1: "#000000",
|
||||
dotGradientColor2: "#4338ca",
|
||||
dotGradientRotation: 0,
|
||||
bgColor: "#FFFFFF",
|
||||
bgTransparent: false,
|
||||
cornerSquareColor: "#000000",
|
||||
cornerDotColor: "#000000",
|
||||
useCustomCornerColors: false,
|
||||
logoFile: null as File | null,
|
||||
logoDataUrl: null as string | null,
|
||||
logoSize: 0.4,
|
||||
logoMargin: 5,
|
||||
hideBackgroundDots: true,
|
||||
size: 1024,
|
||||
errorCorrection: "Q" as ErrorCorrectionLevel,
|
||||
downloadFormat: "png" as DownloadFormat,
|
||||
};
|
||||
|
||||
export const useQrStore = create<QrState>((set) => ({
|
||||
...DEFAULTS,
|
||||
|
||||
setContentType: (t) => set({ contentType: t }),
|
||||
setTextData: (v) => set({ textData: v }),
|
||||
setWifiData: (v) => set((s) => ({ wifiData: { ...s.wifiData, ...v } })),
|
||||
setVcardData: (v) => set((s) => ({ vcardData: { ...s.vcardData, ...v } })),
|
||||
setEmailData: (v) => set((s) => ({ emailData: { ...s.emailData, ...v } })),
|
||||
setPhoneData: (v) => set({ phoneData: v }),
|
||||
setSmsData: (v) => set((s) => ({ smsData: { ...s.smsData, ...v } })),
|
||||
setDotType: (v) => set({ dotType: v }),
|
||||
setCornerSquareType: (v) => set({ cornerSquareType: v }),
|
||||
setCornerDotType: (v) => set({ cornerDotType: v }),
|
||||
setDotColor: (v) => set({ dotColor: v }),
|
||||
setDotGradientEnabled: (v) => set({ dotGradientEnabled: v }),
|
||||
setDotGradientType: (v) => set({ dotGradientType: v }),
|
||||
setDotGradientColor1: (v) => set({ dotGradientColor1: v }),
|
||||
setDotGradientColor2: (v) => set({ dotGradientColor2: v }),
|
||||
setDotGradientRotation: (v) => set({ dotGradientRotation: v }),
|
||||
setBgColor: (v) => set({ bgColor: v }),
|
||||
setBgTransparent: (v) => set({ bgTransparent: v }),
|
||||
setCornerSquareColor: (v) => set({ cornerSquareColor: v }),
|
||||
setCornerDotColor: (v) => set({ cornerDotColor: v }),
|
||||
setUseCustomCornerColors: (v) => set({ useCustomCornerColors: v }),
|
||||
setLogoFile: (f) => {
|
||||
if (!f) {
|
||||
set({ logoFile: null, logoDataUrl: null });
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => set({ logoFile: f, logoDataUrl: reader.result as string });
|
||||
reader.readAsDataURL(f);
|
||||
},
|
||||
setLogoSize: (v) => set({ logoSize: v }),
|
||||
setLogoMargin: (v) => set({ logoMargin: v }),
|
||||
setHideBackgroundDots: (v) => set({ hideBackgroundDots: v }),
|
||||
setSize: (v) => set({ size: v }),
|
||||
setErrorCorrection: (v) => set({ errorCorrection: v }),
|
||||
setDownloadFormat: (v) => set({ downloadFormat: v }),
|
||||
reset: () =>
|
||||
set({
|
||||
...DEFAULTS,
|
||||
wifiData: { ...initialWifi },
|
||||
vcardData: { ...initialVcard },
|
||||
emailData: { ...initialEmail },
|
||||
smsData: { ...initialSms },
|
||||
}),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Encodes the current store state into the string payload for the QR code.
|
||||
*/
|
||||
export function encodeQrData(state: QrState): string {
|
||||
switch (state.contentType) {
|
||||
case "url":
|
||||
case "text":
|
||||
return state.textData;
|
||||
case "wifi": {
|
||||
const w = state.wifiData;
|
||||
return `WIFI:T:${w.encryption};S:${w.ssid};P:${w.password};H:${w.hidden};;`;
|
||||
}
|
||||
case "vcard": {
|
||||
const v = state.vcardData;
|
||||
const lines = [
|
||||
"BEGIN:VCARD",
|
||||
"VERSION:3.0",
|
||||
`N:${v.lastName};${v.firstName}`,
|
||||
`FN:${[v.firstName, v.lastName].filter(Boolean).join(" ")}`,
|
||||
];
|
||||
if (v.phone) lines.push(`TEL:${v.phone}`);
|
||||
if (v.email) lines.push(`EMAIL:${v.email}`);
|
||||
if (v.organization) lines.push(`ORG:${v.organization}`);
|
||||
if (v.title) lines.push(`TITLE:${v.title}`);
|
||||
if (v.url) lines.push(`URL:${v.url}`);
|
||||
lines.push("END:VCARD");
|
||||
return lines.join("\n");
|
||||
}
|
||||
case "email": {
|
||||
const e = state.emailData;
|
||||
const params = [];
|
||||
if (e.subject) params.push(`subject=${encodeURIComponent(e.subject)}`);
|
||||
if (e.body) params.push(`body=${encodeURIComponent(e.body)}`);
|
||||
return `mailto:${e.to}${params.length ? `?${params.join("&")}` : ""}`;
|
||||
}
|
||||
case "phone":
|
||||
return `tel:${state.phoneData}`;
|
||||
case "sms": {
|
||||
const s = state.smsData;
|
||||
return `smsto:${s.phone}:${s.message}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user