Check payload logging
This commit is contained in:
parent
ab7944e866
commit
1e9947f88c
|
|
@ -36,7 +36,7 @@ function SubLayout({ children }) {
|
|||
// Guard: redirect to / if no active business in session.
|
||||
// Also enforce cURL-first: only the cURL profile route is available until an active profile exists.
|
||||
function BusinessGuard({ children, isGlobalSmsRoute }) {
|
||||
const { activeBusinessId, loading, hasGlobalSms } = useBusiness();
|
||||
const { activeBusinessId, loading, isSetupComplete } = useBusiness();
|
||||
const location = useLocation();
|
||||
|
||||
if (loading) {
|
||||
|
|
@ -51,7 +51,7 @@ function BusinessGuard({ children, isGlobalSmsRoute }) {
|
|||
return <Navigate to="/" state={{ from: location }} replace />;
|
||||
}
|
||||
|
||||
if (!hasGlobalSms && !isGlobalSmsRoute) {
|
||||
if (!isSetupComplete && !isGlobalSmsRoute) {
|
||||
return <Navigate to={`/${activeBusinessId}/global-sms`} replace />;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export default function Sidebar() {
|
|||
const navigate = useNavigate();
|
||||
|
||||
const navItems = [
|
||||
{ id: 'globalSms', to: `/${activeBusinessId}/global-sms`, label: 'Global SMS cURL' },
|
||||
{ id: 'globalSms', to: `/${activeBusinessId}/global-sms`, label: 'Omni-channel SMS' },
|
||||
{ id: 'events', to: `/${activeBusinessId}/events`, label: 'Events' },
|
||||
{ id: 'templates', to: `/${activeBusinessId}/templates`, label: 'Templates' },
|
||||
];
|
||||
|
|
@ -70,8 +70,7 @@ export default function Sidebar() {
|
|||
key={id}
|
||||
to={to}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center gap-3 px-3 py-2.5 rounded-md text-sm font-medium transition-colors duration-150 ${
|
||||
isActive
|
||||
`flex items-center gap-3 px-3 py-2.5 rounded-md text-sm font-medium transition-colors duration-150 ${isActive
|
||||
? 'bg-refresh-hover text-primary-blue'
|
||||
: 'text-text-muted hover:text-text-primary hover:bg-row-hover'
|
||||
}`
|
||||
|
|
|
|||
|
|
@ -10,8 +10,16 @@ const SESSION_KEY = 'sms_active_business';
|
|||
export function BusinessProvider({ children }) {
|
||||
const [activeBusiness, setActiveBusinessState] = useState(null);
|
||||
const [hasGlobalSms, setHasGlobalSms] = useState(false);
|
||||
const [isSetupComplete, setIsSetupComplete] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const updateReadyState = useCallback((activeProfile) => {
|
||||
const hasProfile = !!activeProfile;
|
||||
setHasGlobalSms(hasProfile);
|
||||
const p = activeProfile?.provider || {};
|
||||
setIsSetupComplete(hasProfile && !!p.providerName && !!p.senderId && !!p.dltEntityId);
|
||||
}, []);
|
||||
|
||||
// On mount: rehydrate from sessionStorage and refresh from API
|
||||
useEffect(() => {
|
||||
async function rehydrate() {
|
||||
|
|
@ -30,7 +38,7 @@ export function BusinessProvider({ children }) {
|
|||
apiClient.get(`/api/businesses/${businessId}/global-sms/active`).catch(() => ({ data: {} }))
|
||||
]);
|
||||
setActiveBusinessState(bizRes.data);
|
||||
setHasGlobalSms(!!smsRes.data?.activeProfile);
|
||||
updateReadyState(smsRes.data?.activeProfile);
|
||||
sessionStorage.setItem(SESSION_KEY, JSON.stringify({
|
||||
businessId,
|
||||
companyId: runtimeCompanyId || companyId || '',
|
||||
|
|
@ -40,6 +48,7 @@ export function BusinessProvider({ children }) {
|
|||
sessionStorage.removeItem(SESSION_KEY);
|
||||
setActiveBusinessState(null);
|
||||
setHasGlobalSms(false);
|
||||
setIsSetupComplete(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -55,15 +64,17 @@ export function BusinessProvider({ children }) {
|
|||
}));
|
||||
try {
|
||||
const smsRes = await apiClient.get(`/api/businesses/${business.businessId}/global-sms/active`);
|
||||
setHasGlobalSms(!!smsRes.data?.activeProfile);
|
||||
updateReadyState(smsRes.data?.activeProfile);
|
||||
} catch {
|
||||
setHasGlobalSms(false);
|
||||
setIsSetupComplete(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const clearBusiness = useCallback(() => {
|
||||
setActiveBusinessState(null);
|
||||
setHasGlobalSms(false);
|
||||
setIsSetupComplete(false);
|
||||
sessionStorage.removeItem(SESSION_KEY);
|
||||
}, []);
|
||||
|
||||
|
|
@ -71,7 +82,7 @@ export function BusinessProvider({ children }) {
|
|||
|
||||
return (
|
||||
<BusinessContext.Provider value={{
|
||||
activeBusiness, activeBusinessId, setActiveBusiness, clearBusiness, loading, hasGlobalSms, setHasGlobalSms
|
||||
activeBusiness, activeBusinessId, setActiveBusiness, clearBusiness, loading, hasGlobalSms, setHasGlobalSms, isSetupComplete, setIsSetupComplete
|
||||
}}>
|
||||
{children}
|
||||
</BusinessContext.Provider>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import apiClient from '../api/client';
|
||||
import { useBusiness } from '../context/BusinessContext';
|
||||
|
||||
export default function GlobalSms() {
|
||||
const { businessId } = useParams();
|
||||
const { setHasGlobalSms } = useBusiness();
|
||||
const navigate = useNavigate();
|
||||
const { isSetupComplete, setHasGlobalSms, setIsSetupComplete } = useBusiness();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [profiles, setProfiles] = useState([]);
|
||||
const [activeProfileId, setActiveProfileId] = useState(null);
|
||||
|
|
@ -14,30 +15,56 @@ export default function GlobalSms() {
|
|||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState('');
|
||||
|
||||
// Form state for Create / Edit
|
||||
// Form state for Create / Edit Profile
|
||||
const [editingId, setEditingId] = useState(null);
|
||||
const [formName, setFormName] = useState('');
|
||||
const [formCurl, setFormCurl] = useState('');
|
||||
const [formSetActive, setFormSetActive] = useState(true);
|
||||
|
||||
// Form state for Missing Provider Fields
|
||||
const [providerForm, setProviderForm] = useState({ providerName: '', senderId: '', dltEntityId: '' });
|
||||
const [savingProvider, setSavingProvider] = useState(false);
|
||||
|
||||
const loadProfiles = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await apiClient.get(`/api/businesses/${businessId}/global-sms/profiles`);
|
||||
setProfiles(res.data.profiles || []);
|
||||
setActiveProfileId(res.data.activeProfileId);
|
||||
setHasGlobalSms(!!res.data.activeProfileId);
|
||||
const fetchedProfiles = res.data.profiles || [];
|
||||
const fetchActiveId = res.data.activeProfileId;
|
||||
setProfiles(fetchedProfiles);
|
||||
setActiveProfileId(fetchActiveId);
|
||||
|
||||
const activeProfile = fetchedProfiles.find(p => p.id === fetchActiveId) || null;
|
||||
const hasProfile = !!activeProfile;
|
||||
setHasGlobalSms(hasProfile);
|
||||
|
||||
const p = activeProfile?.provider || {};
|
||||
const complete = hasProfile && !!p.providerName && !!p.senderId && !!p.dltEntityId;
|
||||
setIsSetupComplete(complete);
|
||||
|
||||
setProviderForm({
|
||||
providerName: p.providerName || '',
|
||||
senderId: p.senderId || '',
|
||||
dltEntityId: p.dltEntityId || '',
|
||||
});
|
||||
} catch {
|
||||
setError('Failed to load cURL profiles');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [businessId, setHasGlobalSms]);
|
||||
}, [businessId, setHasGlobalSms, setIsSetupComplete]);
|
||||
|
||||
useEffect(() => {
|
||||
loadProfiles();
|
||||
}, [loadProfiles]);
|
||||
|
||||
const activeProfile = profiles.find(p => p.id === activeProfileId) || null;
|
||||
const pData = activeProfile?.provider || {};
|
||||
const missingFields = [];
|
||||
if (activeProfile && !pData.providerName) missingFields.push('providerName');
|
||||
if (activeProfile && !pData.senderId) missingFields.push('senderId');
|
||||
if (activeProfile && !pData.dltEntityId) missingFields.push('dltEntityId');
|
||||
|
||||
function handleAddClick() {
|
||||
setEditingId(null);
|
||||
setFormName('');
|
||||
|
|
@ -51,7 +78,7 @@ export default function GlobalSms() {
|
|||
setEditingId(profile.id);
|
||||
setFormName(profile.name);
|
||||
setFormCurl(profile.rawCurl);
|
||||
setFormSetActive(false); // only matters for create
|
||||
setFormSetActive(false);
|
||||
setError('');
|
||||
setSuccess('');
|
||||
}
|
||||
|
|
@ -108,6 +135,30 @@ export default function GlobalSms() {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleProviderSubmit(e) {
|
||||
e.preventDefault();
|
||||
if (!activeProfileId) return;
|
||||
setSavingProvider(true);
|
||||
setError('');
|
||||
setSuccess('');
|
||||
|
||||
try {
|
||||
await apiClient.patch(`/api/businesses/${businessId}/global-sms/profiles/${activeProfileId}`, {
|
||||
provider: {
|
||||
providerName: providerForm.providerName,
|
||||
senderId: providerForm.senderId.toUpperCase(),
|
||||
dltEntityId: providerForm.dltEntityId,
|
||||
}
|
||||
});
|
||||
setSuccess('Provider details saved successfully!');
|
||||
await loadProfiles();
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.error || 'Failed to save provider details');
|
||||
} finally {
|
||||
setSavingProvider(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex h-64 items-center justify-center">
|
||||
|
|
@ -118,12 +169,37 @@ export default function GlobalSms() {
|
|||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-8 pb-12">
|
||||
{/* Header */}
|
||||
{/* Header & Stepper */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-text-primary mb-2">cURL Profiles</h2>
|
||||
<p className="text-sm text-text-muted">
|
||||
Manage the cURL commands used to generate and test SMS templates. The active profile will be used across the application.
|
||||
<h2 className="text-2xl font-bold text-text-primary mb-2">Setup configuration</h2>
|
||||
<p className="text-sm text-text-muted mb-8">
|
||||
Complete this flow to configure your cURL profile and brand provider data. You must finish setup before generating templates.
|
||||
</p>
|
||||
|
||||
{/* CSS Stepper */}
|
||||
<div className="flex items-center w-full justify-between relative before:absolute before:top-4 before:left-0 before:h-[2px] before:w-full before:bg-border-soft before:-z-10">
|
||||
{[
|
||||
{ label: 'Add / Select Profile', done: !!activeProfile, active: !activeProfile },
|
||||
{ label: 'Validate cURL', done: !!activeProfile, active: false },
|
||||
{ label: 'Complete Fields', done: isSetupComplete, active: !!activeProfile && !isSetupComplete },
|
||||
{ label: 'Ready', done: isSetupComplete, active: isSetupComplete }
|
||||
].map((step, idx) => (
|
||||
<div key={idx} className="flex flex-col items-center">
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-colors border-2 ${step.done
|
||||
? 'bg-primary-blue border-primary-blue text-white'
|
||||
: step.active
|
||||
? 'bg-white border-primary-blue text-primary-blue'
|
||||
: 'bg-white border-border-main text-text-muted'
|
||||
}`}>
|
||||
{step.done ? '✓' : idx + 1}
|
||||
</div>
|
||||
<span className={`mt-2 text-[11px] uppercase tracking-wide font-bold ${step.done || step.active ? 'text-primary-blue' : 'text-text-muted'
|
||||
}`}>
|
||||
{step.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
|
|
@ -139,8 +215,105 @@ export default function GlobalSms() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Active Profile Setup Review Block */}
|
||||
{activeProfile && (
|
||||
<div className={`p-6 rounded-xl border ${isSetupComplete ? 'border-primary-blue bg-[#f0f2fb]' : 'border-[#d47f45] bg-[#fdfaf5]'} shadow-sm`}>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<h3 className="font-bold text-text-primary text-lg">Active Setup: {activeProfile.name}</h3>
|
||||
{isSetupComplete ? (
|
||||
<span className="px-3 py-1 bg-badge-bg text-badge-text border border-badge-border rounded-full text-xs font-bold uppercase tracking-wide">Setup Complete</span>
|
||||
) : (
|
||||
<span className="px-3 py-1 bg-tags-bg text-tags-text border border-tags-border rounded-full text-xs font-bold uppercase tracking-wide">Missing Information</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm font-medium text-text-primary">Parsed Provider Data:</p>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li className="flex justify-between items-center bg-surface-white p-2 rounded border border-border-soft">
|
||||
<span className="text-text-muted">Provider:</span>
|
||||
<span className="font-bold text-text-primary">{pData.providerName || <span className="text-error-text text-xs uppercase">Missing</span>}</span>
|
||||
</li>
|
||||
<li className="flex justify-between items-center bg-surface-white p-2 rounded border border-border-soft">
|
||||
<span className="text-text-muted">Sender ID:</span>
|
||||
<span className="font-bold text-text-primary">{pData.senderId || <span className="text-error-text text-xs uppercase">Missing</span>}</span>
|
||||
</li>
|
||||
<li className="flex justify-between items-center bg-surface-white p-2 rounded border border-border-soft">
|
||||
<span className="text-text-muted">Entity ID:</span>
|
||||
<span className="font-bold text-text-primary">{pData.dltEntityId || <span className="text-error-text text-xs uppercase">Missing</span>}</span>
|
||||
</li>
|
||||
<li className="flex justify-between items-center bg-surface-white p-2 rounded border border-border-soft">
|
||||
<span className="text-text-muted">Auth Key:</span>
|
||||
<span className="font-semibold text-text-primary font-mono">{pData.authKey ? '••••••••' : 'None setup'}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{!isSetupComplete && (
|
||||
<div className="bg-surface-white p-4 rounded-lg border border-[#d47f45]/30">
|
||||
<p className="text-sm font-semibold text-text-primary mb-3">Please fill in the missing fields:</p>
|
||||
<form onSubmit={handleProviderSubmit} className="space-y-3">
|
||||
{missingFields.includes('providerName') && (
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Provider Name (e.g. MSG91)"
|
||||
value={providerForm.providerName}
|
||||
onChange={e => setProviderForm({ ...providerForm, providerName: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-border-main rounded text-sm focus:ring-1 focus:ring-primary-blue bg-page-bg"
|
||||
required
|
||||
/>
|
||||
)}
|
||||
{missingFields.includes('senderId') && (
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Sender ID (6 letters)"
|
||||
maxLength={6}
|
||||
value={providerForm.senderId}
|
||||
onChange={e => setProviderForm({ ...providerForm, senderId: e.target.value.toUpperCase() })}
|
||||
className="w-full px-3 py-2 border border-border-main rounded text-sm focus:ring-1 focus:ring-primary-blue bg-page-bg uppercase"
|
||||
required
|
||||
/>
|
||||
)}
|
||||
{missingFields.includes('dltEntityId') && (
|
||||
<input
|
||||
type="text"
|
||||
placeholder="19-digit DLT PE ID"
|
||||
value={providerForm.dltEntityId}
|
||||
onChange={e => setProviderForm({ ...providerForm, dltEntityId: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-border-main rounded text-sm focus:ring-1 focus:ring-primary-blue bg-page-bg"
|
||||
required
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={savingProvider}
|
||||
className="w-full py-2 bg-[#d47f45] hover:bg-[#cf7b3f] text-white text-sm font-bold rounded shadow-sm transition disabled:opacity-50"
|
||||
>
|
||||
{savingProvider ? 'Saving...' : 'Save Required Details'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isSetupComplete && (
|
||||
<div className="flex flex-col justify-center items-center h-full space-y-4">
|
||||
<p className="text-center text-sm font-medium text-text-muted">Your active cURL profile is fully configured.</p>
|
||||
<button
|
||||
onClick={() => navigate(`/${businessId}/events`)}
|
||||
className="px-6 py-3 bg-primary-blue hover:bg-primary-dark text-white rounded-lg shadow font-semibold text-sm transition w-full"
|
||||
>
|
||||
Continue to Events →
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Profiles List */}
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 pt-4 border-t border-border-soft">
|
||||
<h3 className="font-bold text-text-primary text-lg">All Profiles</h3>
|
||||
{profiles.length > 0 ? (
|
||||
profiles.map(p => {
|
||||
const isActive = p.id === activeProfileId;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user