From da4b765b14b59c187e6f9beb02bcd41e4bebd028 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 5 Apr 2026 16:14:07 +0000 Subject: [PATCH 1/2] feat: show customer branding on login page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds public /v1/store/branding and /v1/store/logo endpoints so the login page can display the customer's name and logo without auth, with "Powered by LunarFront" underneath — matching the sidebar style. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/admin/src/routes/login.tsx | 27 +++++++++++++++++--- packages/backend/src/routes/v1/store.ts | 34 +++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/packages/admin/src/routes/login.tsx b/packages/admin/src/routes/login.tsx index f8dd67e..2938eb7 100644 --- a/packages/admin/src/routes/login.tsx +++ b/packages/admin/src/routes/login.tsx @@ -1,8 +1,13 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { createFileRoute, useRouter, redirect } from '@tanstack/react-router' import { useAuthStore } from '@/stores/auth.store' import { login } from '@/api/auth' +interface Branding { + name: string | null + hasLogo: boolean +} + export const Route = createFileRoute('/login')({ beforeLoad: () => { const { token } = useAuthStore.getState() @@ -20,6 +25,14 @@ function LoginPage() { const [password, setPassword] = useState('') const [error, setError] = useState('') const [loading, setLoading] = useState(false) + const [branding, setBranding] = useState(null) + + useEffect(() => { + fetch('/v1/store/branding') + .then((r) => r.ok ? r.json() : null) + .then((data) => { if (data) setBranding(data) }) + .catch(() => {}) + }, []) async function handleSubmit(e: React.FormEvent) { e.preventDefault() @@ -48,8 +61,16 @@ function LoginPage() { style={{ backgroundColor: '#131c2e', borderColor: '#1e2d45' }} >
-

LunarFront

-

Small Business Management

+ {branding?.hasLogo ? ( + {branding.name + ) : ( +

{branding?.name ?? 'LunarFront'}

+ )} + {branding?.name ? ( +

Powered by LunarFront

+ ) : ( +

Small Business Management

+ )}
diff --git a/packages/backend/src/routes/v1/store.ts b/packages/backend/src/routes/v1/store.ts index 87ea296..ee4e9de 100644 --- a/packages/backend/src/routes/v1/store.ts +++ b/packages/backend/src/routes/v1/store.ts @@ -1,9 +1,43 @@ import type { FastifyPluginAsync } from 'fastify' import { eq } from 'drizzle-orm' import { companies, locations } from '../../db/schema/stores.js' +import { files } from '../../db/schema/files.js' import { ValidationError } from '../../lib/errors.js' export const storeRoutes: FastifyPluginAsync = async (app) => { + // --- Public branding (no auth — used on login page) --- + + app.get('/store/branding', async (_request, reply) => { + const [store] = await app.db.select({ + name: companies.name, + logoFileId: companies.logoFileId, + }).from(companies).limit(1) + if (!store) return reply.send({ name: null, hasLogo: false }) + return reply.send({ name: store.name, hasLogo: !!store.logoFileId }) + }) + + app.get('/store/logo', async (_request, reply) => { + const [store] = await app.db.select({ logoFileId: companies.logoFileId }).from(companies).limit(1) + if (!store?.logoFileId) return reply.status(404).send({ error: { message: 'No logo configured', statusCode: 404 } }) + + const [file] = await app.db.select().from(files).where(eq(files.id, store.logoFileId)).limit(1) + if (!file) return reply.status(404).send({ error: { message: 'Logo file not found', statusCode: 404 } }) + + try { + const data = await app.storage.get(file.path) + const ext = file.path.split('.').pop()?.toLowerCase() + const contentTypeMap: Record = { + jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', webp: 'image/webp', svg: 'image/svg+xml', + } + return reply + .header('Content-Type', contentTypeMap[ext ?? ''] ?? 'application/octet-stream') + .header('Cache-Control', 'public, max-age=3600') + .send(data) + } catch { + return reply.status(404).send({ error: { message: 'Logo file not readable', statusCode: 404 } }) + } + }) + // --- Company / Store Settings --- app.get('/store', { preHandler: [app.authenticate, app.requirePermission('settings.view')] }, async (request, reply) => { From e589ff02f03474a797694886cdb9af8b8d13c4e3 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 5 Apr 2026 16:17:42 +0000 Subject: [PATCH 2/2] fix: remove unused imports to pass lint Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/admin/src/components/pos/pos-cart-panel.tsx | 1 - packages/admin/src/components/pos/pos-receipt.tsx | 1 - packages/backend/src/services/report.service.ts | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/admin/src/components/pos/pos-cart-panel.tsx b/packages/admin/src/components/pos/pos-cart-panel.tsx index 051ee4f..bbf20e6 100644 --- a/packages/admin/src/components/pos/pos-cart-panel.tsx +++ b/packages/admin/src/components/pos/pos-cart-panel.tsx @@ -4,7 +4,6 @@ import { posMutations, posKeys, type Transaction } from '@/api/pos' import { Button } from '@/components/ui/button' import { Separator } from '@/components/ui/separator' import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { X, Banknote, CreditCard, FileText, Ban, UserRound, Tag } from 'lucide-react' import { toast } from 'sonner' diff --git a/packages/admin/src/components/pos/pos-receipt.tsx b/packages/admin/src/components/pos/pos-receipt.tsx index a998d87..df02cc3 100644 --- a/packages/admin/src/components/pos/pos-receipt.tsx +++ b/packages/admin/src/components/pos/pos-receipt.tsx @@ -100,7 +100,6 @@ function useStoreLogo(companyId?: string) { } export function POSReceipt({ data, size = 'thermal', footerText, config }: POSReceiptProps) { - const { transaction: txn, company, location } = data const isThermal = size === 'thermal' if (!isThermal) { diff --git a/packages/backend/src/services/report.service.ts b/packages/backend/src/services/report.service.ts index 1fde1dd..0e52231 100644 --- a/packages/backend/src/services/report.service.ts +++ b/packages/backend/src/services/report.service.ts @@ -1,4 +1,4 @@ -import { eq, and, sql, gte, lt } from 'drizzle-orm' +import { eq, and, gte, lt } from 'drizzle-orm' import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js' import { transactions, drawerSessions, drawerAdjustments, registers } from '../db/schema/pos.js' import { locations } from '../db/schema/stores.js'