feat: add business identity settings for invoice branding

This commit is contained in:
Your Name
2026-02-18 13:34:44 +02:00
parent 3cadb42f8b
commit 06dc063125

View File

@@ -26,6 +26,8 @@
<!-- Content pane -->
<div class="flex-1 p-6 overflow-y-auto">
<Transition name="fade" mode="out-in" :duration="{ enter: 200, leave: 150 }">
<div :key="activeTab">
<!-- General -->
<div v-if="activeTab === 'general'">
<h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">General</h2>
@@ -380,6 +382,85 @@
</div>
</div>
</div>
<!-- Divider -->
<div class="border-t border-border-subtle mt-5 pt-5" />
<!-- Business Identity -->
<h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium mb-4">Business Identity</h3>
<p class="text-[0.6875rem] text-text-tertiary mb-4">This information appears on your invoices.</p>
<div class="space-y-4 max-w-md">
<div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Business Name</label>
<input
v-model="businessName"
type="text"
class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
placeholder="Your Company Name"
@change="saveBusinessSettings"
/>
</div>
<div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Address</label>
<textarea
v-model="businessAddress"
rows="3"
class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible resize-none"
placeholder="123 Business St&#10;City, State ZIP"
@change="saveBusinessSettings"
></textarea>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Email</label>
<input
v-model="businessEmail"
type="email"
class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
placeholder="billing@company.com"
@change="saveBusinessSettings"
/>
</div>
<div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Phone</label>
<input
v-model="businessPhone"
type="tel"
class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
placeholder="(555) 123-4567"
@change="saveBusinessSettings"
/>
</div>
</div>
<div>
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Logo</label>
<div class="flex items-center gap-4">
<div
v-if="businessLogo"
class="w-20 h-12 border border-border-subtle rounded flex items-center justify-center overflow-hidden bg-white"
>
<img :src="businessLogo" class="max-w-full max-h-full object-contain" />
</div>
<div class="flex gap-2">
<button
@click="uploadLogo"
class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors"
>
{{ businessLogo ? 'Change' : 'Upload' }}
</button>
<button
v-if="businessLogo"
@click="removeLogo"
class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-status-error rounded-lg hover:bg-status-error/10 transition-colors"
>
Remove
</button>
</div>
</div>
<p class="text-[0.625rem] text-text-tertiary mt-1">PNG or JPG, max 200x80px. Appears on invoice header.</p>
</div>
</div>
</div>
<!-- Data -->
@@ -468,6 +549,8 @@
</div>
</div>
</div>
</div>
</Transition>
</div>
<!-- Clear Data Confirmation Dialog -->
@@ -554,6 +637,13 @@ const roundingEnabled = ref(false)
const roundingIncrement = ref(15)
const roundingMethod = ref('nearest')
// Business identity settings
const businessName = ref('')
const businessAddress = ref('')
const businessEmail = ref('')
const businessPhone = ref('')
const businessLogo = ref('')
const roundingIncrements = [
{ value: 1, label: '1 minute' },
{ value: 5, label: '5 minutes' },
@@ -701,6 +791,40 @@ async function saveRoundingSettings() {
await settingsStore.updateSetting('rounding_method', roundingMethod.value)
}
// Save business identity settings
async function saveBusinessSettings() {
await settingsStore.updateSetting('business_name', businessName.value)
await settingsStore.updateSetting('business_address', businessAddress.value)
await settingsStore.updateSetting('business_email', businessEmail.value)
await settingsStore.updateSetting('business_phone', businessPhone.value)
}
async function uploadLogo() {
try {
const { open } = await import('@tauri-apps/plugin-dialog')
const selected = await open({
filters: [{ name: 'Images', extensions: ['png', 'jpg', 'jpeg'] }]
})
if (!selected) return
const { readFile } = await import('@tauri-apps/plugin-fs')
const bytes = await readFile(selected as string)
const ext = (selected as string).toLowerCase().endsWith('.png') ? 'png' : 'jpeg'
const base64 = btoa(String.fromCharCode(...new Uint8Array(bytes)))
const dataUrl = `data:image/${ext};base64,${base64}`
businessLogo.value = dataUrl
await settingsStore.updateSetting('business_logo', dataUrl)
} catch (e) {
console.error('Failed to upload logo:', e)
toastStore.error('Failed to upload logo')
}
}
async function removeLogo() {
businessLogo.value = ''
await settingsStore.updateSetting('business_logo', '')
}
// Import file handling
async function handleImportFile() {
try {
@@ -816,5 +940,10 @@ onMounted(async () => {
roundingEnabled.value = settingsStore.settings.rounding_enabled === 'true'
roundingIncrement.value = parseInt(settingsStore.settings.rounding_increment) || 15
roundingMethod.value = settingsStore.settings.rounding_method || 'nearest'
businessName.value = settingsStore.settings.business_name || ''
businessAddress.value = settingsStore.settings.business_address || ''
businessEmail.value = settingsStore.settings.business_email || ''
businessPhone.value = settingsStore.settings.business_phone || ''
businessLogo.value = settingsStore.settings.business_logo || ''
})
</script>