feat: list logos from the api instead of hardcoding it

This commit is contained in:
2026-02-21 22:05:26 +01:00
parent 43c91b3c54
commit 81987ac8f1
4 changed files with 32 additions and 55 deletions

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
const baseApiUrl = useBaseApiUrl();
const { copy: copyBaseApiUrl, icon: baseApiUrlIcon } = useCopyable(baseApiUrl);
const { data: logos } = await useFetch<string[]>("/api/logos");
</script>
<template>
@@ -10,8 +11,8 @@ const { copy: copyBaseApiUrl, icon: baseApiUrlIcon } = useCopyable(baseApiUrl);
<p>
You can easily generate QRCodes by using the API, with no rate
limitation.
<br />
<br />
<br >
<br >
If you are not sure how to use the API, you can fill the QRCode form
and copy the generated API URL.
</p>
@@ -58,7 +59,7 @@ const { copy: copyBaseApiUrl, icon: baseApiUrlIcon } = useCopyable(baseApiUrl);
<div class="flex pb-4 justify-between gap-x-5">
<span class="text-primary font-semibold">logo</span>
<span class="text-slate-700 dark:text-slate-200 text-right">{{
arrayToUnion(LOGOS)
arrayToUnion(logos ?? [])
}}</span>
</div>

View File

@@ -11,10 +11,12 @@ const apiModal = overlay.create(LazyApiModal);
const isQRCodeEmpty = computed(() => qrCode.value === "/default.webp");
const { data: logos } = await useFetch<string[]>("/api/logos");
const formSchema = z
.object({
hasLogo: z.boolean(),
logo: z.enum(LOGOS).optional(),
logo: z.string().optional(),
format: z.enum(IMAGE_FORMATS).default("png"),
content: z
.string()
@@ -103,7 +105,7 @@ const {
<img
:src="qrCode"
class="w-full max-w-[375px] max-h-[375px] sm:max-w-[315px] sm:max-h-[315px] md:max-w-[375px] md:max-h-[375px] m-auto aspect-square border border-gray-100 dark:border-gray-800"
/>
>
<div class="flex-1 flex flex-col justify-center">
<UForm
@@ -138,7 +140,7 @@ const {
<USelectMenu
v-model="formState.logo"
icon="i-heroicons-photo"
:items="unreadonly(LOGOS)"
:items="logos ?? []"
:disabled="!formState.hasLogo"
placeholder="Select logo"
class="w-full"

22
server/api/logos.ts Normal file
View File

@@ -0,0 +1,22 @@
import { readdir } from "fs/promises";
import { join } from "path";
import { createHash } from "crypto";
const logosDir = import.meta.dev
? join(process.cwd(), "public/logos")
: join(process.cwd(), ".output/public/logos");
export default defineCachedEventHandler(async () => {
const files = await readdir(logosDir);
return files
.filter((f) => f.endsWith(".png"))
.map((f) => f.replace(/\.png$/, ""))
.sort();
}, {
maxAge: 60 * 60 * 24,
getKey: async () => {
const files = await readdir(logosDir);
const key = files.filter((f) => f.endsWith(".png")).sort().join(",");
return createHash("sha256").update(key).digest("hex");
},
});

View File

@@ -1,60 +1,12 @@
import { z } from "zod";
// TODO: might be better to load these dynamically lol
export const LOGOS = [
"bereal",
"bitcoin",
"buymeacoffee",
"diaspora",
"discord",
"dropbox",
"ello",
"facebook",
"flickr",
"github",
"googlemaps",
"googlemeet",
"googlemessages",
"imessage",
"instagram",
"kik",
"line",
"linkedin",
"litecoin",
"mastodon",
"medium",
"messenger",
"monero",
"onlyfans",
"patreon",
"paypal",
"peertube",
"pinterest",
"reddit",
"session",
"signal",
"snapchat",
"spotify",
"substack",
"telegram",
"threema",
"twitch",
"venmo",
"viber",
"wechat",
"whatsapp",
"x",
"youtube",
"zoom",
] as const;
export const IMAGE_FORMATS = ["png", "jpeg", "webp"] as const;
export type ImageFormat = (typeof IMAGE_FORMATS)[number];
export const settingsSchema = z.object({
format: z.enum(IMAGE_FORMATS).default("png"),
logo: z.enum(LOGOS).optional(),
logo: z.string().optional(),
content: z.string().min(1, "Required"),
});