feat: add business identity settings for invoice branding
This commit is contained in:
@@ -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 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>
|
||||
|
||||
Reference in New Issue
Block a user