diff --git a/client/src/pages/Businesses.jsx b/client/src/pages/Businesses.jsx index ce696a2..ae307e1 100644 --- a/client/src/pages/Businesses.jsx +++ b/client/src/pages/Businesses.jsx @@ -193,30 +193,54 @@ function UnifiedBusinessCard({ const isImporting = !isScraped && creatingSalesChannelId === channelId; const isLoadingReview = isScraped && reviewLoadingBusinessId === businessId; const hasWebsiteUrl = Boolean(item.channel?.websiteUrl); - const canOpenBusiness = isScraped && item.business && !isOpening; + const isCardInteractive = isScraped ? Boolean(item.business) && !isOpening : !isImporting; + const cardActionLabel = isScraped + ? `Open ${name}` + : hasWebsiteUrl + ? `Start onboarding for ${name}` + : `Use fallback URL for ${name}`; + + function triggerCardAction() { + if (!isCardInteractive) return; + + if (isScraped) { + if (!item.business) return; + onSelect(item.business); + return; + } + + if (hasWebsiteUrl) { + onImport(item.channel); + return; + } + + onFallback(); + } function handleCardClick() { - if (!canOpenBusiness) return; - onSelect(item.business); + triggerCardAction(); } function handleCardKeyDown(event) { - if (!canOpenBusiness) return; + if (!isCardInteractive) return; if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); - onSelect(item.business); + triggerCardAction(); } } return (
@@ -283,7 +307,8 @@ function UnifiedBusinessCard({ ) : ( <> +
+

{section.description}

+
- {isExpanded && ( -
-
- {group.events.map((event) => { - const state = genState[event.slug] || 'idle'; - const eventVariants = variants[event.slug] || []; - const templateStatus = templateStatusBySlug[event.slug] || 'unselected'; - const statusConfig = EVENT_TEMPLATE_STATUS_CONFIG[templateStatus] || EVENT_TEMPLATE_STATUS_CONFIG.unselected; - const selectedTemplatePreview = selectedTemplateBySlug[event.slug] || null; - const hasSelectedTemplate = !!selectedTemplatePreview; - const hasDraftWorkspace = eventVariants.length > 0; - const canOpenGenerationWorkspace = hasDraftWorkspace; - const hasExistingWorkspace = hasSelectedTemplate || canOpenGenerationWorkspace; +
+ {section.events.map((event) => { + const state = genState[event.slug] || 'idle'; + const eventVariants = variants[event.slug] || []; + const templateStatus = templateStatusBySlug[event.slug] || 'unselected'; + const statusConfig = EVENT_TEMPLATE_STATUS_CONFIG[templateStatus] || EVENT_TEMPLATE_STATUS_CONFIG.unselected; + const selectedTemplatePreview = selectedTemplateBySlug[event.slug] || null; + const hasSelectedTemplate = !!selectedTemplatePreview; + const hasDraftWorkspace = eventVariants.length > 0; + const canOpenGenerationWorkspace = hasDraftWorkspace; + const hasExistingWorkspace = hasSelectedTemplate || canOpenGenerationWorkspace; - return ( -
-
-
- {event.isDefault ? ( -
- -
- ) : ( - - )} -
-

{event.label}

-
-
- -
- - - {statusConfig.label} - - -
+ return ( +
+
+
+ {event.isDefault ? ( +
+
+ ) : ( + + )} +
+

{event.label}

- ); - })} +
+ +
+ + {statusConfig.label} + + +
+
-
- )} - - ); - })} + ); + })} +
+ + ))}
)}
diff --git a/client/src/pages/Providers.jsx b/client/src/pages/Providers.jsx index 0bb1072..d97c440 100644 --- a/client/src/pages/Providers.jsx +++ b/client/src/pages/Providers.jsx @@ -3,16 +3,6 @@ import { useNavigate, useParams } from 'react-router-dom'; import apiClient from '../api/client'; import { useBusiness } from '../context/BusinessContext'; -const DESKTOP_SPLIT_QUERY = '(min-width: 1100px)'; -const DEFAULT_LIST_PANE_WIDTH = 340; -const MIN_LIST_PANE_WIDTH = 280; -const MAX_LIST_PANE_WIDTH = 420; -const MIN_DETAIL_PANE_WIDTH = 440; - -function clamp(value, min, max) { - return Math.min(Math.max(value, min), max); -} - function getMissingProviderFields(profile) { const provider = profile?.provider || {}; const missing = []; @@ -52,10 +42,11 @@ function buildProviderSummary(profile) { function ProfileStatusPill({ complete }) { return ( {complete ? 'Complete' : 'Missing Fields'} @@ -80,9 +71,6 @@ export default function Providers() { const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const [copiedProfileId, setCopiedProfileId] = useState(''); - const [isDesktopSplit, setIsDesktopSplit] = useState(false); - const [listPaneWidth, setListPaneWidth] = useState(DEFAULT_LIST_PANE_WIDTH); - const layoutRef = useRef(null); const copyTimeoutRef = useRef(null); const globalSmsPath = `/${businessId}/global-sms`; @@ -112,21 +100,6 @@ export default function Providers() { loadProfiles(); }, [loadProfiles]); - useEffect(() => { - const mediaQuery = window.matchMedia(DESKTOP_SPLIT_QUERY); - const syncLayoutMode = (event) => setIsDesktopSplit(event.matches); - - setIsDesktopSplit(mediaQuery.matches); - - if (typeof mediaQuery.addEventListener === 'function') { - mediaQuery.addEventListener('change', syncLayoutMode); - return () => mediaQuery.removeEventListener('change', syncLayoutMode); - } - - mediaQuery.addListener(syncLayoutMode); - return () => mediaQuery.removeListener(syncLayoutMode); - }, []); - useEffect(() => () => { if (copyTimeoutRef.current) { clearTimeout(copyTimeoutRef.current); @@ -165,37 +138,10 @@ export default function Providers() { setSuccess(''); } - function handleResizeStart(event) { - if (!isDesktopSplit) return; - - event.preventDefault(); - const containerBounds = layoutRef.current?.getBoundingClientRect(); - if (!containerBounds) return; - - const maxAllowedWidth = clamp( - containerBounds.width - MIN_DETAIL_PANE_WIDTH, - MIN_LIST_PANE_WIDTH, - MAX_LIST_PANE_WIDTH, - ); - - function handlePointerMove(moveEvent) { - const nextWidth = clamp( - moveEvent.clientX - containerBounds.left, - MIN_LIST_PANE_WIDTH, - maxAllowedWidth, - ); - setListPaneWidth(nextWidth); - } - - function handlePointerUp() { - document.body.style.userSelect = ''; - window.removeEventListener('mousemove', handlePointerMove); - window.removeEventListener('mouseup', handlePointerUp); - } - - document.body.style.userSelect = 'none'; - window.addEventListener('mousemove', handlePointerMove); - window.addEventListener('mouseup', handlePointerUp); + function handleReturnToList() { + setSelectedProfileId(''); + setError(''); + setSuccess(''); } async function handleActivate(profile) { @@ -274,19 +220,19 @@ export default function Providers() { if (loading) { return ( -
-
+
+
); } return ( -
+

Provider Configuration

- Pick a saved profile to review its complete request and manage the provider details stored against it. + Review the provider details stored against each saved cURL profile.

+
)} {success && ( -
+
{success} - +
)} -
+ {!selectedProfile ? (
@@ -343,11 +289,10 @@ export default function Providers() {
) : ( -
+
{profiles.map((profile) => { const isActive = profile.id === activeProfileId; - const isSelected = profile.id === selectedProfileId; const complete = isProviderSetupComplete(profile); return ( @@ -355,15 +300,12 @@ export default function Providers() { key={profile.id} type="button" onClick={() => handleSelectProfile(profile.id)} - className={`w-full rounded-xl border p-4 text-left transition ${isSelected - ? 'border-primary-blue bg-indigo-50/40 shadow-sm' - : 'border-gray-200 bg-white hover:border-primary-blue hover:bg-gray-50' - }`} + className="w-full rounded-xl border border-gray-200 bg-white p-4 text-left transition hover:border-primary-blue hover:bg-gray-50" >

{profile.name}

-

{buildProviderSummary(profile)}

+

{buildProviderSummary(profile)}

{isActive && ( @@ -387,203 +329,195 @@ export default function Providers() {
)}
+ ) : ( +
+
+
+ - {isDesktopSplit && ( -
- -
- )} +
+
+
+

{selectedProfile.name}

+ {selectedProfile.id === activeProfileId && ( + + Active profile + + )} + +
+

+ Review the exact saved request, then update the provider fields tied to this profile. +

+
-
- {!selectedProfile ? ( -
-
-

Select a profile

-

Choose a saved profile to review

-

- The selected profile will open here with a complete cURL preview, provider details, and activation controls. -

+
+ {selectedProfile.id !== activeProfileId && ( + + )} + + +
- ) : ( - <> -
-
+
+ +
+
+
+
+

Preview

+
+ + Updated {formatUpdatedAt(selectedProfile.updatedAt)} + +
+
+                {selectedProfile.rawCurl}
+              
+
+ +
+
+
+

Provider Details

+

+ These fields are stored against this profile and are used during template publishing. +

+
+ +
-
-

{selectedProfile.name}

- {selectedProfile.id === activeProfileId && ( - - Active profile - - )} - + + handleChange('providerName', event.target.value)} + className={`w-full rounded-lg border px-4 py-2 text-sm font-medium text-text-primary placeholder-placeholder-bg transition focus:border-transparent focus:outline-none focus:ring-2 ${!form.providerName ? 'border-error-text focus:ring-error-text' : 'border-border-main focus:ring-primary-blue'} bg-surface-white`} + placeholder="e.g. MSG91, Gupshup" + /> +
+ +
+
+ + handleChange('senderId', event.target.value.toUpperCase())} + maxLength={6} + className={`w-full rounded-lg border px-4 py-2 font-mono text-sm uppercase tracking-widest text-text-primary placeholder-placeholder-bg transition focus:border-transparent focus:outline-none focus:ring-2 ${!form.senderId ? 'border-error-text focus:ring-error-text' : 'border-border-main focus:ring-primary-blue'} bg-surface-white`} + placeholder="6 CHARS" + /> +

Exactly 6 alphabetic characters.

-

- Review the exact saved request, then update the provider fields tied to this profile. + +

+ + handleChange('dltEntityId', event.target.value)} + className={`w-full rounded-lg border px-4 py-2 font-mono text-sm text-text-primary placeholder-placeholder-bg transition focus:border-transparent focus:outline-none focus:ring-2 ${!form.dltEntityId ? 'border-error-text focus:ring-error-text' : 'border-border-main focus:ring-primary-blue'} bg-surface-white`} + placeholder="19-digit DLT PE ID" + /> +
+
+ +
+ + handleChange('authKey', event.target.value)} + className="w-full rounded-lg border border-border-main bg-surface-white px-4 py-2 font-mono text-sm text-text-primary placeholder-placeholder-bg transition focus:border-transparent focus:outline-none focus:ring-2 focus:ring-primary-blue" + placeholder="Authorization key for your SMS provider" + /> +

+ Used as the Authorization header in your SMS requests.

- -
- {selectedProfile.id !== activeProfileId && ( - - )} - - -
-
-
- -
-
-
-
-

Preview

-
- - Updated {formatUpdatedAt(selectedProfile.updatedAt)} - -
-
-                    {selectedProfile.rawCurl}
-                  
-
- -
-

Provider Details

-

- These fields are stored against this profile and are used during template publishing. -

-
- -
-
- - handleChange('providerName', event.target.value)} - className={`w-full px-4 py-2 rounded-lg bg-surface-white border ${!form.providerName ? 'border-error-text focus:ring-error-text' : 'border-border-main focus:ring-primary-blue'} text-text-primary placeholder-placeholder-bg font-medium focus:outline-none focus:ring-2 focus:border-transparent transition text-sm`} - placeholder="e.g. MSG91, Gupshup" - /> -
- -
-
- - handleChange('senderId', event.target.value.toUpperCase())} - maxLength={6} - className={`w-full px-4 py-2 rounded-lg bg-surface-white border ${!form.senderId ? 'border-error-text focus:ring-error-text' : 'border-border-main focus:ring-primary-blue'} text-text-primary font-mono tracking-widest placeholder-placeholder-bg focus:outline-none focus:ring-2 focus:border-transparent transition text-sm uppercase`} - placeholder="6 CHARS" - /> -

Exactly 6 alphabetic characters.

-
- -
- - handleChange('dltEntityId', event.target.value)} - className={`w-full px-4 py-2 rounded-lg bg-surface-white border ${!form.dltEntityId ? 'border-error-text focus:ring-error-text' : 'border-border-main focus:ring-primary-blue'} text-text-primary font-mono placeholder-placeholder-bg focus:outline-none focus:ring-2 focus:border-transparent transition text-sm`} - placeholder="19-digit DLT PE ID" - /> -
-
- -
- - handleChange('authKey', event.target.value)} - className="w-full px-4 py-2 rounded-lg bg-surface-white border border-border-main text-text-primary font-mono placeholder-placeholder-bg focus:outline-none focus:ring-2 focus:ring-primary-blue focus:border-transparent transition text-sm" - placeholder="Authorization key for your SMS provider" - /> -

Used as the Authorization header in your SMS requests.

-
-
- -
- -
- - - +
+
-
- - )} + + + +
+
-
+ )}
); } diff --git a/client/src/pages/Templates.jsx b/client/src/pages/Templates.jsx index a9e2a6b..1aa81fe 100644 --- a/client/src/pages/Templates.jsx +++ b/client/src/pages/Templates.jsx @@ -278,7 +278,7 @@ export default function Templates() {

) : ( -
+
{visibleTemplates.map((template) => { const appearance = getCardAppearance(template); const boundProfile = template.curlProfileId ? profilesById[template.curlProfileId] || null : null; @@ -297,26 +297,26 @@ export default function Templates() { delete templateCardRefs.current[template.eventSlug]; } }} - className={`overflow-hidden rounded-[28px] border border-gray-200 border-l-4 bg-white px-6 py-5 shadow-[0_12px_30px_rgba(15,23,42,0.06)] transition-all duration-300 ${appearance.accentClassName} ${ + className={`overflow-hidden rounded-[22px] border border-gray-200 border-l-4 bg-white px-4 py-3.5 shadow-[0_8px_22px_rgba(15,23,42,0.05)] transition-all duration-300 ${appearance.accentClassName} ${ highlightedEventSlug === template.eventSlug ? 'ring-2 ring-primary-blue/30' - : 'hover:-translate-y-0.5 hover:shadow-[0_16px_34px_rgba(15,23,42,0.08)]' + : 'hover:-translate-y-0.5 hover:shadow-[0_12px_24px_rgba(15,23,42,0.07)]' }`} > -
+
-

+

{getTemplateDisplayName(template)}

- + {isPublished && ( - + )} {appearance.pillLabel}
-

+

{appearance.description}

@@ -328,38 +328,38 @@ export default function Templates() { aria-label={`Set runtime ${isRuntimeEnabled ? 'paused' : 'active'} for ${getTemplateDisplayName(template)}`} disabled={!isPublished || isRuntimeUpdating} onClick={() => isPublished && handleRuntimeToggle(template)} - className={`relative inline-flex h-7 w-12 items-center rounded-full border transition ${ + className={`relative inline-flex h-6 w-10 items-center rounded-full border transition ${ isPublished ? `${appearance.switchTrackClassName} ${isRuntimeUpdating ? 'cursor-wait opacity-70' : 'cursor-pointer'}` : 'cursor-not-allowed border-[#d8dee8] bg-[#eef1f5] opacity-95' }`} >
-
-
-
+
+
+
-

Profile

-

+

Profile

+

{getBoundProfileSummary(template, boundProfile)}

-

DLT Template ID

-

+

DLT Template ID

+

{formatDltTemplateId(template.templateId)}

-
+
@@ -382,7 +382,7 @@ export default function Templates() { @@ -391,7 +391,7 @@ export default function Templates() {
{isBoundProfileMissing && ( -

+

{template.curlProfileId ? 'The cURL profile used for this template no longer exists. Re-select this template from Events to continue.' : 'This template is not bound to a cURL profile. Re-select it from Events to continue.'}