feat: add custom signature image and signer details support to invoices and settings
This commit is contained in:
@@ -62,7 +62,7 @@
|
||||
<div class="mb-4" x-data="{
|
||||
uploading: false,
|
||||
errorMsg: '',
|
||||
logoUrl: '{{ $settings->header_logo ? asset('storage/' . $settings->header_logo) : '' }}',
|
||||
logoUrl: '{{ $settings->header_logo ? '/storage/' . $settings->header_logo : '' }}',
|
||||
|
||||
async pickFile(input) {
|
||||
if (!input.files.length) return;
|
||||
@@ -72,7 +72,7 @@
|
||||
fd.append('header_logo', input.files[0]);
|
||||
fd.append('_token', document.querySelector('meta[name=csrf-token]').content);
|
||||
try {
|
||||
const res = await fetch('{{ route('invoice-settings.upload-logo') }}', { method: 'POST', body: fd });
|
||||
const res = await fetch('{{ route('invoice-settings.upload-logo', [], false) }}', { method: 'POST', body: fd });
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
this.logoUrl = data.url + '?t=' + Date.now();
|
||||
@@ -91,7 +91,7 @@
|
||||
const fd = new FormData();
|
||||
fd.append('_token', document.querySelector('meta[name=csrf-token]').content);
|
||||
fd.append('_method', 'DELETE');
|
||||
await fetch('{{ route('invoice-settings.delete-logo') }}', { method: 'POST', body: fd });
|
||||
await fetch('{{ route('invoice-settings.delete-logo', [], false) }}', { method: 'POST', body: fd });
|
||||
this.logoUrl = '';
|
||||
}
|
||||
}">
|
||||
@@ -205,6 +205,82 @@
|
||||
<textarea name="default_notes" rows="2" class="input-soft text-sm resize-none"
|
||||
placeholder="e.g. Payment due within 14 days from invoice date.">{{ old('default_notes', $settings->default_notes) }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-2 mt-2 pt-4 border-t border-gray-100">
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Default Signature Image</label>
|
||||
<p class="text-xs text-gray-400 mb-3">Upload your signature here so it appears automatically on all new invoices.</p>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Signer Name</label>
|
||||
<input type="text" name="signer_name" placeholder="e.g. {{ $settings->header_name ?: 'John Doe' }}" value="{{ old('signer_name', $settings->signer_name) }}" class="input-soft text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-gray-600 mb-1">Signer Title</label>
|
||||
<input type="text" name="signer_title" placeholder="e.g. {{ $settings->header_title ?: 'Manager' }}" value="{{ old('signer_title', $settings->signer_title) }}" class="input-soft text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div x-data="{
|
||||
uploading: false,
|
||||
errorMsg: '',
|
||||
signatureUrl: '{{ $settings->signature_image ? '/storage/' . $settings->signature_image : '' }}',
|
||||
|
||||
async pickFile(input) {
|
||||
if (!input.files.length) return;
|
||||
this.uploading = true;
|
||||
this.errorMsg = '';
|
||||
const fd = new FormData();
|
||||
fd.append('signature_image', input.files[0]);
|
||||
fd.append('_token', document.querySelector('meta[name=csrf-token]').content);
|
||||
try {
|
||||
const res = await fetch('{{ route('invoice-settings.upload-signature', [], false) }}', { method: 'POST', body: fd });
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
this.signatureUrl = data.url + '?t=' + Date.now();
|
||||
} else {
|
||||
this.errorMsg = data.message ?? 'Upload failed.';
|
||||
}
|
||||
} catch {
|
||||
this.errorMsg = 'Network error. Please try again.';
|
||||
}
|
||||
this.uploading = false;
|
||||
input.value = '';
|
||||
},
|
||||
|
||||
async removeSignature() {
|
||||
if (!confirm('Remove this signature?')) return;
|
||||
const fd = new FormData();
|
||||
fd.append('_token', document.querySelector('meta[name=csrf-token]').content);
|
||||
fd.append('_method', 'DELETE');
|
||||
await fetch('{{ route('invoice-settings.delete-signature', [], false) }}', { method: 'POST', body: fd });
|
||||
this.signatureUrl = '';
|
||||
}
|
||||
}">
|
||||
|
||||
<template x-if="signatureUrl">
|
||||
<div class="flex items-center gap-4 mb-3 p-3 bg-white rounded-xl border border-pink-100">
|
||||
<img :src="signatureUrl" alt="Signature preview" class="h-16 object-contain">
|
||||
<div>
|
||||
<button type="button" @click="removeSignature()" class="text-xs px-3 py-1.5 rounded-full border border-red-200 text-red-400 hover:bg-red-50">
|
||||
🗑️ Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="relative">
|
||||
<input type="file" accept="image/*" @change="pickFile($event.target)" class="w-full text-sm text-gray-600
|
||||
file:mr-3 file:py-1.5 file:px-4
|
||||
file:rounded-full file:border-0
|
||||
file:text-xs file:font-semibold
|
||||
file:bg-pink-50 file:text-pink-600
|
||||
hover:file:bg-pink-100 cursor-pointer" :disabled="uploading">
|
||||
<span x-show="uploading" class="text-xs text-pink-500 mt-1 block">Uploading…</span>
|
||||
<span x-show="errorMsg" x-text="errorMsg" class="text-xs text-red-500 mt-1 block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user