diff --git a/packages/admin/src/components/pos/pos-payment-dialog.tsx b/packages/admin/src/components/pos/pos-payment-dialog.tsx
index f4c7cf1..63c1339 100644
--- a/packages/admin/src/components/pos/pos-payment-dialog.tsx
+++ b/packages/admin/src/components/pos/pos-payment-dialog.tsx
@@ -63,6 +63,20 @@ export function POSPaymentDialog({ open, onOpenChange, paymentMethod, transactio
const QUICK_AMOUNTS = [1, 5, 10, 20, 50, 100]
+ // Fetch receipt config
+ interface AppConfigEntry { key: string; value: string | null }
+ const { data: configData } = useQuery({
+ queryKey: ['config'],
+ queryFn: () => api.get<{ data: AppConfigEntry[] }>('/v1/config'),
+ enabled: !!result?.id,
+ })
+ const receiptConfig = {
+ header: configData?.data?.find((c) => c.key === 'receipt_header')?.value || undefined,
+ footer: configData?.data?.find((c) => c.key === 'receipt_footer')?.value || undefined,
+ returnPolicy: configData?.data?.find((c) => c.key === 'receipt_return_policy')?.value || undefined,
+ social: configData?.data?.find((c) => c.key === 'receipt_social')?.value || undefined,
+ }
+
// Fetch full receipt data after completion
const { data: receiptData } = useQuery({
queryKey: ['pos', 'receipt', result?.id],
@@ -91,7 +105,7 @@ export function POSPaymentDialog({ open, onOpenChange, paymentMethod, transactio
diff --git a/packages/admin/src/components/pos/pos-receipt.tsx b/packages/admin/src/components/pos/pos-receipt.tsx
index d4f2d47..1d72266 100644
--- a/packages/admin/src/components/pos/pos-receipt.tsx
+++ b/packages/admin/src/components/pos/pos-receipt.tsx
@@ -45,10 +45,18 @@ interface ReceiptData {
}
}
+interface ReceiptConfig {
+ header?: string
+ footer?: string
+ returnPolicy?: string
+ social?: string
+}
+
interface POSReceiptProps {
data: ReceiptData
size?: 'thermal' | 'full'
footerText?: string
+ config?: ReceiptConfig
}
function useStoreLogo(companyId?: string) {
@@ -91,7 +99,7 @@ function useStoreLogo(companyId?: string) {
return logoSrc
}
-export function POSReceipt({ data, size = 'thermal', footerText }: POSReceiptProps) {
+export function POSReceipt({ data, size = 'thermal', footerText, config }: POSReceiptProps) {
const barcodeRef = useRef(null)
const { transaction: txn, company, location } = data
const isThermal = size === 'thermal'
@@ -146,6 +154,7 @@ export function POSReceipt({ data, size = 'thermal', footerText }: POSReceiptPro
>
)}
{phone && {phone}
}
+ {config?.header && {config.header}
}
{/* Transaction info */}
@@ -234,8 +243,14 @@ export function POSReceipt({ data, size = 'thermal', footerText }: POSReceiptPro
{/* Footer */}
- {footerText && (
- {footerText}
+ {(config?.footer || footerText) && (
+ {config?.footer || footerText}
+ )}
+ {config?.returnPolicy && (
+ {config.returnPolicy}
+ )}
+ {config?.social && (
+ {config.social}
)}
)
diff --git a/packages/admin/src/routes/_authenticated/settings.tsx b/packages/admin/src/routes/_authenticated/settings.tsx
index cd4ef97..54770f4 100644
--- a/packages/admin/src/routes/_authenticated/settings.tsx
+++ b/packages/admin/src/routes/_authenticated/settings.tsx
@@ -15,7 +15,8 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from
import { Switch } from '@/components/ui/switch'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { moduleListOptions, moduleMutations, moduleKeys } from '@/api/modules'
-import { Save, Plus, Trash2, MapPin, Building, ImageIcon, Blocks, Lock, Settings2 } from 'lucide-react'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { Save, Plus, Trash2, MapPin, Building, ImageIcon, Blocks, Lock, Settings2, Receipt } from 'lucide-react'
import { toast } from 'sonner'
interface StoreSettings {
@@ -118,6 +119,14 @@ function SettingsPage() {
Settings
+
+
+ General
+ Receipt
+
+
+
+
{/* Store Info */}
@@ -240,6 +249,13 @@ function SettingsPage() {
{/* App Configuration */}
+
+
+
+
+
+
+
)
}
@@ -376,6 +392,106 @@ function AppConfigCard() {
)
}
+const RECEIPT_FIELDS = [
+ { key: 'receipt_header', label: 'Header Text', placeholder: "e.g. San Antonio's Premier String Shop", multiline: false },
+ { key: 'receipt_footer', label: 'Footer Message', placeholder: 'e.g. Thank you for your business!', multiline: false },
+ { key: 'receipt_return_policy', label: 'Return Policy', placeholder: 'e.g. Returns accepted within 30 days with receipt.', multiline: true },
+ { key: 'receipt_social', label: 'Website / Social', placeholder: 'e.g. www.demostore.com | @demostore', multiline: false },
+]
+
+function ReceiptSettingsCard() {
+ const queryClient = useQueryClient()
+ const hasPermission = useAuthStore((s) => s.hasPermission)
+ const canEdit = hasPermission('settings.edit')
+
+ const { data: configData, isLoading } = useQuery(configOptions())
+ const configs = configData?.data ?? []
+
+ const [editing, setEditing] = useState(false)
+ const [fields, setFields] = useState>({})
+
+ function startEdit() {
+ const f: Record = {}
+ for (const rf of RECEIPT_FIELDS) {
+ f[rf.key] = configs.find((c) => c.key === rf.key)?.value ?? ''
+ }
+ setFields(f)
+ setEditing(true)
+ }
+
+ const saveMutation = useMutation({
+ mutationFn: async () => {
+ for (const rf of RECEIPT_FIELDS) {
+ await api.patch(`/v1/config/${rf.key}`, { value: fields[rf.key] || '' })
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['config'] })
+ toast.success('Receipt settings saved')
+ setEditing(false)
+ },
+ onError: (err) => toast.error(err.message),
+ })
+
+ return (
+
+
+
+ Receipt Customization
+
+ {canEdit && !editing && }
+ {editing && (
+
+
+
+
+ )}
+
+
+ {isLoading ? (
+
+ ) : editing ? (
+
+ {RECEIPT_FIELDS.map((rf) => (
+
+
+ {rf.multiline ? (
+
+ ))}
+
+ ) : (
+
+ {RECEIPT_FIELDS.map((rf) => {
+ const value = configs.find((c) => c.key === rf.key)?.value
+ return (
+
+ {rf.label}
+ {value || Not set}
+
+ )
+ })}
+
+ )}
+
+
+ )
+}
+
function LocationCard({ location }: { location: Location }) {
const queryClient = useQueryClient()
const [editing, setEditing] = useState(false)
diff --git a/packages/backend/src/db/migrations/0042_user-pin.sql b/packages/backend/src/db/migrations/0042_user-pin.sql
index 9db149d..457fb44 100644
--- a/packages/backend/src/db/migrations/0042_user-pin.sql
+++ b/packages/backend/src/db/migrations/0042_user-pin.sql
@@ -10,7 +10,11 @@ BEGIN
END LOOP;
END $$;
--- Seed POS lock timeout config
-INSERT INTO "app_config" ("key", "value", "description")
-VALUES ('pos_lock_timeout', '15', 'POS auto-lock timeout in minutes (0 to disable)')
+-- Seed POS config
+INSERT INTO "app_config" ("key", "value", "description") VALUES
+ ('pos_lock_timeout', '15', 'POS auto-lock timeout in minutes (0 to disable)'),
+ ('receipt_header', '', 'Text shown below logo on receipts'),
+ ('receipt_footer', '', 'Thank you message at bottom of receipt'),
+ ('receipt_return_policy', '', 'Return policy text on receipt (blank to hide)'),
+ ('receipt_social', '', 'Website or social media shown on receipt')
ON CONFLICT ("key") DO NOTHING;