255 lines
11 KiB
JavaScript
255 lines
11 KiB
JavaScript
import { useEffect, useMemo, useState } from 'react';
|
|
import apiClient from '../api/client';
|
|
|
|
function getMissingProviderFields(profile) {
|
|
const provider = profile?.provider || {};
|
|
const missing = [];
|
|
if (!provider.providerName) missing.push('providerName');
|
|
if (!provider.senderId) missing.push('senderId');
|
|
if (!provider.dltEntityId) missing.push('dltEntityId');
|
|
return missing;
|
|
}
|
|
|
|
export default function WhitelistModal({ businessId, template, boundProfile, onClose, onSuccess }) {
|
|
const [profile, setProfile] = useState(boundProfile);
|
|
const [providerForm, setProviderForm] = useState({ providerName: '', senderId: '', dltEntityId: '' });
|
|
const [templateId, setTemplateId] = useState('');
|
|
const [toNumber, setToNumber] = useState('');
|
|
const [savingProvider, setSavingProvider] = useState(false);
|
|
const [publishing, setPublishing] = useState(false);
|
|
const [error, setError] = useState('');
|
|
const [step, setStep] = useState('provider');
|
|
|
|
useEffect(() => {
|
|
setProfile(boundProfile);
|
|
setProviderForm({
|
|
providerName: boundProfile?.provider?.providerName || '',
|
|
senderId: boundProfile?.provider?.senderId || '',
|
|
dltEntityId: boundProfile?.provider?.dltEntityId || '',
|
|
});
|
|
}, [boundProfile]);
|
|
|
|
const missingFields = useMemo(() => getMissingProviderFields(profile), [profile]);
|
|
|
|
useEffect(() => {
|
|
if (!boundProfile) {
|
|
setError('The cURL profile bound to this template is missing. Re-select the template from Events before publishing.');
|
|
setStep('provider');
|
|
return;
|
|
}
|
|
|
|
setError('');
|
|
setStep(missingFields.length > 0 ? 'provider' : 'publish');
|
|
}, [boundProfile, missingFields]);
|
|
|
|
async function handleProviderSubmit(e) {
|
|
e.preventDefault();
|
|
if (!profile?.id) return;
|
|
|
|
setSavingProvider(true);
|
|
setError('');
|
|
|
|
try {
|
|
const res = await apiClient.patch(
|
|
`/api/businesses/${businessId}/global-sms/profiles/${profile.id}`,
|
|
{
|
|
provider: {
|
|
providerName: providerForm.providerName,
|
|
senderId: providerForm.senderId.toUpperCase(),
|
|
dltEntityId: providerForm.dltEntityId,
|
|
},
|
|
}
|
|
);
|
|
|
|
setProfile(res.data);
|
|
setProviderForm({
|
|
providerName: res.data?.provider?.providerName || '',
|
|
senderId: res.data?.provider?.senderId || '',
|
|
dltEntityId: res.data?.provider?.dltEntityId || '',
|
|
});
|
|
setStep(getMissingProviderFields(res.data).length > 0 ? 'provider' : 'publish');
|
|
} catch (err) {
|
|
setError(err.response?.data?.error || 'Failed to save provider details');
|
|
} finally {
|
|
setSavingProvider(false);
|
|
}
|
|
}
|
|
|
|
async function handlePublish(e) {
|
|
e.preventDefault();
|
|
if (!templateId.trim() || !toNumber.trim()) return;
|
|
|
|
setPublishing(true);
|
|
setError('');
|
|
|
|
try {
|
|
await apiClient.post(`/api/businesses/${businessId}/templates/${template.eventSlug}/publish`, {
|
|
templateId: templateId.trim(),
|
|
toNumber: toNumber.trim(),
|
|
});
|
|
await Promise.resolve(onSuccess());
|
|
} catch (err) {
|
|
if (err.response?.data?.missingFields?.length) {
|
|
setError(`Missing provider fields: ${err.response.data.missingFields.join(', ')}`);
|
|
setStep('provider');
|
|
} else {
|
|
setError(err.response?.data?.error || 'Failed to publish template');
|
|
}
|
|
} finally {
|
|
setPublishing(false);
|
|
}
|
|
}
|
|
|
|
const isProfileMissing = !profile?.id;
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm overflow-y-auto pt-10 pb-10">
|
|
<div className="bg-surface-white border border-border-main rounded-lg p-8 w-full max-w-md shadow-sm my-auto">
|
|
<div className="w-12 h-12 rounded-full bg-tags-bg border border-tags-border flex items-center justify-center mx-auto mb-4">
|
|
<span className="text-xl">✅</span>
|
|
</div>
|
|
|
|
<h3 className="text-lg font-bold text-text-primary text-center mb-1">
|
|
{step === 'provider' ? 'Complete Provider Details' : 'Publish Template'}
|
|
</h3>
|
|
<p className="text-sm text-text-muted text-center mb-1">
|
|
{step === 'provider'
|
|
? 'Save the missing mandatory provider fields on the bound cURL profile before publishing.'
|
|
: 'Provide the DLT template ID and destination number to complete publish.'}
|
|
</p>
|
|
<p className="text-sm font-semibold text-text-primary text-center mb-2 capitalize">
|
|
{template.eventLabel || template.eventSlug.replace(/_/g, ' ')}
|
|
</p>
|
|
{profile && (
|
|
<p className="text-xs text-text-muted text-center mb-6 uppercase tracking-wide font-semibold">
|
|
Bound Profile: {profile.name}
|
|
</p>
|
|
)}
|
|
|
|
{error && (
|
|
<div className="mb-4 px-4 py-2.5 rounded-md text-error-text bg-delayed-bg border border-delayed-border text-sm font-medium">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{step === 'provider' ? (
|
|
<form onSubmit={handleProviderSubmit} className="space-y-4">
|
|
{missingFields.includes('providerName') && (
|
|
<div>
|
|
<label className="block text-sm font-semibold text-text-primary mb-1.5">Provider Name</label>
|
|
<input
|
|
type="text"
|
|
value={providerForm.providerName}
|
|
onChange={e => setProviderForm(prev => ({ ...prev, providerName: e.target.value }))}
|
|
className="w-full px-4 py-2.5 rounded-lg bg-page-bg border border-border-main text-text-primary placeholder-placeholder-bg focus:outline-none focus:ring-2 focus:ring-primary-blue text-sm"
|
|
placeholder="e.g. MSG91"
|
|
autoFocus
|
|
required
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{missingFields.includes('senderId') && (
|
|
<div>
|
|
<label className="block text-sm font-semibold text-text-primary mb-1.5">Sender ID</label>
|
|
<input
|
|
type="text"
|
|
value={providerForm.senderId}
|
|
onChange={e => setProviderForm(prev => ({ ...prev, senderId: e.target.value.toUpperCase() }))}
|
|
className="w-full px-4 py-2.5 rounded-lg bg-page-bg border border-border-main font-mono uppercase text-text-primary placeholder-placeholder-bg focus:outline-none focus:ring-2 focus:ring-primary-blue text-sm"
|
|
placeholder="6 CHARS"
|
|
maxLength={6}
|
|
required
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{missingFields.includes('dltEntityId') && (
|
|
<div>
|
|
<label className="block text-sm font-semibold text-text-primary mb-1.5">DLT Entity ID</label>
|
|
<input
|
|
type="text"
|
|
value={providerForm.dltEntityId}
|
|
onChange={e => setProviderForm(prev => ({ ...prev, dltEntityId: e.target.value }))}
|
|
className="w-full px-4 py-2.5 rounded-lg bg-page-bg border border-border-main font-mono text-text-primary placeholder-placeholder-bg focus:outline-none focus:ring-2 focus:ring-primary-blue text-sm"
|
|
placeholder="19-digit DLT PE ID"
|
|
required
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-3 pt-4">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
disabled={savingProvider}
|
|
className="flex-1 py-2.5 rounded-lg border border-border-main text-text-primary hover:bg-page-bg text-sm font-medium transition disabled:opacity-50"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={savingProvider || isProfileMissing || missingFields.some(field => {
|
|
if (field === 'providerName') return !providerForm.providerName.trim();
|
|
if (field === 'senderId') return !providerForm.senderId.trim();
|
|
if (field === 'dltEntityId') return !providerForm.dltEntityId.trim();
|
|
return false;
|
|
})}
|
|
className="flex-1 py-2.5 rounded-lg bg-primary-blue hover:bg-primary-dark text-white text-sm font-semibold transition shadow-sm disabled:opacity-50 flex items-center justify-center gap-2"
|
|
>
|
|
{savingProvider ? <><span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> Saving…</> : 'Save Details'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
) : (
|
|
<form onSubmit={handlePublish} className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-semibold text-text-primary mb-1.5">DLT Template ID</label>
|
|
<input
|
|
type="text"
|
|
value={templateId}
|
|
onChange={e => setTemplateId(e.target.value)}
|
|
placeholder="e.g. 1234567890987654321"
|
|
className="w-full px-4 py-2.5 rounded-lg bg-page-bg border border-border-main font-mono text-text-primary placeholder-placeholder-bg focus:outline-none focus:ring-2 focus:ring-primary-blue text-sm"
|
|
autoFocus
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-semibold text-text-primary mb-1.5">Destination Phone Number</label>
|
|
<input
|
|
type="text"
|
|
value={toNumber}
|
|
onChange={e => setToNumber(e.target.value)}
|
|
placeholder="e.g. 919876543210"
|
|
className="w-full px-4 py-2.5 rounded-lg bg-page-bg border border-border-main font-mono text-text-primary placeholder-placeholder-bg focus:outline-none focus:ring-2 focus:ring-primary-blue text-sm"
|
|
required
|
|
/>
|
|
<p className="text-xs text-text-muted mt-1">This sends the publish-triggering SMS request.</p>
|
|
</div>
|
|
|
|
<div className="flex gap-3 pt-4">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
disabled={publishing}
|
|
className="flex-1 py-2.5 rounded-lg border border-border-main text-text-primary hover:bg-page-bg text-sm font-medium transition disabled:opacity-50"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={publishing || !templateId.trim() || !toNumber.trim()}
|
|
className="flex-1 py-2.5 rounded-lg bg-primary-blue hover:bg-primary-dark text-white text-sm font-semibold transition shadow-sm disabled:opacity-50 flex items-center justify-center gap-2"
|
|
>
|
|
{publishing ? <><span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> Publishing…</> : 'Publish'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|