profit-planet-frontend/src/app/referral-management/components/generateReferralLinkWidget.tsx
DeathKaioken a88c7cc133 tod mich in den mail
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 16:44:45 +02:00

209 lines
7.8 KiB
TypeScript

'use client'
import React, { useMemo, useState } from 'react'
import { ClipboardDocumentIcon } from '@heroicons/react/24/outline'
import { createReferralLink } from '../hooks/generateReferralLink'
import { useToast } from '../../components/toast/toastComponent'
import { useTranslation } from '../../i18n/useTranslation'
interface Props {
onCreated?: () => void | Promise<void>
}
export default function GenerateReferralLinkWidget({ onCreated }: Props) {
const { t } = useTranslation()
const { showToast } = useToast()
// Defaults: Unlimited + Never expires
const [maxUses, setMaxUses] = useState<string>('-1')
const [expiresInDays, setExpiresInDays] = useState<string>('-1')
// Track which select is locking the other: 'max' | 'exp' | null
const [lockedBy, setLockedBy] = useState<'max' | 'exp' | null>('max')
const [generatedLink, setGeneratedLink] = useState<string>('')
const [isCopying, setIsCopying] = useState(false)
const [isGenerating, setIsGenerating] = useState(false)
const expiryOptions = useMemo(
() => [
{ value: '1', label: t('referralManagement.expiry1Day') },
{ value: '2', label: t('referralManagement.expiry2Days') },
{ value: '3', label: t('referralManagement.expiry3Days') },
{ value: '4', label: t('referralManagement.expiry4Days') },
{ value: '5', label: t('referralManagement.expiry5Days') },
{ value: '6', label: t('referralManagement.expiry6Days') },
{ value: '7', label: t('referralManagement.expiry7Days') },
{ value: '-1', label: t('referralManagement.expiryNever') },
],
[t]
)
const maxUsesOptions = useMemo(
() => [
{ value: '1', label: t('referralManagement.maxUses1') },
{ value: '5', label: t('referralManagement.maxUses5') },
{ value: '10', label: t('referralManagement.maxUses10') },
{ value: '50', label: t('referralManagement.maxUses50') },
{ value: '-1', label: t('referralManagement.maxUsesUnlimited') },
],
[t]
)
// Handlers that enforce coupling
const onChangeMaxUses = (val: string) => {
setMaxUses(val)
if (val === '-1') {
// Unlimited -> force never expires, lock expires
setExpiresInDays('-1')
setLockedBy('max')
} else {
// Unlock if this was the locker
if (lockedBy === 'max') setLockedBy(null)
}
}
const onChangeExpires = (val: string) => {
setExpiresInDays(val)
if (val === '-1') {
// Never expires -> force unlimited, lock max uses
setMaxUses('-1')
setLockedBy('exp')
} else {
// Unlock if this was the locker
if (lockedBy === 'exp') setLockedBy(null)
}
}
const onGenerate = async () => {
setIsGenerating(true)
setGeneratedLink('')
try {
const payload = {
expiresInDays: parseInt(expiresInDays, 10),
maxUses: parseInt(maxUses, 10),
}
const res = await createReferralLink(payload)
console.log('✅ Referral create result:', res)
const body: any = res.body
const url =
body?.data?.url ||
body?.data?.link ||
body?.url ||
body?.link ||
(body?.data?.code ? `${window.location.origin}/signup?ref=${body.data.code}` : '') ||
(body?.code ? `${window.location.origin}/signup?ref=${body.code}` : '')
if (url) setGeneratedLink(url)
if (res.ok) {
showToast({
variant: 'success',
title: t('referralManagement.createLink'),
message: t('referralManagement.createSuccess'),
})
} else {
showToast({
variant: 'error',
title: t('referralManagement.createLink'),
message: body?.message || body?.error || t('referralManagement.createError'),
})
}
if (res.ok && onCreated) await onCreated()
} catch {
showToast({
variant: 'error',
title: t('referralManagement.createLink'),
message: t('referralManagement.createError'),
})
} finally {
setIsGenerating(false)
}
}
const onCopy = async () => {
if (!generatedLink) return
try {
setIsCopying(true)
await navigator.clipboard.writeText(generatedLink)
showToast({
variant: 'success',
title: t('referralManagement.copyLink'),
message: t('referralManagement.copiedMessage'),
})
setTimeout(() => setIsCopying(false), 800)
} catch {
showToast({
variant: 'error',
title: t('referralManagement.copyFailed'),
message: t('referralManagement.copyFailedMessage'),
})
setIsCopying(false)
}
}
const disableExpires = lockedBy === 'max'
const disableMaxUses = lockedBy === 'exp'
return (
<div className="rounded-[28px] border border-white/80 bg-white/85 p-6 shadow-[0_22px_60px_-34px_rgba(15,23,42,0.28)] backdrop-blur">
<h2 className="text-xl font-bold text-slate-950 mb-4 break-words">{t('referralManagement.generateTitle')}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-slate-700 mb-1 break-words">{t('referralManagement.maxUsesLabel')}</label>
<select
value={maxUses}
onChange={(e) => onChangeMaxUses(e.target.value)}
disabled={disableMaxUses}
className="w-full rounded-2xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-slate-300 focus:outline-none focus:ring-2 focus:ring-slate-900/20 disabled:bg-slate-100 disabled:text-slate-500"
>
{maxUsesOptions.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
{disableMaxUses && <p className="mt-1 text-xs text-slate-500 break-words">{t('referralManagement.lockedByNeverExpires')}</p>}
</div>
<div>
<label className="block text-sm font-medium text-slate-700 mb-1 break-words">{t('referralManagement.expiresIn')}</label>
<select
value={expiresInDays}
onChange={(e) => onChangeExpires(e.target.value)}
disabled={disableExpires}
className="w-full rounded-2xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-slate-300 focus:outline-none focus:ring-2 focus:ring-slate-900/20 disabled:bg-slate-100 disabled:text-slate-500"
>
{expiryOptions.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
{disableExpires && <p className="mt-1 text-xs text-slate-500 break-words">{t('referralManagement.lockedByUnlimited')}</p>}
</div>
</div>
<div className="mt-6 flex flex-wrap items-start gap-3">
<button
onClick={onGenerate}
disabled={isGenerating}
className="inline-flex items-center gap-2 rounded-2xl bg-[#8D6B1D] px-4 py-2 text-sm font-semibold text-white shadow-[0_18px_40px_-24px_rgba(141,107,29,0.85)] transition hover:bg-[#7A5E1A] disabled:opacity-60"
>
{isGenerating ? t('referralManagement.generating') : t('referralManagement.generateLink')}
</button>
{generatedLink && (
<div className="flex flex-1 min-w-[16rem] flex-wrap items-center gap-2">
<code className="max-w-full rounded-2xl border border-slate-200 bg-slate-50 px-3 py-2 text-sm text-slate-700 break-all">{generatedLink}</code>
<button
onClick={onCopy}
className="inline-flex items-center gap-1 rounded-2xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 transition hover:bg-slate-50"
>
<ClipboardDocumentIcon className="h-4 w-4" />
{isCopying ? t('referralManagement.copied') : t('referralManagement.copy')}
</button>
</div>
)}
</div>
</div>
)
}