From b164f73b43bba93df37f871f17d1648169f43613 Mon Sep 17 00:00:00 2001 From: DeathKaioken Date: Wed, 18 Feb 2026 11:17:07 +0100 Subject: [PATCH] feat: abo --- .../summary/hooks/subscribeAbo.ts | 20 +- src/app/coffee-abonnements/summary/page.tsx | 110 +++++++++- .../profile/components/financeInvoices.tsx | 189 ++++++++++++++++++ src/app/profile/components/userAbo.tsx | 46 +++-- src/app/profile/hooks/getAbo.ts | 79 ++++---- src/app/profile/hooks/getAboInvoices.ts | 107 ++++++++++ src/app/profile/page.tsx | 6 +- 7 files changed, 483 insertions(+), 74 deletions(-) create mode 100644 src/app/profile/components/financeInvoices.tsx create mode 100644 src/app/profile/hooks/getAboInvoices.ts diff --git a/src/app/coffee-abonnements/summary/hooks/subscribeAbo.ts b/src/app/coffee-abonnements/summary/hooks/subscribeAbo.ts index 35b949d..203f44e 100644 --- a/src/app/coffee-abonnements/summary/hooks/subscribeAbo.ts +++ b/src/app/coffee-abonnements/summary/hooks/subscribeAbo.ts @@ -7,6 +7,7 @@ export type SubscribeAboInput = { billing_interval?: string interval_count?: number is_auto_renew?: boolean + is_for_self?: boolean target_user_id?: number recipient_name?: string recipient_email?: string @@ -22,7 +23,7 @@ export type SubscribeAboInput = { frequency?: string startDate?: string // NEW: logged-in user id - referred_by?: number + referred_by?: number | string } type Abonement = any @@ -41,13 +42,13 @@ export async function subscribeAbo(input: SubscribeAboInput) { const hasItems = Array.isArray(input.items) && input.items.length > 0 if (!hasItems && !input.coffeeId) throw new Error('coffeeId is required') - const hasRecipientFields = !!(input.recipient_name || input.recipient_email || input.recipient_notes) - if (hasRecipientFields && !input.recipient_name) { - throw new Error('recipient_name is required when gifting to a non-account recipient.') + const isForSelf = input.is_for_self ?? true + if (!isForSelf && (!input.recipient_email || input.recipient_email.trim() === '')) { + throw new Error('recipient_email is required when subscription is for someone else.') } // NEW: validate customer fields (required in UI) - const requiredFields = ['firstName','lastName','email','street','postalCode','city','country','frequency','startDate'] as const + const requiredFields = ['firstName','lastName','email','street','postalCode','city','country','frequency'] as const const missing = requiredFields.filter(k => { const v = (input as any)[k] return typeof v !== 'string' || v.trim() === '' @@ -60,6 +61,7 @@ export async function subscribeAbo(input: SubscribeAboInput) { billing_interval: input.billing_interval ?? 'month', interval_count: input.interval_count ?? 1, is_auto_renew: input.is_auto_renew ?? true, + is_for_self: isForSelf, // NEW: include customer fields firstName: input.firstName, lastName: input.lastName, @@ -69,7 +71,7 @@ export async function subscribeAbo(input: SubscribeAboInput) { city: input.city, country: input.country?.toUpperCase?.() ?? input.country, frequency: input.frequency, - startDate: input.startDate, + startDate: input.startDate || undefined, } if (hasItems) { body.items = input.items!.map(i => ({ @@ -88,9 +90,9 @@ export async function subscribeAbo(input: SubscribeAboInput) { } // NEW: always include available recipient fields and target_user_id when provided if (input.target_user_id != null) body.target_user_id = input.target_user_id - if (input.recipient_name) body.recipient_name = input.recipient_name - if (input.recipient_email) body.recipient_email = input.recipient_email - if (input.recipient_notes) body.recipient_notes = input.recipient_notes + if (!isForSelf && input.recipient_email) body.recipient_email = input.recipient_email + if (!isForSelf && input.recipient_name) body.recipient_name = input.recipient_name + if (!isForSelf && input.recipient_notes) body.recipient_notes = input.recipient_notes // NEW: always include referred_by if provided if (input.referred_by != null) body.referred_by = input.referred_by diff --git a/src/app/coffee-abonnements/summary/page.tsx b/src/app/coffee-abonnements/summary/page.tsx index 721f2fb..540f699 100644 --- a/src/app/coffee-abonnements/summary/page.tsx +++ b/src/app/coffee-abonnements/summary/page.tsx @@ -13,6 +13,7 @@ export default function SummaryPage() { const user = useAuthStore(state => state.user) const [selections, setSelections] = useState>({}); const [selectedPlanCapsules, setSelectedPlanCapsules] = useState<60 | 120>(120); + const [isForSelf, setIsForSelf] = useState(true); const [form, setForm] = useState({ firstName: '', lastName: '', @@ -22,7 +23,10 @@ export default function SummaryPage() { city: '', country: 'DE', frequency: 'monatlich', - startDate: '' + startDate: '', + recipientEmail: '', + recipientName: '', + recipientNotes: '', }); const [showThanks, setShowThanks] = useState(false); const [confetti, setConfetti] = useState<{ left: number; delay: number; color: string }[]>([]); @@ -142,6 +146,11 @@ export default function SummaryPage() { setForm(prev => ({ ...prev, [name]: value })); }; + const handleRecipientNotes = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setForm(prev => ({ ...prev, [name]: value })); + }; + const fillFromLoggedInData = () => { if (!user) { setSubmitError('No logged-in user data found to fill the fields.'); @@ -168,10 +177,25 @@ export default function SummaryPage() { })); }; + const requiredSelfFields: Array = [ + 'firstName', + 'lastName', + 'email', + 'street', + 'postalCode', + 'city', + 'country', + 'frequency', + ] + + const hasRequiredSelfFields = requiredSelfFields.every(k => form[k].trim() !== '') + const hasRequiredGiftFields = isForSelf || form.recipientEmail.trim() !== '' + const canSubmit = selectedEntries.length > 0 && totalPacks === requiredPacks && - Object.values(form).every(v => (typeof v === 'string' ? v.trim() !== '' : true)); + hasRequiredSelfFields && + hasRequiredGiftFields; const backToSelection = () => router.push('/coffee-abonnements'); @@ -182,6 +206,11 @@ export default function SummaryPage() { setSubmitError(`Order must contain exactly ${requiredPacks} packs (${selectedPlanCapsules} capsules).`) return } + if (!isForSelf && !form.recipientEmail.trim()) { + setSubmitError('Recipient email is required when the subscription is for someone else.') + return + } + setSubmitError(null) setSubmitLoading(true) try { @@ -193,6 +222,7 @@ export default function SummaryPage() { billing_interval: 'month', interval_count: 1, is_auto_renew: true, + is_for_self: isForSelf, // NEW: pass customer fields firstName: form.firstName.trim(), lastName: form.lastName.trim(), @@ -202,7 +232,10 @@ export default function SummaryPage() { city: form.city.trim(), country: form.country.trim(), frequency: form.frequency.trim(), - startDate: form.startDate.trim(), + startDate: form.startDate.trim() || undefined, + recipient_email: isForSelf ? undefined : form.recipientEmail.trim(), + recipient_name: isForSelf ? undefined : (form.recipientName.trim() || undefined), + recipient_notes: isForSelf ? undefined : (form.recipientNotes.trim() || undefined), // NEW: always include referred_by if available referred_by: typeof currentUserId === 'number' ? currentUserId : undefined, } @@ -294,6 +327,30 @@ export default function SummaryPage() { > Fill fields with logged in data +
+ + +
{/* inputs translated */}
@@ -337,9 +394,42 @@ export default function SummaryPage() {
- +
+ {!isForSelf && ( + <> +
+ + +
+
+ + +
+
+ +