bolt-templates-sms-extensio.../client/src/components/WhitelistModal.jsx
2026-03-27 01:50:18 +05:30

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-xl p-8 w-full max-w-md shadow-xl 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>
);
}