187 lines
7.2 KiB
TypeScript
187 lines
7.2 KiB
TypeScript
import React from 'react';
|
|
import { usePage, router } from '@inertiajs/react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import MarketingLayout from '@/layouts/mainWebsite';
|
|
import { Loader, CheckCircle } from 'lucide-react';
|
|
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
|
|
import { useLocale } from '@/hooks/useLocale';
|
|
import { ADMIN_HOME_PATH } from '@/admin/constants';
|
|
import { Button } from '@/components/ui/button';
|
|
import { fetchGiftVoucherByCheckout, type GiftVoucherLookupResponse } from '@/lib/giftVouchers';
|
|
|
|
type SuccessProps = {
|
|
type?: string;
|
|
};
|
|
|
|
const GiftSuccess: React.FC = () => {
|
|
const { t } = useTranslation('marketing');
|
|
const { localizedPath } = useLocalizedRoutes();
|
|
const locale = useLocale();
|
|
const [voucher, setVoucher] = React.useState<GiftVoucherLookupResponse | null>(null);
|
|
const [loading, setLoading] = React.useState(true);
|
|
const [error, setError] = React.useState<string | null>(null);
|
|
const [copied, setCopied] = React.useState(false);
|
|
|
|
React.useEffect(() => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const checkoutId = params.get('checkout_id') || sessionStorage.getItem('gift_checkout_id');
|
|
const transactionId = params.get('transaction_id');
|
|
|
|
fetchGiftVoucherByCheckout(checkoutId || undefined, transactionId || undefined)
|
|
.then((data) => {
|
|
if (data) {
|
|
setVoucher(data);
|
|
} else {
|
|
setError(t('success.gift_lookup_failed'));
|
|
}
|
|
})
|
|
.catch(() => setError(t('success.gift_lookup_failed')))
|
|
.finally(() => setLoading(false));
|
|
}, [t]);
|
|
|
|
const onCopy = async () => {
|
|
if (!voucher?.code) return;
|
|
try {
|
|
await navigator.clipboard.writeText(voucher.code);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
} catch (e) {
|
|
setError(t('success.gift_copy_failed'));
|
|
}
|
|
};
|
|
|
|
const onShare = async () => {
|
|
if (!voucher?.code) return;
|
|
const text = t('success.gift_share_text', {
|
|
code: voucher.code,
|
|
amount: voucher.amount.toFixed(2),
|
|
currency: voucher.currency,
|
|
});
|
|
|
|
if (navigator.share) {
|
|
try {
|
|
await navigator.share({ title: t('success.gift_title'), text });
|
|
return;
|
|
} catch (e) {
|
|
// fall back to copy
|
|
}
|
|
}
|
|
|
|
await onCopy();
|
|
};
|
|
|
|
return (
|
|
<MarketingLayout title={t('success.gift_title')}>
|
|
<div className="min-h-screen bg-gradient-to-b from-background via-muted/30 to-background py-16">
|
|
<div className="mx-auto max-w-3xl space-y-6 px-4 text-center sm:px-6">
|
|
<CheckCircle className="mx-auto h-12 w-12 text-green-500" />
|
|
<h1 className="text-3xl font-bold text-foreground">{t('success.gift_title')}</h1>
|
|
<p className="text-muted-foreground">{t('success.gift_description')}</p>
|
|
<div className="rounded-xl border bg-card p-6 text-left shadow-sm">
|
|
<h2 className="text-lg font-semibold">{t('success.gift_code_title')}</h2>
|
|
{loading && <p className="text-muted-foreground">{t('success.gift_loading')}</p>}
|
|
{error && <p className="text-destructive">{error}</p>}
|
|
{voucher && (
|
|
<div className="mt-3 space-y-3">
|
|
<div className="flex flex-wrap items-center justify-between gap-3 rounded-lg border bg-muted/50 px-4 py-3">
|
|
<div className="text-left">
|
|
<p className="text-xs uppercase tracking-wide text-muted-foreground">{t('success.gift_code_label')}</p>
|
|
<p className="font-mono text-lg font-bold">{voucher.code}</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button size="sm" variant="secondary" onClick={onCopy}>
|
|
{copied ? t('success.gift_copied') : t('success.gift_copy')}
|
|
</Button>
|
|
<Button size="sm" onClick={onShare}>
|
|
{t('success.gift_share')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{t('success.gift_value', {
|
|
amount: voucher.amount.toFixed(2),
|
|
currency: voucher.currency,
|
|
})}
|
|
</p>
|
|
{voucher.expires_at && (
|
|
<p className="text-sm text-muted-foreground">
|
|
{t('success.gift_expires', {
|
|
date: new Date(voucher.expires_at).toLocaleDateString(locale || undefined),
|
|
})}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="rounded-xl border bg-card p-6 text-left shadow-sm">
|
|
<h2 className="text-lg font-semibold">{t('success.gift_bullets_title')}</h2>
|
|
<ul className="mt-3 list-disc space-y-2 pl-5 text-muted-foreground">
|
|
<li>{t('success.gift_bullet_email')}</li>
|
|
<li>{t('success.gift_bullet_validity')}</li>
|
|
<li>{t('success.gift_bullet_redeem')}</li>
|
|
</ul>
|
|
</div>
|
|
<div className="flex justify-center">
|
|
<Button asChild size="lg">
|
|
<a href={localizedPath('/', locale || undefined)}>{t('success.gift_cta_home')}</a>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</MarketingLayout>
|
|
);
|
|
};
|
|
|
|
const AuthRedirectSuccess: React.FC<{ emailVerified?: boolean | null }> = ({ emailVerified }) => {
|
|
const { t } = useTranslation('success');
|
|
const { localizedPath } = useLocalizedRoutes();
|
|
|
|
if (emailVerified) {
|
|
router.visit(ADMIN_HOME_PATH, { preserveState: false });
|
|
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center bg-gray-50">
|
|
<div className="text-center">
|
|
<Loader className="mx-auto mb-2 inline-block h-8 w-8 animate-spin rounded-full border border-2 border-blue-600 border-t-transparent" />
|
|
<p className="text-gray-600">{t('redirecting')}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<MarketingLayout title={t('complete_purchase')}>
|
|
<div className="flex min-h-screen items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
|
<div className="w-full max-w-md rounded-lg bg-white p-8 shadow-md">
|
|
<div className="text-center">
|
|
<h2 className="mb-4 text-2xl font-bold text-gray-900">{t('complete_purchase')}</h2>
|
|
<p className="mb-6 text-gray-600">{t('login_to_continue')}</p>
|
|
<a
|
|
href={localizedPath('/login')}
|
|
className="mb-2 block rounded-md bg-blue-600 px-6 py-2 font-medium text-white transition duration-300 hover:bg-blue-700"
|
|
>
|
|
{t('login')}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</MarketingLayout>
|
|
);
|
|
};
|
|
|
|
const Success: React.FC<SuccessProps> = () => {
|
|
const { auth } = usePage<{ auth: { user?: { email_verified_at?: string | null } } }>().props;
|
|
const searchParams = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : new URLSearchParams();
|
|
const type = searchParams.get('type');
|
|
|
|
if (type === 'gift') {
|
|
return <GiftSuccess />;
|
|
}
|
|
|
|
return <AuthRedirectSuccess emailVerified={auth.user?.email_verified_at} />;
|
|
};
|
|
|
|
Success.layout = (page: React.ReactNode) => page;
|
|
|
|
export default Success;
|