142 lines
6.8 KiB
JavaScript
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; |