dev #21

Merged
Seazn merged 35 commits from dev into main 2026-05-21 17:34:44 +00:00
5 changed files with 201 additions and 120 deletions
Showing only changes of commit a5cf0745e3 - Show all commits

View File

@ -315,6 +315,78 @@ export default function AdminDashboardPage() {
<ArrowRightIcon className="h-5 w-5 text-blue-600 opacity-70 group-hover:opacity-100" /> <ArrowRightIcon className="h-5 w-5 text-blue-600 opacity-70 group-hover:opacity-100" />
</button> </button>
{/* User Verify */}
<button
type="button"
onClick={() => router.push('/admin/user-verify')}
className="group w-full flex items-center justify-between rounded-lg border border-rose-200 bg-rose-50 hover:bg-rose-100 px-4 py-4 transform transition-transform duration-200 hover:scale-[1.02] hover:shadow-md"
>
<div className="flex items-center gap-4">
<span className="inline-flex h-10 w-10 items-center justify-center rounded-md bg-rose-100 border border-rose-200 group-hover:animate-pulse">
<ExclamationTriangleIcon className="h-6 w-6 text-rose-600" />
</span>
<div className="text-left">
<div className="text-base font-semibold text-rose-900">User Verify</div>
<div className="text-xs text-rose-700">Review and verify user onboarding status</div>
</div>
</div>
<ArrowRightIcon className="h-5 w-5 text-rose-600 opacity-70 group-hover:opacity-100" />
</button>
{/* Finance Management */}
<button
type="button"
onClick={() => router.push('/admin/finance-management')}
className="group w-full flex items-center justify-between rounded-lg border border-emerald-200 bg-emerald-50 hover:bg-emerald-100 px-4 py-4 transform transition-transform duration-200 hover:scale-[1.02] hover:shadow-md"
>
<div className="flex items-center gap-4">
<span className="inline-flex h-10 w-10 items-center justify-center rounded-md bg-emerald-100 border border-emerald-200 group-hover:animate-pulse">
<BanknotesIcon className="h-6 w-6 text-emerald-600" />
</span>
<div className="text-left">
<div className="text-base font-semibold text-emerald-900">Finance Management</div>
<div className="text-xs text-emerald-700">Tax rates, billing settings and finance tools</div>
</div>
</div>
<ArrowRightIcon className="h-5 w-5 text-emerald-600 opacity-70 group-hover:opacity-100" />
</button>
{/* Pool Management */}
<button
type="button"
onClick={() => router.push('/admin/pool-management')}
className="group w-full flex items-center justify-between rounded-lg border border-cyan-200 bg-cyan-50 hover:bg-cyan-100 px-4 py-4 transform transition-transform duration-200 hover:scale-[1.02] hover:shadow-md"
>
<div className="flex items-center gap-4">
<span className="inline-flex h-10 w-10 items-center justify-center rounded-md bg-cyan-100 border border-cyan-200 group-hover:animate-pulse">
<ServerStackIcon className="h-6 w-6 text-cyan-600" />
</span>
<div className="text-left">
<div className="text-base font-semibold text-cyan-900">Pool Management</div>
<div className="text-xs text-cyan-700">Manage pool structures and assignments</div>
</div>
</div>
<ArrowRightIcon className="h-5 w-5 text-cyan-600 opacity-70 group-hover:opacity-100" />
</button>
{/* Affiliate Management */}
<button
type="button"
onClick={() => router.push('/admin/affiliate-management')}
className="group w-full flex items-center justify-between rounded-lg border border-violet-200 bg-violet-50 hover:bg-violet-100 px-4 py-4 transform transition-transform duration-200 hover:scale-[1.02] hover:shadow-md"
>
<div className="flex items-center gap-4">
<span className="inline-flex h-10 w-10 items-center justify-center rounded-md bg-violet-100 border border-violet-200 group-hover:animate-pulse">
<UsersIcon className="h-6 w-6 text-violet-600" />
</span>
<div className="text-left">
<div className="text-base font-semibold text-violet-900">Affiliate Management</div>
<div className="text-xs text-violet-700">Partner content and affiliate controls</div>
</div>
</div>
<ArrowRightIcon className="h-5 w-5 text-violet-600 opacity-70 group-hover:opacity-100" />
</button>
{/* News Management */} {/* News Management */}
<button <button
type="button" type="button"

View File

@ -33,6 +33,9 @@ export type SubscribeAboInput = {
invoiceCity?: string invoiceCity?: string
invoicePhone?: string invoicePhone?: string
invoiceEmail?: string invoiceEmail?: string
uidNumber?: string
atuNumber?: string
taxMode?: 'standard_vat' | 'reverse_charge'
signingCity?: string signingCity?: string
signatureDataUrl?: string signatureDataUrl?: string
// logged-in user id // logged-in user id
@ -99,6 +102,9 @@ export async function subscribeAbo(input: SubscribeAboInput) {
paymentMethod: input.paymentMethod || undefined, paymentMethod: input.paymentMethod || undefined,
invoiceByEmail: input.invoiceByEmail ?? false, invoiceByEmail: input.invoiceByEmail ?? false,
invoiceSameAsShipping: input.invoiceSameAsShipping ?? true, invoiceSameAsShipping: input.invoiceSameAsShipping ?? true,
uidNumber: input.uidNumber || undefined,
atuNumber: input.atuNumber || undefined,
taxMode: input.taxMode || undefined,
signingCity: input.signingCity || undefined, signingCity: input.signingCity || undefined,
signatureDataUrl: input.signatureDataUrl || undefined, signatureDataUrl: input.signatureDataUrl || undefined,
} }

View File

@ -4,7 +4,7 @@ import PageLayout from '../../components/PageLayout';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useActiveCoffees } from '../hooks/getActiveCoffees'; import { useActiveCoffees } from '../hooks/getActiveCoffees';
import { getStandardVatRate, getVatRates } from './hooks/getTaxRate'; import { getStandardVatRate, getVatRates } from './hooks/getTaxRate';
import { subscribeAbo } from './hooks/subscribeAbo'; import { subscribeAbo, type SubscribeAboInput } from './hooks/subscribeAbo';
import useAuthStore from '../../store/authStore' import useAuthStore from '../../store/authStore'
import { useShippingFees } from '../hooks/useShippingFees'; import { useShippingFees } from '../hooks/useShippingFees';
import { useAboContractTemplateHtml } from './hooks/useAboContractTemplateHtml' import { useAboContractTemplateHtml } from './hooks/useAboContractTemplateHtml'
@ -43,6 +43,24 @@ function hashString(value: string): number {
return hash >>> 0 return hash >>> 0
} }
const HOME_COUNTRY_CODE = 'AT'
function normalizeUid(value: unknown): string {
if (typeof value !== 'string') return ''
return value.replace(/\s+/g, '').toUpperCase()
}
function isLikelyValidUid(value: string): boolean {
return /^[A-Z]{2}[A-Z0-9]{4,14}$/.test(value)
}
function pickFirstString(...values: unknown[]): string {
for (const value of values) {
if (typeof value === 'string' && value.trim() !== '') return value.trim()
}
return ''
}
export default function SummaryPage() { export default function SummaryPage() {
const router = useRouter(); const router = useRouter();
const { coffees, loading, error } = useActiveCoffees(); const { coffees, loading, error } = useActiveCoffees();
@ -75,6 +93,7 @@ export default function SummaryPage() {
invoiceCity: '', invoiceCity: '',
invoicePhone: '', invoicePhone: '',
invoiceEmail: '', invoiceEmail: '',
uidNumber: '',
signingCity: '', signingCity: '',
}); });
const [showThanks, setShowThanks] = useState(false); const [showThanks, setShowThanks] = useState(false);
@ -90,12 +109,27 @@ export default function SummaryPage() {
const templateVariableNames = useMemo(() => extractTemplateVariables(contractHtml), [contractHtml]) const templateVariableNames = useMemo(() => extractTemplateVariables(contractHtml), [contractHtml])
const templateVariableNamesKey = useMemo(() => templateVariableNames.join('|'), [templateVariableNames]) const templateVariableNamesKey = useMemo(() => templateVariableNames.join('|'), [templateVariableNames])
const [contractVariables, setContractVariables] = useState<Record<string, string>>({}) const [contractVariables, setContractVariables] = useState<Record<string, string>>({})
const isCompanyCustomer = user?.userType === 'company' || user?.user_type === 'company'
const profileUidNumber = useMemo(() => normalizeUid(pickFirstString(
user?.uidNumber,
user?.uid_number,
user?.atuNumber,
user?.atu_number,
user?.companyProfile?.uid_number,
user?.companyProfile?.atu_number
)), [user])
const enteredUidNumber = useMemo(() => normalizeUid(form.uidNumber), [form.uidNumber])
const effectiveUidNumber = enteredUidNumber || profileUidNumber
const hasValidCompanyUid = isCompanyCustomer && isLikelyValidUid(effectiveUidNumber)
const isForeignInvoiceCountry = form.country.toUpperCase() !== HOME_COUNTRY_CODE
const isReverseCharge = isCompanyCustomer && hasValidCompanyUid && isForeignInvoiceCountry
// Auto-compute contract variables from form state for preview // Auto-compute contract variables from form state for preview
useEffect(() => { useEffect(() => {
if (!templateVariableNamesKey) return if (!templateVariableNamesKey) return
const fullName = `${form.firstName} ${form.lastName}`.trim() const fullName = `${form.firstName} ${form.lastName}`.trim()
const isCompany = user?.userType === 'company' || user?.user_type === 'company'
const invoiceSame = form.invoiceSameAsShipping const invoiceSame = form.invoiceSameAsShipping
const computed: Record<string, string> = { const computed: Record<string, string> = {
@ -103,8 +137,8 @@ export default function SummaryPage() {
currentDate: new Date().toLocaleDateString('de-AT', { day: '2-digit', month: '2-digit', year: 'numeric' }), currentDate: new Date().toLocaleDateString('de-AT', { day: '2-digit', month: '2-digit', year: 'numeric' }),
recipientName: fullName, recipientName: fullName,
recipientAddress: `${form.street}, ${form.postalCode} ${form.city}`.trim(), recipientAddress: `${form.street}, ${form.postalCode} ${form.city}`.trim(),
shippingCustomerClass: isCompany ? '' : 'checked', shippingCustomerClass: isCompanyCustomer ? '' : 'checked',
shippingCompanyClass: isCompany ? 'checked' : '', shippingCompanyClass: isCompanyCustomer ? 'checked' : '',
shippingFullName: fullName, shippingFullName: fullName,
shippingStreet: form.street, shippingStreet: form.street,
shippingPostalCode: form.postalCode, shippingPostalCode: form.postalCode,
@ -112,8 +146,8 @@ export default function SummaryPage() {
shippingPhone: form.phone, shippingPhone: form.phone,
shippingEmail: form.email, shippingEmail: form.email,
invoiceSameAsShippingMark: invoiceSame ? '✓' : '', invoiceSameAsShippingMark: invoiceSame ? '✓' : '',
invoiceCompanyClass: isCompany ? 'checked' : '', invoiceCompanyClass: isCompanyCustomer ? 'checked' : '',
invoiceCustomerClass: isCompany ? '' : 'checked', invoiceCustomerClass: isCompanyCustomer ? '' : 'checked',
invoiceFullName: invoiceSame ? fullName : form.invoiceFullName, invoiceFullName: invoiceSame ? fullName : form.invoiceFullName,
invoiceStreet: invoiceSame ? form.street : form.invoiceStreet, invoiceStreet: invoiceSame ? form.street : form.invoiceStreet,
invoicePostalCode: invoiceSame ? form.postalCode : form.invoicePostalCode, invoicePostalCode: invoiceSame ? form.postalCode : form.invoicePostalCode,
@ -122,10 +156,10 @@ export default function SummaryPage() {
invoiceEmail: invoiceSame ? form.email : form.invoiceEmail, invoiceEmail: invoiceSame ? form.email : form.invoiceEmail,
fnCheckedClass: '', fnCheckedClass: '',
fnNumber: '', fnNumber: '',
atuCheckedClass: '', atuCheckedClass: hasValidCompanyUid ? 'checked' : '',
atuNumber: '', atuNumber: effectiveUidNumber,
entrepreneurClass: isCompany ? 'checked' : '', entrepreneurClass: isCompanyCustomer ? 'checked' : '',
consumerClass: isCompany ? '' : 'checked', consumerClass: isCompanyCustomer ? '' : 'checked',
paymentSepaClass: form.paymentMethod === 'sepa' ? 'checked' : '', paymentSepaClass: form.paymentMethod === 'sepa' ? 'checked' : '',
paymentCardClass: form.paymentMethod === 'card' ? 'checked' : '', paymentCardClass: form.paymentMethod === 'card' ? 'checked' : '',
paymentSofortClass: form.paymentMethod === 'sofort' ? 'checked' : '', paymentSofortClass: form.paymentMethod === 'sofort' ? 'checked' : '',
@ -134,7 +168,7 @@ export default function SummaryPage() {
fullName, fullName,
} }
setContractVariables(computed) setContractVariables(computed)
}, [templateVariableNamesKey, form, user, signatureDataUrl]) }, [templateVariableNamesKey, form, signatureDataUrl, effectiveUidNumber, hasValidCompanyUid, isCompanyCustomer])
const populatedContractHtml = useMemo(() => { const populatedContractHtml = useMemo(() => {
if (!contractHtml) return null if (!contractHtml) return null
@ -458,8 +492,9 @@ export default function SummaryPage() {
[totalPrice, shippingFee] [totalPrice, shippingFee]
); );
const taxAmount = useMemo(() => totalPrice * taxRate, [totalPrice, taxRate]); const effectiveTaxRate = isReverseCharge ? 0 : taxRate
const taxAmountWithShipping = useMemo(() => netWithShipping * taxRate, [netWithShipping, taxRate]); const taxAmount = useMemo(() => totalPrice * effectiveTaxRate, [totalPrice, effectiveTaxRate]);
const taxAmountWithShipping = useMemo(() => netWithShipping * effectiveTaxRate, [netWithShipping, effectiveTaxRate]);
const totalWithTax = useMemo(() => netWithShipping + taxAmountWithShipping, [netWithShipping, taxAmountWithShipping]); const totalWithTax = useMemo(() => netWithShipping + taxAmountWithShipping, [netWithShipping, taxAmountWithShipping]);
const handleInput = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => { const handleInput = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
@ -478,23 +513,24 @@ export default function SummaryPage() {
return; return;
} }
const pick = (...values: any[]) => {
for (const value of values) {
if (typeof value === 'string' && value.trim() !== '') return value.trim();
}
return '';
};
setSubmitError(null); setSubmitError(null);
setForm(prev => ({ setForm(prev => ({
...prev, ...prev,
firstName: pick(user.firstName, user.firstname, user.givenName, user.first_name) || prev.firstName, firstName: pickFirstString(user.firstName, user.firstname, user.givenName, user.first_name) || prev.firstName,
lastName: pick(user.lastName, user.lastname, user.familyName, user.last_name) || prev.lastName, lastName: pickFirstString(user.lastName, user.lastname, user.familyName, user.last_name) || prev.lastName,
email: pick(user.email, user.mail) || prev.email, email: pickFirstString(user.email, user.mail) || prev.email,
street: pick(user.street, user.addressStreet, user.address?.street, user.address_line_1) || prev.street, street: pickFirstString(user.street, user.addressStreet, user.address?.street, user.address_line_1) || prev.street,
postalCode: pick(user.postalCode, user.zipCode, user.zip, user.addressPostalCode, user.address?.postalCode) || prev.postalCode, postalCode: pickFirstString(user.postalCode, user.zipCode, user.zip, user.addressPostalCode, user.address?.postalCode) || prev.postalCode,
city: pick(user.city, user.addressCity, user.town, user.address?.city) || prev.city, city: pickFirstString(user.city, user.addressCity, user.town, user.address?.city) || prev.city,
country: (pick(user.country, user.countryCode, user.addressCountry, user.address?.country) || prev.country).toUpperCase(), country: (pickFirstString(user.country, user.countryCode, user.addressCountry, user.address?.country) || prev.country).toUpperCase(),
uidNumber: normalizeUid(pickFirstString(
user.uidNumber,
user.uid_number,
user.atuNumber,
user.atu_number,
user.companyProfile?.uid_number,
user.companyProfile?.atu_number
) || prev.uidNumber),
})); }));
}; };
@ -544,7 +580,7 @@ export default function SummaryPage() {
setSubmitError(null) setSubmitError(null)
setSubmitLoading(true) setSubmitLoading(true)
try { try {
const payload = { const payload: SubscribeAboInput = {
items: selectedEntries.map(entry => ({ items: selectedEntries.map(entry => ({
coffeeId: entry.coffee.id, coffeeId: entry.coffee.id,
quantity: Math.round(entry.quantity / 10), // packs quantity: Math.round(entry.quantity / 10), // packs
@ -576,6 +612,9 @@ export default function SummaryPage() {
} : {}), } : {}),
signingCity: form.signingCity.trim() || undefined, signingCity: form.signingCity.trim() || undefined,
signatureDataUrl: signatureDataUrl || undefined, signatureDataUrl: signatureDataUrl || undefined,
uidNumber: effectiveUidNumber || undefined,
atuNumber: effectiveUidNumber || undefined,
taxMode: isReverseCharge ? 'reverse_charge' : 'standard_vat',
referred_by: typeof currentUserId === 'number' ? currentUserId : undefined, referred_by: typeof currentUserId === 'number' ? currentUserId : undefined,
} }
console.info('[SummaryPage] subscribeAbo payload:', payload) console.info('[SummaryPage] subscribeAbo payload:', payload)
@ -730,10 +769,30 @@ export default function SummaryPage() {
{/* Invoice address */} {/* Invoice address */}
<div className="mt-6 border-t border-gray-200 pt-4"> <div className="mt-6 border-t border-gray-200 pt-4">
<h3 className="text-base font-semibold text-gray-900 mb-3">Invoice address</h3> <h3 className="text-base font-semibold text-gray-900 mb-3">Invoice address</h3>
{isCompanyCustomer && (
<div className="mb-3 rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-900">
Unternehmer mit gueltiger UID und Rechnungsland ausserhalb von {HOME_COUNTRY_CODE} werden per Reverse Charge ohne ausgewiesene MwSt verrechnet.
</div>
)}
<label className="flex items-center gap-2 text-sm mb-3"> <label className="flex items-center gap-2 text-sm mb-3">
<input type="checkbox" name="invoiceSameAsShipping" checked={form.invoiceSameAsShipping} onChange={handleCheckbox} className="accent-[#1C2B4A]" /> <input type="checkbox" name="invoiceSameAsShipping" checked={form.invoiceSameAsShipping} onChange={handleCheckbox} className="accent-[#1C2B4A]" />
Same as shipping address Same as shipping address
</label> </label>
{isCompanyCustomer && (
<div className="mb-4">
<label className="block text-sm font-medium mb-1">UID Number (optional)</label>
<input
name="uidNumber"
value={form.uidNumber}
onChange={handleInput}
placeholder="z.B. SI12345678"
className="w-full rounded border px-3 py-2 bg-white border-gray-300 uppercase focus:outline-none focus:ring-2 focus:ring-[#1C2B4A]"
/>
<p className="mt-1 text-xs text-gray-600">
Ohne gueltige UID wird die Rechnung mit normaler MwSt erstellt.
</p>
</div>
)}
{!form.invoiceSameAsShipping && ( {!form.invoiceSameAsShipping && (
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
<div className="sm:col-span-2"> <div className="sm:col-span-2">
@ -902,13 +961,18 @@ export default function SummaryPage() {
<span className="text-lg font-extrabold tracking-tight text-[#1C2B4A]">{netWithShipping.toFixed(2)}</span> <span className="text-lg font-extrabold tracking-tight text-[#1C2B4A]">{netWithShipping.toFixed(2)}</span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-sm">Tax ({(taxRate * 100).toFixed(1)}%)</span> <span className="text-sm">{isReverseCharge ? 'Tax (Reverse Charge)' : `Tax (${(effectiveTaxRate * 100).toFixed(1)}%)`}</span>
<span className="text-sm font-medium">{taxAmountWithShipping.toFixed(2)}</span> <span className="text-sm font-medium">{taxAmountWithShipping.toFixed(2)}</span>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-semibold">Total incl. tax</span> <span className="text-sm font-semibold">Total incl. tax</span>
<span className="text-xl font-extrabold text-[#1C2B4A]">{totalWithTax.toFixed(2)}</span> <span className="text-xl font-extrabold text-[#1C2B4A]">{totalWithTax.toFixed(2)}</span>
</div> </div>
{isReverseCharge && (
<div className="mt-2 rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-900">
Reverse Charge aktiv: gueltige UID und auslaendisches Rechnungsland erkannt.
</div>
)}
{/* Validation summary (refined design) */} {/* Validation summary (refined design) */}
<div className="mt-2 text-xs text-gray-700"> <div className="mt-2 text-xs text-gray-700">
Selected: {totalCapsules} capsules ({totalPacks} packs of 10). Target: {selectedPlanCapsules} capsules ({requiredPacks} packs). Selected: {totalCapsules} capsules ({totalPacks} packs of 10). Target: {selectedPlanCapsules} capsules ({requiredPacks} packs).

View File

@ -761,88 +761,11 @@ export default function Header({ setGlobalLoggingOut }: HeaderProps) {
onClick={() => { router.push('/admin'); setMobileMenuOpen(false); }} onClick={() => { router.push('/admin'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white" className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
> >
Dashboard Admin Dashboard
</button> </button>
<button <p className="px-2 py-1 text-xs text-slate-500 dark:text-indigo-100/70">
onClick={() => { router.push('/admin/dashboard-management'); setMobileMenuOpen(false); }} Open the dashboard to access all admin modules via icon panels.
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white" </p>
>
Dashboard Management
</button>
<button
onClick={() => { router.push('/admin/user-verify'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
User Verify
</button>
<button
onClick={() => { router.push('/admin/user-management'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
User Management
</button>
{DISPLAY_MATRIX && (
<button
onClick={() => { router.push('/admin/matrix-management'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
Matrix Management
</button>
)}
<button
onClick={() => { router.push('/admin/contract-management'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
Contract Management
</button>
{DISPLAY_ABONEMENTS && (
<>
<button
onClick={() => { router.push('/admin/subscriptions'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
Coffee Management
</button>
<button
onClick={() => { router.push('/admin/finance-management'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
Finance Management
</button>
</>
)}
{DISPLAY_POOLS && (
<button
onClick={() => { router.push('/admin/pool-management'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
Pool Management
</button>
)}
<button
onClick={() => { router.push('/admin/affiliate-management'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
Affiliate Management
</button>
{DISPLAY_NEWS && (
<button
onClick={() => { router.push('/admin/news-management'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
News Management
</button>
)}
{/* ADDED: Dev Management in hamburger admin nav */}
{isAdminOrSuper && (
<button
onClick={() => { router.push('/admin/dev-management'); setMobileMenuOpen(false); }}
className="w-full text-left rounded-lg px-2 py-1.5 text-slate-800 hover:bg-indigo-50 hover:text-slate-900 transition-colors dark:text-indigo-50 dark:hover:bg-white/10 dark:hover:text-white"
>
Dev Management
</button>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -15,7 +15,8 @@ interface CompanyProfileData {
companyPhone: string companyPhone: string
contactPersonName: string contactPersonName: string
contactPersonPhone: string contactPersonPhone: string
vatNumber: string registrationNumber: string
uidNumber: string
street: string street: string
postalCode: string postalCode: string
city: string city: string
@ -44,7 +45,8 @@ const init: CompanyProfileData = {
companyPhone: '', companyPhone: '',
contactPersonName: '', contactPersonName: '',
contactPersonPhone: '', contactPersonPhone: '',
vatNumber: '', registrationNumber: '',
uidNumber: '',
street: '', street: '',
postalCode: '', postalCode: '',
city: '', city: '',
@ -216,7 +218,8 @@ export default function CompanyAdditionalInformationPage() {
companyPhone: profile?.phone || me?.companyPhone || prev.companyPhone, companyPhone: profile?.phone || me?.companyPhone || prev.companyPhone,
contactPersonName: profile?.contact_person_name || me?.contactPersonName || prev.contactPersonName, contactPersonName: profile?.contact_person_name || me?.contactPersonName || prev.contactPersonName,
contactPersonPhone: profile?.contact_person_phone || me?.contactPersonPhone || prev.contactPersonPhone, contactPersonPhone: profile?.contact_person_phone || me?.contactPersonPhone || prev.contactPersonPhone,
vatNumber: profile?.registration_number || prev.vatNumber, registrationNumber: profile?.registration_number || prev.registrationNumber,
uidNumber: profile?.uid_number || profile?.atu_number || prev.uidNumber,
street: profile?.address || prev.street, street: profile?.address || prev.street,
postalCode: profile?.zip_code || prev.postalCode, postalCode: profile?.zip_code || prev.postalCode,
city: profile?.city || prev.city, city: profile?.city || prev.city,
@ -281,7 +284,7 @@ export default function CompanyAdditionalInformationPage() {
const validate = () => { const validate = () => {
const required: (keyof CompanyProfileData)[] = [ const required: (keyof CompanyProfileData)[] = [
'companyName','companyEmail','companyPhone','contactPersonName','contactPersonPhone', 'companyName','companyEmail','companyPhone','contactPersonName','contactPersonPhone',
'vatNumber','street','postalCode','city','country','accountHolder','iban' 'street','postalCode','city','country','accountHolder','iban'
] ]
for (const k of required) { for (const k of required) {
if (!form[k].trim()) { if (!form[k].trim()) {
@ -414,7 +417,9 @@ export default function CompanyAdditionalInformationPage() {
zip_code: form.postalCode, // Backend expects 'zip_code' zip_code: form.postalCode, // Backend expects 'zip_code'
city: form.city, city: form.city,
country: form.country, country: form.country,
registrationNumber: form.vatNumber, // Map VAT number to registration number registrationNumber: form.registrationNumber || undefined,
uidNumber: form.uidNumber || undefined,
atuNumber: form.uidNumber || undefined,
businessType: 'company', // Default business type businessType: 'company', // Default business type
branch: null, // Not collected in form, set to null branch: null, // Not collected in form, set to null
numberOfEmployees: null, // Not collected in form, set to null numberOfEmployees: null, // Not collected in form, set to null
@ -580,15 +585,26 @@ export default function CompanyAdditionalInformationPage() {
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">
VAT / Reg No. * Registration Number (optional)
</label> </label>
<input <input
name="vatNumber" name="registrationNumber"
value={form.vatNumber} value={form.registrationNumber}
onChange={handleChange} onChange={handleChange}
placeholder="e.g. DE123456789" placeholder="e.g. FN123456a"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
UID Number (optional)
</label>
<input
name="uidNumber"
value={form.uidNumber}
onChange={handleChange}
placeholder="e.g. ATU12345678"
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent"
required
/> />
</div> </div>
<div className="sm:col-span-2 lg:col-span-3"> <div className="sm:col-span-2 lg:col-span-3">