170 lines
6.7 KiB
TypeScript
170 lines
6.7 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import { Head, Link, useForm, usePage } from '@inertiajs/react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import MarketingLayout from '@/layouts/mainWebsite';
|
|
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
type EligiblePurchase = {
|
|
id: number;
|
|
package_name: string;
|
|
purchased_at: string;
|
|
expires_at: string;
|
|
price: number | string;
|
|
currency: string;
|
|
};
|
|
|
|
type WithdrawalConfirmProps = {
|
|
eligiblePurchases: EligiblePurchase[];
|
|
windowDays: number;
|
|
};
|
|
|
|
const WithdrawalConfirm: React.FC<WithdrawalConfirmProps> = ({ eligiblePurchases, windowDays }) => {
|
|
const { t, i18n } = useTranslation('marketing');
|
|
const { localizedPath } = useLocalizedRoutes();
|
|
const { flash } = usePage<{ flash?: { success?: string; error?: string } }>().props;
|
|
const initialPurchaseId = eligiblePurchases[0]?.id ?? null;
|
|
const { data, setData, post, processing, errors } = useForm<{ purchase_id: number | null }>({
|
|
purchase_id: initialPurchaseId,
|
|
});
|
|
|
|
const formatDate = (value: string) => new Date(value).toLocaleDateString(i18n.language);
|
|
const formatDateTime = (value: string) => new Date(value).toLocaleString(i18n.language);
|
|
const formattedPurchases = useMemo(
|
|
() => eligiblePurchases.map((purchase) => ({
|
|
...purchase,
|
|
purchasedLabel: formatDateTime(purchase.purchased_at),
|
|
expiresLabel: formatDate(purchase.expires_at),
|
|
})),
|
|
[eligiblePurchases, i18n.language],
|
|
);
|
|
|
|
const handleSubmit = (event: React.FormEvent) => {
|
|
event.preventDefault();
|
|
post(localizedPath('/widerruf'), { preserveScroll: true });
|
|
};
|
|
|
|
const hasEligible = formattedPurchases.length > 0;
|
|
|
|
return (
|
|
<MarketingLayout title={t('withdrawal.title')}>
|
|
<Head title={t('withdrawal.title')} />
|
|
<section className="bg-white py-16">
|
|
<div className="mx-auto max-w-4xl px-6">
|
|
<header className="mb-10">
|
|
<p className="text-sm uppercase tracking-[0.2em] text-gray-400">
|
|
Die Fotospiel App
|
|
</p>
|
|
<h1 className="mt-2 text-3xl font-semibold text-gray-900 md:text-4xl">
|
|
{t('withdrawal.title')}
|
|
</h1>
|
|
<p className="mt-3 text-sm text-gray-500">
|
|
{t('withdrawal.subtitle', { days: windowDays })}
|
|
</p>
|
|
</header>
|
|
|
|
{flash?.success && (
|
|
<div className="mb-6 rounded-xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-700">
|
|
{flash.success}
|
|
</div>
|
|
)}
|
|
|
|
{flash?.error && (
|
|
<div className="mb-6 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700">
|
|
{flash.error}
|
|
</div>
|
|
)}
|
|
|
|
{!hasEligible ? (
|
|
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-8">
|
|
<h2 className="text-lg font-semibold text-gray-900">
|
|
{t('withdrawal.empty_title')}
|
|
</h2>
|
|
<p className="mt-2 text-sm text-gray-600">
|
|
{t('withdrawal.empty_body')}
|
|
</p>
|
|
<Link
|
|
href={localizedPath('/packages')}
|
|
className="mt-6 inline-flex items-center text-sm font-semibold text-gray-900 underline underline-offset-4"
|
|
>
|
|
{t('withdrawal.empty_cta')}
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
<div className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
|
|
<h2 className="text-lg font-semibold text-gray-900">
|
|
{t('withdrawal.selection_title')}
|
|
</h2>
|
|
<p className="mt-2 text-sm text-gray-600">
|
|
{t('withdrawal.selection_body')}
|
|
</p>
|
|
|
|
<div className="mt-6 space-y-3">
|
|
{formattedPurchases.map((purchase) => (
|
|
<label
|
|
key={purchase.id}
|
|
className="flex cursor-pointer items-start gap-4 rounded-xl border border-gray-200 p-4 transition hover:border-gray-300"
|
|
>
|
|
<input
|
|
type="radio"
|
|
name="purchase_id"
|
|
value={purchase.id}
|
|
checked={data.purchase_id === purchase.id}
|
|
onChange={() => setData('purchase_id', purchase.id)}
|
|
className="mt-1 h-4 w-4 text-gray-900"
|
|
/>
|
|
<div className="flex-1">
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
<div>
|
|
<p className="text-sm font-semibold text-gray-900">
|
|
{purchase.package_name}
|
|
</p>
|
|
<p className="mt-1 text-xs text-gray-500">
|
|
{t('withdrawal.purchase_date', { date: purchase.purchasedLabel })}
|
|
</p>
|
|
</div>
|
|
<div className="text-sm font-semibold text-gray-900">
|
|
{purchase.price} {purchase.currency}
|
|
</div>
|
|
</div>
|
|
<p className="mt-2 text-xs text-gray-500">
|
|
{t('withdrawal.expires_at', { date: purchase.expiresLabel })}
|
|
</p>
|
|
</div>
|
|
</label>
|
|
))}
|
|
</div>
|
|
|
|
{errors.purchase_id && (
|
|
<p className="mt-3 text-sm text-red-600">
|
|
{errors.purchase_id}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-4 rounded-2xl border border-gray-200 bg-gray-50 p-6 sm:flex-row sm:items-center sm:justify-between">
|
|
<div>
|
|
<p className="text-sm font-semibold text-gray-900">
|
|
{t('withdrawal.confirm_title')}
|
|
</p>
|
|
<p className="mt-1 text-sm text-gray-600">
|
|
{t('withdrawal.confirm_body')}
|
|
</p>
|
|
</div>
|
|
<Button type="submit" size="lg" disabled={processing || !data.purchase_id}>
|
|
{processing ? t('withdrawal.confirm_processing') : t('withdrawal.confirm_button')}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
)}
|
|
</div>
|
|
</section>
|
|
</MarketingLayout>
|
|
);
|
|
};
|
|
|
|
WithdrawalConfirm.layout = (page: React.ReactNode) => page;
|
|
|
|
export default WithdrawalConfirm;
|