157 lines
5.4 KiB
TypeScript
157 lines
5.4 KiB
TypeScript
'use client';
|
|
import React, { useState, useMemo } from 'react';
|
|
import PageLayout from '../components/PageLayout';
|
|
import { useRouter } from 'next/navigation';
|
|
import { useActiveCoffees } from './hooks/getActiveCoffees';
|
|
import { useShippingFees } from './hooks/useShippingFees';
|
|
import AboHeroHeader from './components/AboHeroHeader';
|
|
import AboStepper from './components/AboStepper';
|
|
import CoffeeSelectionGrid from './components/CoffeeSelectionGrid';
|
|
import SelectionSummaryCard from './components/SelectionSummaryCard';
|
|
import SubscribeGuard from './components/SubscribeGuard';
|
|
import {
|
|
COFFEE_SELECTIONS_STORAGE_KEY,
|
|
COFFEE_SELECTIONS_UNIT,
|
|
COFFEE_SELECTIONS_UNIT_STORAGE_KEY,
|
|
getOrderPackError,
|
|
getRemainingMinPacks,
|
|
MAX_ABO_PACKS,
|
|
packsToCapsules,
|
|
} from './lib/orderRules';
|
|
|
|
import { useTranslation } from '../i18n/useTranslation';
|
|
|
|
export default function CoffeeAbonnementPage() {
|
|
return (
|
|
<SubscribeGuard>
|
|
<CoffeeAbonnementPageContent />
|
|
</SubscribeGuard>
|
|
);
|
|
}
|
|
|
|
function CoffeeAbonnementPageContent() {
|
|
const { t } = useTranslation();
|
|
const [selections, setSelections] = useState<Record<string, number>>({});
|
|
const router = useRouter();
|
|
|
|
// Fetch active coffees from the backend
|
|
const { coffees, loading, error } = useActiveCoffees();
|
|
|
|
// Shipping fees (threshold-based)
|
|
const { resolveShippingFee, loading: shippingLoading, error: shippingError } = useShippingFees();
|
|
|
|
const selectedEntries = useMemo(
|
|
() =>
|
|
Object.entries(selections).map(([id, qty]) => {
|
|
const coffee = coffees.find((c) => c.id === id);
|
|
if (!coffee) return null;
|
|
return { coffee, quantity: qty };
|
|
}).filter(Boolean) as { coffee: ReturnType<typeof useActiveCoffees>['coffees'][number]; quantity: number }[],
|
|
[selections, coffees]
|
|
);
|
|
|
|
const totalPrice = useMemo(
|
|
() =>
|
|
selectedEntries.reduce(
|
|
(sum, entry) => sum + entry.quantity * entry.coffee.pricePer10,
|
|
0
|
|
),
|
|
[selectedEntries]
|
|
);
|
|
|
|
const totalPacks = useMemo(
|
|
() => selectedEntries.reduce((sum, entry) => sum + entry.quantity, 0),
|
|
[selectedEntries]
|
|
);
|
|
|
|
const totalCapsules = useMemo(() => packsToCapsules(totalPacks), [totalPacks]);
|
|
const selectedShippingFee = resolveShippingFee(totalCapsules);
|
|
const isFreeShippingSelected = Number(selectedShippingFee) === 0;
|
|
const orderPackError = getOrderPackError(totalPacks);
|
|
const remainingMinPacks = getRemainingMinPacks(totalPacks);
|
|
|
|
const totalNetWithShipping = useMemo(
|
|
() => totalPrice + (Number.isFinite(selectedShippingFee) ? selectedShippingFee : 0),
|
|
[totalPrice, selectedShippingFee]
|
|
);
|
|
|
|
const canProceed = selectedEntries.length > 0 && !orderPackError;
|
|
|
|
const proceedToSummary = () => {
|
|
if (!canProceed) return;
|
|
try {
|
|
sessionStorage.setItem(COFFEE_SELECTIONS_STORAGE_KEY, JSON.stringify(selections));
|
|
sessionStorage.setItem(COFFEE_SELECTIONS_UNIT_STORAGE_KEY, COFFEE_SELECTIONS_UNIT);
|
|
} catch {}
|
|
router.push('/coffee-abonnements/summary');
|
|
};
|
|
|
|
const setQuantity = (id: string, nextValue: number) => {
|
|
setSelections((prev) => {
|
|
const normalized = Math.max(0, Math.floor(Number(nextValue) || 0));
|
|
const current = prev[id] || 0;
|
|
const otherTotal = Object.entries(prev).reduce((sum, [key, qty]) => key === id ? sum : sum + qty, 0);
|
|
const maxForCoffee = Math.max(0, MAX_ABO_PACKS - otherTotal);
|
|
const bounded = Math.min(normalized, maxForCoffee);
|
|
|
|
if (bounded <= 0) {
|
|
if (!(id in prev)) return prev;
|
|
const next = { ...prev };
|
|
delete next[id];
|
|
return next;
|
|
}
|
|
|
|
if (bounded === current) return prev;
|
|
return { ...prev, [id]: bounded };
|
|
});
|
|
};
|
|
|
|
const adjustQuantity = (id: string, delta: number) => {
|
|
const current = selections[id] || 0;
|
|
setQuantity(id, current + delta);
|
|
};
|
|
|
|
return (
|
|
<PageLayout contentClassName="flex-1 relative w-full">
|
|
<div className="min-h-screen bg-[radial-gradient(circle_at_top_left,rgba(251,191,36,0.10),transparent_22%),radial-gradient(circle_at_top_right,rgba(56,189,248,0.10),transparent_24%),linear-gradient(180deg,#f8fafc_0%,#f8fafc_50%,#eef2ff_100%)]">
|
|
<div className="max-w-[1820px] mx-auto px-4 sm:px-6 xl:px-10 py-8 space-y-5">
|
|
<AboHeroHeader
|
|
title={t('autofix.kb0b660e2')}
|
|
subtitle={t('autofix.k7f48f374')}
|
|
/>
|
|
|
|
<AboStepper currentStep={1} />
|
|
|
|
<CoffeeSelectionGrid
|
|
coffees={coffees}
|
|
loading={loading}
|
|
error={error}
|
|
selections={selections}
|
|
totalPacks={totalPacks}
|
|
onAdjustQuantity={adjustQuantity}
|
|
onSetQuantity={setQuantity}
|
|
title={t('autofix.k0b03e660')}
|
|
/>
|
|
|
|
<SelectionSummaryCard
|
|
selectedEntries={selectedEntries}
|
|
shippingLoading={shippingLoading}
|
|
isFreeShippingSelected={isFreeShippingSelected}
|
|
selectedShippingFee={selectedShippingFee}
|
|
totalNetWithShipping={totalNetWithShipping}
|
|
totalCapsules={totalCapsules}
|
|
totalPacks={totalPacks}
|
|
orderPackError={orderPackError}
|
|
remainingMinPacks={remainingMinPacks}
|
|
canProceed={canProceed}
|
|
onProceed={proceedToSummary}
|
|
title={t('autofix.ke7b634f2')}
|
|
emptyText={t('autofix.kec078e54')}
|
|
continueText={t('autofix.k02665163')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</PageLayout>
|
|
);
|
|
}
|