RegisterFrontend/src/features/referralManagement/components/GenerateReferralForm.jsx
2025-09-08 16:04:45 +02:00

142 lines
6.8 KiB
JavaScript

import React, { useState } from "react";
import useAuthStore from "../../../store/authStore";
import { showToast } from "../../toast/toastUtils";
import { useTranslation } from "react-i18next";
import { log } from "../../../utils/logger";
import { createReferralLinkApi } from "../api/referralApi";
function GenerateReferralForm({ onReferralGenerated }) {
const [expiresInDays, setExpiresInDays] = useState(3); // can become number | 'unlimited'
const [maxUses, setMaxUses] = useState(10); // can become number | 'unlimited'
const [loading, setLoading] = useState(false);
const { accessToken } = useAuthStore();
const { t, i18n } = useTranslation('referral'); // get i18n for existence check
const UNLIMITED_SENTINEL = 0; // backend sentinel for unlimited uses
// Helper: only call t if key exists to avoid missingKey log noise for newly added keys
const tr = (key, fallback) => (i18n?.exists(`referral:${key}`) ? t(key) : fallback);
const handleGenerateReferral = async () => {
setLoading(true);
log("[GenerateReferralForm] Generating referral link...", { expiresInDays, maxUses });
try {
// For unlimited send maxUses: 0 (sentinel) instead of omitting (backend expects a value)
const body =
maxUses === 'unlimited' || expiresInDays === 'unlimited'
? {
expiresInDays: expiresInDays === 'unlimited' ? 0 : expiresInDays, // 0 sentinel = no expiration
maxUses: maxUses === 'unlimited' ? UNLIMITED_SENTINEL : maxUses,
}
: { expiresInDays, maxUses };
log("[GenerateReferralForm] Payload to /api/referral/create:", body);
const data = await createReferralLinkApi(body);
log("[GenerateReferralForm] Referral link response:", data);
if (data && data.link) {
showToast({ type: "success", message: t('toast.generateSuccess') });
} else {
showToast({ type: "error", message: t('toast.generateNoLink') });
}
await onReferralGenerated();
} catch (error) {
log("[GenerateReferralForm] Error generating referral link:", error);
// createReferralLinkApi will have shown server message toast for known cases,
// show a generic error only if no user-friendly message was already shown
if (!error?.handled) {
showToast({ type: "error", message: t('toast.generateError') });
}
}
setLoading(false);
};
return (
<div className="bg-white overflow-hidden shadow rounded-lg mb-8">
<div className="p-6 border-b border-gray-100">
<h2 className="text-xl font-semibold text-gray-900">{t('form.generateHeading')}</h2>
<p className="text-gray-600 mt-1">{t('form.generateSubtitle')}</p>
</div>
<div className="p-6">
<form className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Link Expiration */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">{t('form.linkExpiration')}</label>
<select
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-700"
value={expiresInDays}
onChange={(e) => {
const v = e.target.value;
const parsed = v === 'unlimited' ? 'unlimited' : Number(v);
setExpiresInDays(parsed);
if (v === 'unlimited') {
// Automatically set the other field to unlimited as requested
setMaxUses('unlimited');
log("[GenerateReferralForm] Expiration set to 'unlimited' -> also setting maxUses to 'unlimited'");
}
}}
style={{ color: expiresInDays ? "black" : "gray" }}
>
<option value="unlimited">{tr('form.option.expirationUnlimited','Unlimited')}</option>
<option value="1">{t('form.option.1Day')}</option>
<option value="3">{t('form.option.3Days')}</option>
<option value="7">{t('form.option.1Week')}</option>
</select>
</div>
{/* Maximum Uses */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">{t('form.maximumUses')}</label>
<select
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-700"
value={maxUses}
onChange={(e) => {
const v = e.target.value;
const parsed = v === 'unlimited' ? 'unlimited' : Number(v);
setMaxUses(parsed);
if (v === 'unlimited') {
// Automatically set the other field to unlimited as requested
setExpiresInDays('unlimited');
log("[GenerateReferralForm] maxUses set to 'unlimited' -> also setting expiresInDays to 'unlimited'");
}
}}
style={{ color: maxUses ? "black" : "gray" }}
>
{/* Unlimited option (add translation). Fallback avoids missingKey warnings */}
<option value="unlimited">{tr('form.option.unlimited','Unlimited')}</option>
<option value="1">{t('form.option.use1')}</option>
<option value="3">{t('form.option.use3')}</option>
<option value="5">{t('form.option.use5')}</option>
<option value="10">{t('form.option.use10')}</option>
</select>
</div>
</div>
{/* Generate Button */}
<div className="flex justify-start">
<button
type="button"
className="bg-blue-600 text-white px-8 py-3 rounded-lg font-semibold hover:bg-blue-700 transition duration-200 flex items-center space-x-2 shadow-lg"
onClick={handleGenerateReferral}
disabled={loading}
>
{loading ? (
<svg className="animate-spin w-5 h-5 text-white" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
)}
<span>{loading ? t('form.button.generating') : t('form.button.generate')}</span>
</button>
</div>
</form>
{/* No error or generated link display here */}
</div>
</div>
);
}
export default GenerateReferralForm;