'use client' import React, { useEffect } from 'react' import { useRouter } from 'next/navigation' import useAuthStore from '../../store/authStore' import PageLayout from '../../components/PageLayout' import BlueBlurryBackground from '../../components/background/blueblurry' import { useMyAbonements } from '../hooks/getAbo' import FinanceInvoices from '../components/financeInvoices' import { useActiveCoffees } from '../../coffee-abonnements/hooks/getActiveCoffees' import { changeSubscriptionStatus, editSubscriptionContent } from '../hooks/editAbo' import ConfirmActionModal from '../../components/modals/ConfirmActionModal' type UiLifecycleStatus = 'issued' | 'ongoing' | 'finished' | 'pause' | 'cancelled' const normalizeSubscriptionStatus = (rawStatus?: string | null): UiLifecycleStatus => { const status = (rawStatus || '').toLowerCase() if (status === 'pause' || status === 'paused') return 'pause' if (status === 'cancelled' || status === 'canceled') return 'cancelled' if (status === 'finished' || status === 'expired') return 'finished' if (status === 'issued') return 'issued' return 'ongoing' } const statusBadgeClass = (status: UiLifecycleStatus) => { if (status === 'ongoing') return 'bg-green-100 text-green-800' if (status === 'pause') return 'bg-amber-100 text-amber-800' if (status === 'cancelled') return 'bg-red-100 text-red-700' if (status === 'finished') return 'bg-gray-200 text-gray-700' return 'bg-blue-100 text-blue-800' } const displayStatus = (status: UiLifecycleStatus) => status === 'pause' ? 'Pause' : status === 'cancelled' ? 'Cancelled' : status === 'ongoing' ? 'Ongoing' : status === 'finished' ? 'Finished' : 'Issued' const formatDate = (value?: string | null) => { if (!value) return '—' const date = new Date(value) return Number.isNaN(date.getTime()) ? '—' : date.toLocaleDateString('de-DE') } const formatMoney = (value?: string | number | null, currency?: string | null) => { if (value == null || value === '') return '—' const amount = typeof value === 'string' ? Number(value) : value if (!Number.isFinite(Number(amount))) return String(value) return new Intl.NumberFormat('de-DE', { style: 'currency', currency: currency || 'EUR' }).format(Number(amount)) } export default function ProfileSubscriptionsPage() { const router = useRouter() const user = useAuthStore(state => state.user) const isAuthReady = useAuthStore(state => state.isAuthReady) const [hasHydrated, setHasHydrated] = React.useState(false) const [selectedAboId, setSelectedAboId] = React.useState(null) const [refreshKey, setRefreshKey] = React.useState(0) const [editingContent, setEditingContent] = React.useState(false) const [draftItems, setDraftItems] = React.useState>({}) const [savingContent, setSavingContent] = React.useState(false) const [contentError, setContentError] = React.useState(null) const [statusBusy, setStatusBusy] = React.useState(false) const [statusError, setStatusError] = React.useState(null) const [statusConfirmTarget, setStatusConfirmTarget] = React.useState<'ongoing' | 'pause' | 'cancelled' | null>(null) const { data: subscriptions, loading, error } = useMyAbonements(refreshKey) const { coffees, loading: coffeesLoading, error: coffeesError } = useActiveCoffees() useEffect(() => { setHasHydrated(true) }, []) useEffect(() => { if (!hasHydrated || !isAuthReady) return if (!user) router.replace('/login') }, [hasHydrated, isAuthReady, user, router]) useEffect(() => { if (!subscriptions.length) { setSelectedAboId(null) return } setSelectedAboId(prev => { if (prev != null && subscriptions.some((sub) => String(sub.id) === String(prev))) return prev return subscriptions[0].id }) }, [subscriptions]) const coffeeImageById = React.useMemo(() => { const map: Record = {} coffees.forEach((coffee) => { if (coffee.image) { map[String(coffee.id)] = coffee.image } }) return map }, [coffees]) if (!hasHydrated || !isAuthReady || !user) { return (

Loading...

) } const selectedAbo = subscriptions.find((sub) => String(sub.id) === String(selectedAboId)) || null const status = normalizeSubscriptionStatus(selectedAbo?.status) const includedItems = selectedAbo?.pack_breakdown || selectedAbo?.items || [] const totalPacks = includedItems.reduce((sum, item) => sum + (Number(item.quantity) || 0), 0) const draftTotalPacks = Object.values(draftItems).reduce((sum, qty) => sum + (Number(qty) || 0), 0) const canChangeContent = status === 'ongoing' || status === 'pause' || status === 'issued' const startEditingContent = () => { if (!selectedAbo) return const nextDraft: Record = {} ;(selectedAbo.pack_breakdown || selectedAbo.items || []).forEach((item) => { const key = String(item.coffeeId ?? '') if (!key) return nextDraft[key] = (nextDraft[key] || 0) + (Number(item.quantity) || 0) }) setDraftItems(nextDraft) setEditingContent(true) setContentError(null) } const updateDraftQty = (coffeeId: string, value: number) => { setDraftItems((prev) => ({ ...prev, [coffeeId]: Math.max(0, Math.floor(Number(value) || 0)) })) } const cancelEditingContent = () => { setEditingContent(false) setDraftItems({}) setContentError(null) } const saveContentChanges = async () => { if (!selectedAbo) return setContentError(null) const items = Object.entries(draftItems) .filter(([, qty]) => Number(qty) > 0) .map(([coffeeId, quantity]) => ({ coffeeId, quantity: Number(quantity) })) if (!items.length) { setContentError('Please select at least one coffee with quantity greater than 0.') return } if (draftTotalPacks !== 6 && draftTotalPacks !== 12) { setContentError('Total packs must be exactly 6 or 12.') return } try { setSavingContent(true) await editSubscriptionContent(selectedAbo.id, items) setEditingContent(false) setRefreshKey((k) => k + 1) } catch (e: any) { setContentError(e?.message || 'Failed to update subscription content.') } finally { setSavingContent(false) } } const onStatusAction = async (targetStatus: 'ongoing' | 'pause' | 'cancelled') => { if (!selectedAbo) return setStatusError(null) try { setStatusBusy(true) await changeSubscriptionStatus(selectedAbo.id, targetStatus) setEditingContent(false) setRefreshKey((k) => k + 1) } catch (e: any) { setStatusError(e?.message || 'Failed to update subscription status.') } finally { setStatusBusy(false) } } const openStatusConfirm = (targetStatus: 'ongoing' | 'pause' | 'cancelled') => { setStatusConfirmTarget(targetStatus) setStatusError(null) } const closeStatusConfirm = () => { if (statusBusy) return setStatusConfirmTarget(null) } const confirmStatusChange = async () => { if (!statusConfirmTarget) return await onStatusAction(statusConfirmTarget) setStatusConfirmTarget(null) } const confirmTitle = statusConfirmTarget === 'pause' ? 'Pause subscription?' : statusConfirmTarget === 'ongoing' ? 'Resume subscription?' : 'Cancel subscription?' const confirmDescription = statusConfirmTarget === 'pause' ? 'Your subscription will be paused. Billing and deliveries remain stopped until you resume it.' : statusConfirmTarget === 'ongoing' ? 'Your subscription will become ongoing again for the next billing cycle.' : 'Your subscription will be cancelled and cannot continue automatically. This action is intended to stop future cycles.' const confirmButtonText = statusConfirmTarget === 'pause' ? 'Yes, pause' : statusConfirmTarget === 'ongoing' ? 'Yes, resume' : 'Yes, cancel subscription' return (

My Subscriptions

Select any subscription to view details and included items.

{loading ? (
Loading subscriptions…
) : error ? (
{error}
) : subscriptions.length === 0 ? (
You don’t have any subscriptions yet.
) : (

All subscriptions

{subscriptions.length} total

{subscriptions.map((subscription) => { const isSelected = String(subscription.id) === String(selectedAboId) const subscriptionStatus = normalizeSubscriptionStatus(subscription.status) const packs = (subscription.pack_breakdown || subscription.items || []).reduce( (sum, item) => sum + (Number(item.quantity) || 0), 0, ) return ( ) })}
{selectedAbo && ( <>

Subscription details

{selectedAbo.name || 'Coffee Subscription'}

{displayStatus(status)}
{(status === 'ongoing' || status === 'issued') && ( )} {status === 'pause' && ( )} {(status === 'ongoing' || status === 'pause' || status === 'issued') && ( )} {(status === 'finished' || status === 'cancelled') && (

No further status changes are available for this subscription.

)}
{statusError && (

{statusError}

)}

Subscription ID

{selectedAbo.id}

Price

{formatMoney(selectedAbo.price, selectedAbo.currency)}

Frequency

{selectedAbo.frequency || '—'}

Country

{(selectedAbo.country || '').toUpperCase() || '—'}

Started

{formatDate(selectedAbo.startedAt || selectedAbo.createdAt)}

Next billing

{formatDate(selectedAbo.nextBillingAt)}

Included in your subscription

{includedItems.length} item(s), {totalPacks} total pack(s)

Changes apply from your next billing cycle.

{!editingContent && canChangeContent && ( )}
{!canChangeContent && (

Coffee content can only be changed while a subscription is issued, ongoing, or paused.

)} {includedItems.length === 0 ? (
No included items were returned for this subscription.
) : (
{includedItems.map((item, index) => { const imageUrl = item.coffeeId ? coffeeImageById[String(item.coffeeId)] : '' return (

{item.coffeeName || `Coffee #${item.coffeeId || index + 1}`}

Coffee ID: {item.coffeeId || '—'}

Packs included: {Number(item.quantity) || 0}

{imageUrl ? ( {item.coffeeName ) : (
)}
) })}
)} {editingContent && canChangeContent && (

Edit coffee content

Selected packs: {draftTotalPacks} (must be 6 or 12)

{coffeesLoading ? (

Loading available coffees…

) : coffeesError ? (

{coffeesError}

) : (
{coffees.map((coffee) => { const key = String(coffee.id) const qty = draftItems[key] || 0 return (

{coffee.name}

{coffee.description || 'No description'}

updateDraftQty(key, Number(e.target.value))} className="w-20 rounded-md border border-gray-300 px-2 py-1 text-sm text-gray-900" />
) })}
)} {contentError && (

{contentError}

)}
)}
)}
)}
) }