profit-planet-frontend/src/app/admin/contract-management/components/companySettingsPanel.tsx
seaznCode bcc953edc1 iwd
2026-05-17 16:17:30 +02:00

274 lines
9.9 KiB
TypeScript

'use client'
import { useTranslation } from '../../../i18n/useTranslation'
import { useEffect, useRef, useState } from 'react'
import useContractManagement from '../hooks/useContractManagement'
const LOGO_ACCEPTED_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/svg+xml']
const MAX_LOGO_BYTES = 1024 * 1024
type CompanySettingsForm = {
company_name: string
company_street: string
company_postal_city: string
company_country: string
company_logo_base64: string | null
company_logo_mime_type: string | null
}
function fileToBase64Payload(file: File) {
return new Promise<{ base64: string; mimeType: string }>((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
const result = typeof reader.result === 'string' ? reader.result : ''
const match = result.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.+)$/)
if (!match) {
reject(new Error('invalid_data_url'))
return
}
resolve({ mimeType: match[1], base64: match[2] })
}
reader.onerror = () => reject(new Error('read_failed'))
reader.readAsDataURL(file)
})
}
export default function CompanySettingsPanel() {
const { t } = useTranslation()
const { getCompanySettings, updateCompanySettings } = useContractManagement()
const logoInputRef = useRef<HTMLInputElement | null>(null)
const [form, setForm] = useState<CompanySettingsForm>({
company_name: '',
company_street: '',
company_postal_city: '',
company_country: '',
company_logo_base64: null,
company_logo_mime_type: null,
})
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [saved, setSaved] = useState(false)
const [saveError, setSaveError] = useState<string>('')
const [logoError, setLogoError] = useState<string>('')
useEffect(() => {
getCompanySettings()
.then((data) => {
setForm({
company_name: data.company_name || '',
company_street: data.company_street || '',
company_postal_city: data.company_postal_city || '',
company_country: data.company_country || '',
company_logo_base64: data.company_logo_base64 || null,
company_logo_mime_type: data.company_logo_mime_type || null,
})
})
.catch(() => {})
.finally(() => setLoading(false))
}, [getCompanySettings])
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setForm((prev) => ({ ...prev, [name]: value }))
setSaved(false)
}
const handleLogoChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
setLogoError('')
if (!file) return
if (!LOGO_ACCEPTED_TYPES.includes(file.type)) {
setLogoError(t('autofix.k2bd38d5e'))
if (logoInputRef.current) logoInputRef.current.value = ''
return
}
if (file.size > MAX_LOGO_BYTES) {
setLogoError(t('autofix.k394b7f42'))
if (logoInputRef.current) logoInputRef.current.value = ''
return
}
try {
const { base64, mimeType } = await fileToBase64Payload(file)
setForm((prev) => ({
...prev,
company_logo_base64: base64,
company_logo_mime_type: mimeType,
}))
setSaved(false)
} catch {
setLogoError(t('autofix.k8a1d4c20'))
} finally {
if (logoInputRef.current) logoInputRef.current.value = ''
}
}
const handleRemoveLogo = () => {
setForm((prev) => ({
...prev,
company_logo_base64: null,
company_logo_mime_type: null,
}))
setLogoError('')
setSaved(false)
if (logoInputRef.current) logoInputRef.current.value = ''
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setSaving(true)
setSaved(false)
setSaveError('')
try {
await updateCompanySettings(form)
setSaved(true)
setTimeout(() => setSaved(false), 3000)
} catch {
setSaveError(t('autofix.k95a16b2b'))
} finally {
setSaving(false)
}
}
const logoPreviewSrc = form.company_logo_base64
? `data:${form.company_logo_mime_type || 'image/png'};base64,${form.company_logo_base64}`
: null
if (loading) {
return (
<div className="flex items-center gap-2 py-4 text-sm text-gray-500">
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-blue-900" />
{t('autofix.k81a1b900')}
</div>
)
}
return (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="rounded-2xl border border-slate-200 bg-slate-50/80 p-4 shadow-sm">
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div className="space-y-1">
<h3 className="text-lg font-semibold text-slate-900">{t('autofix.k0198ce13')}</h3>
<p className="text-sm text-slate-500">{t('autofix.k03d7361d')}</p>
<p className="text-xs text-slate-400">{t('autofix.k1c2b0975')}</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<input
ref={logoInputRef}
type="file"
accept={LOGO_ACCEPTED_TYPES.join(',')}
onChange={handleLogoChange}
className="hidden"
/>
<button
type="button"
onClick={() => logoInputRef.current?.click()}
className="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 transition hover:border-slate-400 hover:bg-slate-100"
>
{logoPreviewSrc ? t('autofix.k7d3f0e11') : t('autofix.k089f42a1')}
</button>
{logoPreviewSrc && (
<button
type="button"
onClick={handleRemoveLogo}
className="rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-semibold text-red-700 transition hover:bg-red-100"
>
{t('autofix.k0d8e2d01')}
</button>
)}
</div>
</div>
<div className="mt-4 rounded-2xl border border-dashed border-slate-300 bg-white p-4">
{logoPreviewSrc ? (
<img
src={logoPreviewSrc}
alt={t('autofix.k0198ce13')}
className="max-h-24 max-w-full object-contain"
/>
) : (
<div className="text-sm text-slate-500">{t('autofix.k432b8a12')}</div>
)}
</div>
{logoError && <div className="mt-3 text-sm font-medium text-red-600">{logoError}</div>}
</div>
<div>
<h3 className="mb-3 flex items-center gap-2 text-lg font-semibold text-slate-900">
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" /></svg>
{t('autofix.kaa8bbc8e')}
</h3>
<p className="mb-4 text-sm text-slate-500">{t('autofix.k15bea9bb')}</p>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<label htmlFor="company_name" className="mb-1 block text-sm font-medium text-gray-700">{t('autofix.k33918465')}</label>
<input
type="text"
id="company_name"
name="company_name"
value={form.company_name}
onChange={handleChange}
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-transparent focus:ring-2 focus:ring-blue-500"
placeholder={t('autofix.k91e69df1')}
/>
</div>
<div>
<label htmlFor="company_street" className="mb-1 block text-sm font-medium text-gray-700">Street</label>
<input
type="text"
id="company_street"
name="company_street"
value={form.company_street}
onChange={handleChange}
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-transparent focus:ring-2 focus:ring-blue-500"
placeholder={t('autofix.k81c7c2f2')}
/>
</div>
<div>
<label htmlFor="company_postal_city" className="mb-1 block text-sm font-medium text-gray-700">Postal Code &amp; City</label>
<input
type="text"
id="company_postal_city"
name="company_postal_city"
value={form.company_postal_city}
onChange={handleChange}
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-transparent focus:ring-2 focus:ring-blue-500"
placeholder={t('autofix.k93165aea')}
/>
</div>
<div>
<label htmlFor="company_country" className="mb-1 block text-sm font-medium text-gray-700">Country</label>
<input
type="text"
id="company_country"
name="company_country"
value={form.company_country}
onChange={handleChange}
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-transparent focus:ring-2 focus:ring-blue-500"
placeholder="Germany"
/>
</div>
</div>
</div>
{saveError && <div className="text-sm font-medium text-red-600">{saveError}</div>}
<div className="flex items-center gap-3">
<button
type="submit"
disabled={saving}
className={`rounded-lg px-5 py-2 text-sm font-semibold text-white transition-colors ${saving ? 'cursor-not-allowed bg-gray-400' : 'bg-blue-900 hover:bg-blue-800'}`}
>
{saving ? t('autofix.kac6cedc7') : 'Save'}
</button>
{saved && <span className="text-sm font-medium text-green-600">{t('autofix.ka29ac729')}</span>}
</div>
</form>
)
}