Fallback changes, API is not working
This commit is contained in:
parent
b5ebb830d8
commit
37792c1704
|
|
@ -42,7 +42,7 @@ function BusinessGuard({ children, isGlobalSmsRoute }) {
|
|||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-page-bg">
|
||||
<div className="w-8 h-8 border-2 border-indigo-200 border-t-primary-blue rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-refresh-active border-t-primary-blue rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,14 +37,14 @@ export default function ImagePicker({ currentImage, onSelect }) {
|
|||
onClick={() => onSelect(img.url)}
|
||||
className={`relative rounded-lg overflow-hidden border-2 aspect-video transition-all ${
|
||||
isSelected
|
||||
? 'border-indigo-600 ring-2 ring-indigo-600 ring-opacity-50 shadow-md'
|
||||
? 'border-primary-blue ring-2 ring-primary-blue ring-opacity-50 shadow-md'
|
||||
: 'border-transparent hover:border-gray-300 opacity-80 hover:opacity-100 shadow-sm'
|
||||
}`}
|
||||
>
|
||||
<img src={img.url} alt={`brand-pic-${i}`} className="w-full h-full object-cover" />
|
||||
<div className={`absolute inset-0 bg-indigo-600/20 transition-opacity ${isSelected ? 'opacity-100' : 'opacity-0'}`} />
|
||||
<div className={`absolute inset-0 bg-primary-blue/20 transition-opacity ${isSelected ? 'opacity-100' : 'opacity-0'}`} />
|
||||
{isSelected && (
|
||||
<div className="absolute top-1.5 right-1.5 w-5 h-5 bg-indigo-600 rounded-full flex items-center justify-center shadow-lg border-2 border-white">
|
||||
<div className="absolute top-1.5 right-1.5 w-5 h-5 bg-primary-blue rounded-full flex items-center justify-center shadow-sm border-2 border-white">
|
||||
<svg className="w-3 h-3 text-white" viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /></svg>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,9 @@
|
|||
import { useState } from 'react';
|
||||
import apiClient from '../api/client';
|
||||
import {
|
||||
getBusinessDomain,
|
||||
getBusinessImage,
|
||||
getBusinessName,
|
||||
getBusinessTagline,
|
||||
} from '../utils/businessProfile';
|
||||
|
||||
export default function RegisterBusinessModal({ onClose }) {
|
||||
const [url, setUrl] = useState('');
|
||||
const [status, setStatus] = useState('idle');
|
||||
const [createdBusiness, setCreatedBusiness] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
async function handleSubmit(e) {
|
||||
|
|
@ -21,10 +14,9 @@ export default function RegisterBusinessModal({ onClose }) {
|
|||
setError('');
|
||||
|
||||
try {
|
||||
const res = await apiClient.post('/api/businesses', {
|
||||
await apiClient.post('/api/businesses', {
|
||||
websiteUrl: url.trim(),
|
||||
});
|
||||
setCreatedBusiness(res.data);
|
||||
setStatus('success');
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.error || 'Something went wrong. Please try again.');
|
||||
|
|
@ -32,45 +24,18 @@ export default function RegisterBusinessModal({ onClose }) {
|
|||
}
|
||||
}
|
||||
|
||||
const successName = getBusinessName(createdBusiness);
|
||||
const successDomain = getBusinessDomain(createdBusiness);
|
||||
const successTagline = getBusinessTagline(createdBusiness);
|
||||
const successImage = getBusinessImage(createdBusiness);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-8 w-full max-w-md shadow-xl">
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-8 w-full max-w-md shadow-sm">
|
||||
|
||||
{status === 'success' && (
|
||||
<div className="text-center">
|
||||
<div className="w-14 h-14 rounded-full bg-green-50 text-green-600 flex items-center justify-center mx-auto mb-4 text-2xl">✓</div>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-2">Business Added!</h2>
|
||||
<p className="text-gray-500 text-sm mb-4 font-medium">Brand detected and ready for onboarding.</p>
|
||||
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-4 mb-6 text-left">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-16 h-16 rounded-2xl overflow-hidden bg-white border border-gray-200 shadow-sm shrink-0 flex items-center justify-center">
|
||||
{successImage ? (
|
||||
<img src={successImage} alt={successName} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<span className="text-xl font-bold text-indigo-600">
|
||||
{successName?.[0]?.toUpperCase() || 'B'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-indigo-600 font-bold text-lg tracking-tight truncate">{successName}</p>
|
||||
{successDomain && (
|
||||
<p className="text-sm text-gray-500 font-medium truncate mt-0.5">{successDomain}</p>
|
||||
)}
|
||||
{successTagline && (
|
||||
<p className="text-sm text-gray-700 mt-2 leading-relaxed line-clamp-2">{successTagline}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-500 text-sm mb-6 font-medium">Your business has been registered successfully.</p>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-full py-2.5 rounded-lg bg-indigo-600 hover:bg-indigo-700 shadow-sm text-white font-medium transition"
|
||||
className="w-full py-2.5 rounded-lg bg-primary-blue hover:bg-primary-dark shadow-sm text-white font-medium transition"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
|
|
@ -82,7 +47,7 @@ export default function RegisterBusinessModal({ onClose }) {
|
|||
<div className="mb-6">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-2 tracking-tight">Add a Business</h2>
|
||||
<p className="text-gray-500 text-sm leading-relaxed">
|
||||
Enter the storefront website URL and we'll scrape it to detect the brand, images, and copy you need for onboarding.
|
||||
Enter the storefront website URL and we'll scrape it to detect the brand and set up your business.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -95,7 +60,7 @@ export default function RegisterBusinessModal({ onClose }) {
|
|||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder="https://yourstore.com"
|
||||
disabled={status === 'loading'}
|
||||
className="w-full px-4 py-2.5 rounded-lg bg-white border border-gray-300 text-gray-900 placeholder-gray-400 font-medium focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent transition disabled:opacity-50 text-sm shadow-sm"
|
||||
className="w-full px-4 py-2.5 rounded-lg bg-white border border-gray-300 text-gray-900 placeholder-gray-400 font-medium focus:outline-none focus:ring-2 focus:ring-primary-blue focus:border-transparent transition disabled:opacity-50 text-sm shadow-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -116,7 +81,7 @@ export default function RegisterBusinessModal({ onClose }) {
|
|||
<button
|
||||
type="submit"
|
||||
disabled={status === 'loading' || !url.trim()}
|
||||
className="flex-[1.2] py-2.5 rounded-lg bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium transition shadow-sm disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
className="flex-[1.2] py-2.5 rounded-lg bg-primary-blue hover:bg-primary-dark text-white text-sm font-medium transition shadow-sm disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
{status === 'loading' ? (
|
||||
<><span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> Analysing…</>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const SVG_ICONS = {
|
|||
function TopLevelStatus({ done, active }) {
|
||||
if (done) {
|
||||
return (
|
||||
<span className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-indigo-600 text-white shadow-sm">
|
||||
<span className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-primary-blue text-white shadow-sm">
|
||||
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
|
|
@ -31,7 +31,7 @@ function TopLevelStatus({ done, active }) {
|
|||
}
|
||||
|
||||
if (active) {
|
||||
return <span className="inline-block h-2.5 w-2.5 rounded-full bg-indigo-600 shadow-sm" />;
|
||||
return <span className="inline-block h-2.5 w-2.5 rounded-full bg-primary-blue shadow-sm" />;
|
||||
}
|
||||
|
||||
return <span className="inline-block h-2.5 w-2.5 rounded-full bg-gray-200" />;
|
||||
|
|
@ -40,7 +40,7 @@ function TopLevelStatus({ done, active }) {
|
|||
function StageMarker({ done, active, enabled }) {
|
||||
if (done) {
|
||||
return (
|
||||
<span className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-indigo-600 text-white shadow-sm">
|
||||
<span className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-primary-blue text-white shadow-sm">
|
||||
<svg className="h-2.5 w-2.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
|
|
@ -49,14 +49,14 @@ function StageMarker({ done, active, enabled }) {
|
|||
}
|
||||
|
||||
if (active) {
|
||||
return <span className="inline-block h-3 w-3 rounded-full border-2 border-white bg-[#8b2bfa] shadow-[0_0_0_1px_rgba(139,43,250,0.2)]" />;
|
||||
return <span className="inline-block h-3 w-3 rounded-full border-2 border-white bg-primary-blue shadow-[0_0_0_1px_var(--color-primary-blue)]" />;
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
return <span className="inline-block h-3 w-3 rounded-full bg-gray-200" />;
|
||||
}
|
||||
|
||||
return <span className="inline-block h-3 w-3 rounded-full bg-indigo-200" />;
|
||||
return <span className="inline-block h-3 w-3 rounded-full bg-refresh-active" />;
|
||||
}
|
||||
|
||||
export default function Sidebar() {
|
||||
|
|
@ -133,7 +133,7 @@ export default function Sidebar() {
|
|||
</button>
|
||||
{activeBusiness && (
|
||||
<div className="mt-4 flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-md bg-indigo-600 flex items-center justify-center text-sm font-bold text-white shrink-0 shadow-sm">
|
||||
<div className="w-8 h-8 rounded-md bg-primary-blue flex items-center justify-center text-sm font-bold text-white shrink-0 shadow-sm">
|
||||
{activeBusiness.brandName?.[0]?.toUpperCase() || 'B'}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
|
|
@ -161,7 +161,7 @@ export default function Sidebar() {
|
|||
{item.enabled ? (
|
||||
<NavLink
|
||||
to={item.to}
|
||||
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-semibold transition-colors duration-150 ${
|
||||
className={`flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors duration-150 ${
|
||||
item.active
|
||||
? 'bg-gray-100/70 text-gray-900'
|
||||
: 'text-gray-500 hover:text-gray-900 hover:bg-gray-50'
|
||||
|
|
@ -186,7 +186,7 @@ export default function Sidebar() {
|
|||
) : (
|
||||
<div
|
||||
aria-disabled="true"
|
||||
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-semibold text-gray-300 cursor-not-allowed select-none"
|
||||
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium text-gray-300 cursor-not-allowed select-none"
|
||||
>
|
||||
{SVG_ICONS[item.id]}
|
||||
<span className="flex-1 truncate">{item.label}</span>
|
||||
|
|
@ -203,12 +203,12 @@ export default function Sidebar() {
|
|||
{item.substeps.map((substep) => (
|
||||
<div key={substep.id} className="relative flex items-center pr-3 group cursor-default">
|
||||
<div className="w-[44px] flex justify-center items-center shrink-0">
|
||||
{substep.active && <div className="w-1.5 h-1.5 rounded-full bg-[#8b2bfa] z-10 shadow-[0_0_0_2px_white]" />}
|
||||
{substep.active && <div className="w-1.5 h-1.5 rounded-full bg-primary-blue z-10 shadow-[0_0_0_2px_white]" />}
|
||||
</div>
|
||||
<div
|
||||
className={`flex-1 px-3 py-2.5 rounded-[12px] text-[14px] transition-colors ${
|
||||
className={`flex-1 px-3 py-2.5 rounded-md text-[14px] transition-colors ${
|
||||
substep.active
|
||||
? 'bg-[#f5eeff] text-[#8b2bfa] font-semibold'
|
||||
? 'bg-refresh-hover text-primary-blue font-semibold'
|
||||
: 'text-gray-500 font-medium hover:text-gray-900 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default function TestSmsModal({ businessId, template, onClose }) {
|
|||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-8 w-full max-w-md shadow-xl">
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-8 w-full max-w-md shadow-sm">
|
||||
<div className="w-12 h-12 rounded-full bg-green-50 flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-xl">📱</span>
|
||||
</div>
|
||||
|
|
@ -52,7 +52,7 @@ export default function TestSmsModal({ businessId, template, onClose }) {
|
|||
value={toNumber}
|
||||
onChange={e => setToNumber(e.target.value)}
|
||||
placeholder="e.g. 919876543210 (with country code)"
|
||||
className="w-full px-4 py-2.5 rounded-lg bg-gray-50 border border-gray-300 font-mono text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-600 text-sm"
|
||||
className="w-full px-4 py-2.5 rounded-lg bg-gray-50 border border-gray-300 font-mono text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-blue text-sm"
|
||||
autoFocus
|
||||
required
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export default function WhitelistModal({ businessId, template, boundProfile, onC
|
|||
|
||||
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="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>
|
||||
|
|
|
|||
|
|
@ -3,38 +3,38 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-primary-blue: #4F5BD5;
|
||||
--color-primary-dark: #2F3DB9;
|
||||
--color-link-blue: #5B72D7;
|
||||
--color-primary-blue: #3838C4;
|
||||
--color-primary-dark: #2B2BA3;
|
||||
--color-link-blue: #4E4ED6;
|
||||
--color-page-bg: #F5F6F8;
|
||||
--color-surface-white: #FFFFFF;
|
||||
--color-table-header: #F4F5F7;
|
||||
--color-pagination-bg: #F8F9FB;
|
||||
--color-refresh-hover: #F4F6FF;
|
||||
--color-refresh-active: #E9EDFF;
|
||||
--color-row-hover: #EEF0F8;
|
||||
--color-text-primary: #3F434C;
|
||||
--color-text-muted: #8B9098;
|
||||
--color-header-text: #4B4F57;
|
||||
--color-channel-name: #61656D;
|
||||
--color-border-main: #E2E5EA;
|
||||
--color-border-soft: #ECEFF3;
|
||||
--color-item-border: #ECEEF2;
|
||||
--color-badge-bg: #F0FAEF;
|
||||
--color-badge-text: #5FA05A;
|
||||
--color-badge-border: #9FCF9B;
|
||||
--color-delayed-bg: #FFF1F0;
|
||||
--color-delayed-text: #D94F4F;
|
||||
--color-delayed-border: #F5A5A5;
|
||||
--color-tags-bg: #FFF8F3;
|
||||
--color-tags-text: #E58D4F;
|
||||
--color-tags-border: #F2B17F;
|
||||
--color-error-text: #C62828;
|
||||
--color-sla-green: #4CAF50;
|
||||
--color-sla-grey: #BDBDBD;
|
||||
--color-spinner-track: #D8DEFE;
|
||||
--color-placeholder-bg: #E0E0E0;
|
||||
--color-empty-bg: #FAFAFA;
|
||||
--color-table-header: #F9FAFB;
|
||||
--color-pagination-bg: #FFFFFF;
|
||||
--color-refresh-hover: #F0F0FA;
|
||||
--color-refresh-active: #E0E0F5;
|
||||
--color-row-hover: #F9FAFB;
|
||||
--color-text-primary: #1F2937;
|
||||
--color-text-muted: #6B7280;
|
||||
--color-header-text: #4B5563;
|
||||
--color-channel-name: #4B5563;
|
||||
--color-border-main: #E5E7EB;
|
||||
--color-border-soft: #F3F4F6;
|
||||
--color-item-border: #E5E7EB;
|
||||
--color-badge-bg: #F0FDF4;
|
||||
--color-badge-text: #166534;
|
||||
--color-badge-border: #BBF7D0;
|
||||
--color-delayed-bg: #FEF2F2;
|
||||
--color-delayed-text: #991B1B;
|
||||
--color-delayed-border: #FECACA;
|
||||
--color-tags-bg: #FFFBEB;
|
||||
--color-tags-text: #92400E;
|
||||
--color-tags-border: #FDE68A;
|
||||
--color-error-text: #DC2626;
|
||||
--color-sla-green: #22C55E;
|
||||
--color-sla-grey: #D1D5DB;
|
||||
--color-spinner-track: #E0E7FF;
|
||||
--color-placeholder-bg: #D1D5DB;
|
||||
--color-empty-bg: #F9FAFB;
|
||||
}
|
||||
|
||||
:root {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const NAV_CARDS = [
|
|||
function DeleteConfirmModal({ brandName, onCancel, onConfirm, deleting }) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-8 w-full max-w-md shadow-xl">
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-8 w-full max-w-md shadow-sm">
|
||||
<div className="w-12 h-12 rounded-full bg-red-50 flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-xl">🗑</span>
|
||||
</div>
|
||||
|
|
@ -65,7 +65,7 @@ export default function Brand() {
|
|||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="w-8 h-8 border-2 border-indigo-200 border-t-indigo-600 rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-refresh-active border-t-indigo-600 rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ export default function Brand() {
|
|||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="text-center max-w-lg px-8">
|
||||
<div className="w-16 h-16 rounded-2xl bg-indigo-600 flex items-center justify-center mx-auto mb-6 text-2xl font-bold text-white shadow-lg shadow-indigo-600/20">
|
||||
<div className="w-16 h-16 rounded-lg bg-primary-blue flex items-center justify-center mx-auto mb-6 text-2xl font-bold text-white shadow-sm">
|
||||
S
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-3 tracking-tight">SMS Template Extension</h1>
|
||||
|
|
@ -84,7 +84,7 @@ export default function Brand() {
|
|||
</p>
|
||||
<button
|
||||
onClick={() => setShowModal(true)}
|
||||
className="px-8 py-3 rounded-lg bg-indigo-600 hover:bg-indigo-700 text-white font-medium shadow-sm transition-all focus:ring-2 focus:ring-offset-2 focus:ring-indigo-600"
|
||||
className="px-8 py-3 rounded-lg bg-primary-blue hover:bg-primary-dark text-white font-medium shadow-sm transition-all focus:ring-2 focus:ring-offset-2 focus:ring-primary-blue"
|
||||
>
|
||||
Register Your Business
|
||||
</button>
|
||||
|
|
@ -100,13 +100,13 @@ export default function Brand() {
|
|||
<div className="max-w-4xl mx-auto">
|
||||
|
||||
{/* Brand header card */}
|
||||
<div className="rounded-xl bg-white border border-gray-200 shadow-sm p-8 mb-6">
|
||||
<div className="rounded-lg bg-white border border-gray-200 shadow-sm p-8 mb-6">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h1 className="text-3xl font-bold text-gray-900 tracking-tight truncate">{brand.brandName}</h1>
|
||||
<p className="text-gray-500 mt-1 text-sm font-medium">{brand.domain}</p>
|
||||
<div className="flex items-center gap-2 mt-4 flex-wrap">
|
||||
<span className="text-xs px-2.5 py-1 rounded-md bg-indigo-50 border border-indigo-100 text-indigo-700 font-medium capitalize">
|
||||
<span className="text-xs px-2.5 py-1 rounded-md bg-refresh-hover border border-refresh-active text-primary-dark font-medium capitalize">
|
||||
{brand.tone}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400 font-medium tracking-wide">
|
||||
|
|
@ -156,7 +156,7 @@ export default function Brand() {
|
|||
|
||||
{/* Brand images */}
|
||||
{brand.relevantImagePaths?.length > 0 && (
|
||||
<div className="rounded-xl bg-white border border-gray-200 shadow-sm p-6 mb-6">
|
||||
<div className="rounded-lg bg-white border border-gray-200 shadow-sm p-6 mb-6">
|
||||
<p className="text-xs text-gray-400 font-semibold uppercase tracking-wider mb-4">Brand Images</p>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
||||
{brand.relevantImagePaths.map((url, i) => (
|
||||
|
|
@ -182,7 +182,7 @@ export default function Brand() {
|
|||
<Link
|
||||
key={card.to}
|
||||
to={card.to}
|
||||
className="rounded-xl bg-white border border-gray-200 shadow-sm p-5 hover:border-indigo-300 hover:ring-1 hover:ring-indigo-300 transition-all group"
|
||||
className="rounded-lg bg-white border border-gray-200 shadow-sm p-5 hover:border-primary-blue hover:ring-1 hover:ring-primary-blue transition-all group"
|
||||
>
|
||||
<div className="text-2xl mb-3 grayscale group-hover:grayscale-0 opacity-80 group-hover:opacity-100 transition-all">{card.icon}</div>
|
||||
<p className="font-semibold text-gray-900 mb-1">{card.label}</p>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
function DeleteConfirmModal({ businessName, onCancel, onConfirm, deleting }) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-8 w-full max-w-md shadow-xl">
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-8 w-full max-w-md shadow-sm">
|
||||
<div className="w-12 h-12 rounded-full bg-red-50 flex items-center justify-center mx-auto mb-4">
|
||||
<span className="text-xl">🗑</span>
|
||||
</div>
|
||||
|
|
@ -52,21 +52,21 @@ function BusinessCreatedModal({ business, onClose }) {
|
|||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm">
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-8 w-full max-w-md shadow-xl">
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-8 w-full max-w-md shadow-sm">
|
||||
<div className="w-14 h-14 rounded-full bg-green-50 text-green-600 flex items-center justify-center mx-auto mb-4 text-2xl">✓</div>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-2 text-center">Business Added!</h2>
|
||||
<p className="text-gray-500 text-sm mb-4 font-medium text-center">Your business is ready for onboarding.</p>
|
||||
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-4 mb-6">
|
||||
<div className="rounded-lg border border-gray-200 bg-gray-50 p-4 mb-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-16 h-16 rounded-2xl overflow-hidden bg-white border border-gray-200 shadow-sm shrink-0 flex items-center justify-center">
|
||||
<div className="w-16 h-16 rounded-lg overflow-hidden bg-white border border-gray-200 shadow-sm shrink-0 flex items-center justify-center">
|
||||
{image ? (
|
||||
<img src={image} alt={name} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<span className="text-xl font-bold text-indigo-600">{name?.[0]?.toUpperCase() || 'B'}</span>
|
||||
<span className="text-xl font-bold text-primary-blue">{name?.[0]?.toUpperCase() || 'B'}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-indigo-600 font-bold text-lg tracking-tight truncate">{name}</p>
|
||||
<p className="text-primary-blue font-bold text-lg tracking-tight truncate">{name}</p>
|
||||
{domain && <p className="text-sm text-gray-500 font-medium truncate mt-0.5">{domain}</p>}
|
||||
{tagline && <p className="text-sm text-gray-700 mt-2 leading-relaxed line-clamp-2">{tagline}</p>}
|
||||
</div>
|
||||
|
|
@ -74,7 +74,7 @@ function BusinessCreatedModal({ business, onClose }) {
|
|||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-full py-2.5 rounded-lg bg-indigo-600 hover:bg-indigo-700 shadow-sm text-white font-medium transition"
|
||||
className="w-full py-2.5 rounded-lg bg-primary-blue hover:bg-primary-dark shadow-sm text-white font-medium transition"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
|
|
@ -89,13 +89,13 @@ function SalesChannelCard({ channel, disabled, onImport }) {
|
|||
const image = getBusinessImage(channel);
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-4 shadow-sm transition hover:border-indigo-300 hover:shadow-md">
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm transition hover:border-primary-blue hover:shadow-md">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-12 h-12 rounded-xl overflow-hidden bg-gray-50 border border-gray-200 shrink-0 flex items-center justify-center">
|
||||
<div className="w-12 h-12 rounded-lg overflow-hidden bg-gray-50 border border-gray-200 shrink-0 flex items-center justify-center">
|
||||
{image ? (
|
||||
<img src={image} alt={name} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<span className="text-sm font-bold text-indigo-600">{name?.[0]?.toUpperCase() || 'S'}</span>
|
||||
<span className="text-sm font-bold text-primary-blue">{name?.[0]?.toUpperCase() || 'S'}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
|
|
@ -126,7 +126,6 @@ export default function Businesses() {
|
|||
const [salesChannels, setSalesChannels] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [salesChannelsStatus, setSalesChannelsStatus] = useState('loading');
|
||||
const [salesChannelsError, setSalesChannelsError] = useState('');
|
||||
const [salesChannelQuery, setSalesChannelQuery] = useState('');
|
||||
const [selectingBusinessId, setSelectingBusinessId] = useState('');
|
||||
const [creatingSalesChannelId, setCreatingSalesChannelId] = useState('');
|
||||
|
|
@ -147,6 +146,7 @@ export default function Businesses() {
|
|||
const availableSalesChannels = useMemo(() => (
|
||||
salesChannels.filter((channel) => !configuredApplicationIds.has(getChannelId(channel)))
|
||||
), [configuredApplicationIds, salesChannels]);
|
||||
const showSalesChannelsSection = salesChannelsStatus === 'success' && availableSalesChannels.length > 0;
|
||||
|
||||
const filteredSalesChannels = useMemo(() => {
|
||||
const query = salesChannelQuery.trim().toLowerCase();
|
||||
|
|
@ -174,7 +174,6 @@ export default function Businesses() {
|
|||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSalesChannelsError('');
|
||||
|
||||
try {
|
||||
const [businessesRes, salesChannelsRes] = await Promise.allSettled([
|
||||
|
|
@ -189,9 +188,6 @@ export default function Businesses() {
|
|||
if (salesChannelsRes.status === 'rejected') {
|
||||
setSalesChannels([]);
|
||||
setSalesChannelsStatus('error');
|
||||
setSalesChannelsError(
|
||||
salesChannelsRes.reason?.message || 'Active sales channels could not be loaded right now.'
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
@ -218,13 +214,12 @@ export default function Businesses() {
|
|||
if (!applicationId) return;
|
||||
|
||||
if (!channel.websiteUrl) {
|
||||
setSalesChannelsError('A website URL could not be derived from this sales channel. Please use Add Business and enter the URL manually.');
|
||||
setError('A website URL could not be derived from this sales channel. Please use Add Business and enter the URL manually.');
|
||||
return;
|
||||
}
|
||||
|
||||
setCreatingSalesChannelId(applicationId);
|
||||
setError('');
|
||||
setSalesChannelsError('');
|
||||
|
||||
try {
|
||||
const res = await apiClient.post('/api/businesses', {
|
||||
|
|
@ -258,7 +253,7 @@ export default function Businesses() {
|
|||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="w-8 h-8 border-2 border-indigo-200 border-t-indigo-600 rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-refresh-active border-t-indigo-600 rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -272,12 +267,14 @@ export default function Businesses() {
|
|||
{businesses.length > 0 ? 'Your Businesses' : 'Set Up Your First Business'}
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
Import from an active sales channel when available, or use the website URL fallback to scrape manually.
|
||||
{showSalesChannelsSection
|
||||
? 'Import from an active sales channel when available, or use the website URL fallback to scrape manually.'
|
||||
: 'Add a storefront URL and we’ll scrape it to set up your business.'}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowModal(true)}
|
||||
className="px-4 py-2.5 rounded-lg bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-semibold shadow-sm transition"
|
||||
className="px-4 py-2.5 rounded-lg bg-primary-blue hover:bg-primary-dark text-white text-sm font-semibold shadow-sm transition"
|
||||
>
|
||||
+ Add Business
|
||||
</button>
|
||||
|
|
@ -301,7 +298,7 @@ export default function Businesses() {
|
|||
{businesses.map((biz) => (
|
||||
<div
|
||||
key={biz.businessId}
|
||||
className="group rounded-xl bg-white border border-gray-200 shadow-sm hover:border-indigo-300 hover:ring-1 hover:ring-indigo-300 transition-all overflow-hidden"
|
||||
className="group rounded-lg bg-white border border-gray-200 shadow-sm hover:border-primary-blue hover:ring-1 hover:ring-primary-blue transition-all overflow-hidden"
|
||||
>
|
||||
<button
|
||||
className="w-full text-left p-6"
|
||||
|
|
@ -309,11 +306,11 @@ export default function Businesses() {
|
|||
disabled={selectingBusinessId === biz.businessId}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-16 h-16 rounded-2xl overflow-hidden bg-gray-50 border border-gray-200 shrink-0 flex items-center justify-center shadow-sm">
|
||||
<div className="w-16 h-16 rounded-lg overflow-hidden bg-gray-50 border border-gray-200 shrink-0 flex items-center justify-center shadow-sm">
|
||||
{getBusinessImage(biz) ? (
|
||||
<img src={getBusinessImage(biz)} alt={getBusinessName(biz)} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<span className="text-lg font-bold text-indigo-600">
|
||||
<span className="text-lg font-bold text-primary-blue">
|
||||
{getBusinessName(biz)?.[0]?.toUpperCase() || 'B'}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -330,8 +327,8 @@ export default function Businesses() {
|
|||
Added {new Date(biz.createdAt).toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' })}
|
||||
</p>
|
||||
{selectingBusinessId === biz.businessId && (
|
||||
<div className="mt-3 inline-flex items-center gap-2 text-xs text-indigo-600 font-semibold">
|
||||
<span className="w-3.5 h-3.5 border-2 border-indigo-200 border-t-indigo-600 rounded-full animate-spin" />
|
||||
<div className="mt-3 inline-flex items-center gap-2 text-xs text-primary-blue font-semibold">
|
||||
<span className="w-3.5 h-3.5 border-2 border-refresh-active border-t-indigo-600 rounded-full animate-spin" />
|
||||
Opening…
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -339,7 +336,7 @@ export default function Businesses() {
|
|||
</div>
|
||||
</button>
|
||||
<div className="px-6 py-3 bg-gray-50 border-t border-gray-100 flex justify-between items-center">
|
||||
<span className="text-xs text-indigo-600 font-semibold group-hover:underline">Click to manage →</span>
|
||||
<span className="text-xs text-primary-blue font-semibold group-hover:underline">Click to manage →</span>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setDeleteTarget(biz); }}
|
||||
className="text-xs text-red-500 hover:text-red-700 font-medium transition"
|
||||
|
|
@ -351,13 +348,14 @@ export default function Businesses() {
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-xl border border-dashed border-gray-300 bg-white p-8 text-center shadow-sm">
|
||||
<div className="rounded-lg border border-dashed border-gray-300 bg-white p-8 text-center shadow-sm">
|
||||
<p className="text-gray-900 font-semibold mb-1">No configured businesses yet.</p>
|
||||
<p className="text-sm text-gray-500">Import from an active sales channel below, or use Add Business to enter a storefront URL manually.</p>
|
||||
<p className="text-sm text-gray-500">Use Add Business to enter a storefront URL and get started.</p>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{showSalesChannelsSection && (
|
||||
<section>
|
||||
<div className="flex items-end justify-between gap-4 mb-4">
|
||||
<div>
|
||||
|
|
@ -366,64 +364,46 @@ export default function Businesses() {
|
|||
</div>
|
||||
<button
|
||||
onClick={() => setShowModal(true)}
|
||||
className="text-sm font-semibold text-indigo-600 hover:text-indigo-700 transition"
|
||||
className="text-sm font-semibold text-primary-blue hover:text-primary-dark transition"
|
||||
>
|
||||
Use website URL fallback
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{salesChannelsError && (
|
||||
<div className="mb-4 px-4 py-3 rounded-md bg-amber-50 border border-amber-200 text-amber-800 font-medium text-sm">
|
||||
{salesChannelsError}
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-1.5">Search Sales Channels</label>
|
||||
<input
|
||||
type="text"
|
||||
value={salesChannelQuery}
|
||||
onChange={(e) => setSalesChannelQuery(e.target.value)}
|
||||
placeholder="Search by channel name or domain"
|
||||
className="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm text-gray-900 placeholder-gray-400 shadow-sm focus:outline-none focus:ring-2 focus:ring-primary-blue focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{salesChannelsStatus === 'loading' ? (
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-8 text-center shadow-sm">
|
||||
<div className="w-8 h-8 border-2 border-indigo-200 border-t-indigo-600 rounded-full animate-spin mx-auto mb-3" />
|
||||
<p className="text-sm text-gray-500 font-medium">Loading active sales channels…</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{filteredSalesChannels.map((channel) => {
|
||||
const channelId = getChannelId(channel);
|
||||
return (
|
||||
<SalesChannelCard
|
||||
key={channelId}
|
||||
channel={channel}
|
||||
disabled={creatingSalesChannelId === channelId}
|
||||
onImport={() => handleCreateFromSalesChannel(channel)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : availableSalesChannels.length > 0 ? (
|
||||
<div className="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-semibold text-gray-700 mb-1.5">Search Sales Channels</label>
|
||||
<input
|
||||
type="text"
|
||||
value={salesChannelQuery}
|
||||
onChange={(e) => setSalesChannelQuery(e.target.value)}
|
||||
placeholder="Search by channel name or domain"
|
||||
className="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm text-gray-900 placeholder-gray-400 shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent"
|
||||
/>
|
||||
|
||||
{filteredSalesChannels.length === 0 && (
|
||||
<div className="mt-4 rounded-lg border border-dashed border-gray-300 bg-gray-50 p-6 text-center">
|
||||
<p className="text-sm font-semibold text-gray-900">No active sales channels matched your search.</p>
|
||||
<p className="text-sm text-gray-500 mt-1">Use the website URL fallback if you want to scrape a storefront directly.</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{filteredSalesChannels.map((channel) => {
|
||||
const channelId = getChannelId(channel);
|
||||
return (
|
||||
<SalesChannelCard
|
||||
key={channelId}
|
||||
channel={channel}
|
||||
disabled={creatingSalesChannelId === channelId}
|
||||
onImport={() => handleCreateFromSalesChannel(channel)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{filteredSalesChannels.length === 0 && (
|
||||
<div className="mt-4 rounded-xl border border-dashed border-gray-300 bg-gray-50 p-6 text-center">
|
||||
<p className="text-sm font-semibold text-gray-900">No active sales channels matched your search.</p>
|
||||
<p className="text-sm text-gray-500 mt-1">Use the website URL fallback if you want to scrape a storefront directly.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-xl border border-dashed border-gray-300 bg-white p-8 text-center shadow-sm">
|
||||
<p className="text-gray-900 font-semibold mb-1">No active sales channels are available right now.</p>
|
||||
<p className="text-sm text-gray-500">Use Add Business to enter a website URL manually and keep moving.</p>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showModal && (
|
||||
|
|
|
|||
|
|
@ -507,7 +507,7 @@ export default function Events() {
|
|||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="w-8 h-8 border-2 border-indigo-200 border-t-indigo-600 rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-refresh-active border-t-indigo-600 rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -533,7 +533,7 @@ export default function Events() {
|
|||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder="Search events"
|
||||
className="w-full rounded-xl border border-gray-300 bg-white py-3 pl-11 pr-10 text-sm font-medium text-gray-900 placeholder-gray-400 shadow-sm transition focus:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-100"
|
||||
className="w-full rounded-lg border border-gray-300 bg-white py-3 pl-11 pr-10 text-sm font-medium text-gray-900 placeholder-gray-400 shadow-sm transition focus:border-primary-blue focus:outline-none focus:ring-2 focus:ring-indigo-100"
|
||||
/>
|
||||
{searchTerm && (
|
||||
<button
|
||||
|
|
@ -550,7 +550,7 @@ export default function Events() {
|
|||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="inline-flex items-center rounded-full border border-indigo-100 bg-indigo-50 px-3 py-2 text-xs font-semibold text-indigo-700">
|
||||
<span className="inline-flex items-center rounded-full border border-refresh-active bg-refresh-hover px-3 py-2 text-xs font-semibold text-primary-dark">
|
||||
{totalVisibleEvents} visible
|
||||
</span>
|
||||
<button
|
||||
|
|
@ -578,18 +578,18 @@ export default function Events() {
|
|||
)}
|
||||
|
||||
{showAddForm && (
|
||||
<form onSubmit={handleAddEvent} className="mb-8 flex gap-3 p-5 rounded-xl bg-gray-50 border border-gray-200 shadow-sm">
|
||||
<form onSubmit={handleAddEvent} className="mb-8 flex gap-3 p-5 rounded-lg bg-gray-50 border border-gray-200 shadow-sm">
|
||||
<input
|
||||
value={newLabel}
|
||||
onChange={(e) => setNewLabel(e.target.value)}
|
||||
placeholder="Event name (e.g. Return Initiated)"
|
||||
className="flex-1 px-4 py-2.5 rounded-lg bg-white border border-gray-300 text-gray-900 placeholder-gray-400 font-medium focus:outline-none focus:ring-2 focus:ring-indigo-600 text-sm shadow-sm"
|
||||
className="flex-1 px-4 py-2.5 rounded-lg bg-white border border-gray-300 text-gray-900 placeholder-gray-400 font-medium focus:outline-none focus:ring-2 focus:ring-primary-blue text-sm shadow-sm"
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={addingEvent || !newLabel.trim()}
|
||||
className="px-6 py-2.5 rounded-lg bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium transition shadow-sm disabled:opacity-50"
|
||||
className="px-6 py-2.5 rounded-lg bg-primary-blue hover:bg-primary-dark text-white text-sm font-medium transition shadow-sm disabled:opacity-50"
|
||||
>
|
||||
{addingEvent ? 'Adding…' : 'Add'}
|
||||
</button>
|
||||
|
|
@ -597,7 +597,7 @@ export default function Events() {
|
|||
)}
|
||||
|
||||
{groupedEvents.length === 0 ? (
|
||||
<div className="rounded-2xl border border-dashed border-gray-300 bg-white px-6 py-12 text-center shadow-sm">
|
||||
<div className="rounded-lg border border-dashed border-gray-300 bg-white px-6 py-12 text-center shadow-sm">
|
||||
<p className="text-base font-semibold text-gray-900">No events match your search.</p>
|
||||
<p className="mt-2 text-sm text-gray-500">Try a different keyword or clear the search to see the full lifecycle list.</p>
|
||||
</div>
|
||||
|
|
@ -607,7 +607,7 @@ export default function Events() {
|
|||
const isExpanded = searchTerm.trim() ? true : !!expandedGroups[group.id];
|
||||
|
||||
return (
|
||||
<section key={group.id} className="overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm">
|
||||
<section key={group.id} className="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleGroup(group.id)}
|
||||
|
|
@ -615,7 +615,7 @@ export default function Events() {
|
|||
>
|
||||
<div className="flex min-w-0 items-start gap-4">
|
||||
<div className={`mt-1 h-3 w-3 rounded-full shadow-sm ${
|
||||
group.id === 'fulfillment' ? 'bg-indigo-500' :
|
||||
group.id === 'fulfillment' ? 'bg-refresh-hover0' :
|
||||
group.id === 'delivery' ? 'bg-sky-500' :
|
||||
group.id === 'cancellations' ? 'bg-rose-500' :
|
||||
group.id === 'returns' ? 'bg-amber-500' :
|
||||
|
|
@ -653,7 +653,7 @@ export default function Events() {
|
|||
const canViewTemplate = templateStatus !== 'unselected';
|
||||
|
||||
return (
|
||||
<div key={event.slug} className="rounded-xl bg-white border border-gray-200 shadow-sm overflow-hidden">
|
||||
<div key={event.slug} className="rounded-lg bg-white border border-gray-200 shadow-sm overflow-hidden">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between px-6 py-5 gap-4">
|
||||
<div className="flex items-start gap-4">
|
||||
{event.isDefault ? (
|
||||
|
|
@ -697,11 +697,11 @@ export default function Events() {
|
|||
className={`px-4 py-2 rounded-lg text-sm font-medium transition shadow-sm flex items-center gap-2 disabled:opacity-50 ${
|
||||
state === 'done' || state === 'selected'
|
||||
? 'bg-white border border-gray-300 text-gray-700 hover:bg-gray-50'
|
||||
: 'bg-indigo-50 border border-indigo-200 text-indigo-700 hover:bg-indigo-100'
|
||||
: 'bg-refresh-hover border border-refresh-active text-primary-dark hover:bg-indigo-100'
|
||||
}`}
|
||||
>
|
||||
{state === 'loading' ? (
|
||||
<><span className="w-4 h-4 border-2 border-indigo-300 border-t-indigo-600 rounded-full animate-spin" /> Generating…</>
|
||||
<><span className="w-4 h-4 border-2 border-primary-blue border-t-indigo-600 rounded-full animate-spin" /> Generating…</>
|
||||
) : state === 'done' || state === 'selected' ? (
|
||||
<>↺ Regenerate</>
|
||||
) : (
|
||||
|
|
@ -743,9 +743,9 @@ export default function Events() {
|
|||
return (
|
||||
<div
|
||||
key={variantKey}
|
||||
className={`rounded-xl border bg-white p-5 shadow-sm transition ${
|
||||
className={`rounded-lg border bg-white p-5 shadow-sm transition ${
|
||||
isSelectingThis
|
||||
? 'border-indigo-300 ring-2 ring-indigo-100'
|
||||
? 'border-primary-blue ring-2 ring-indigo-100'
|
||||
: 'border-gray-200 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
|
|
@ -790,13 +790,13 @@ export default function Events() {
|
|||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => handleVariableMenuToggle(variantKey)}
|
||||
disabled={!canInsertVariable}
|
||||
className="text-xs px-3 py-2 rounded-md bg-white border border-indigo-200 text-indigo-700 font-semibold hover:bg-indigo-50 transition disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="text-xs px-3 py-2 rounded-md bg-white border border-refresh-active text-primary-dark font-semibold hover:bg-refresh-hover transition disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
# Add Variable
|
||||
</button>
|
||||
|
||||
{openVariableMenuKey === variantKey && (
|
||||
<div className="absolute right-0 z-20 mt-2 w-56 rounded-xl border border-gray-200 bg-white shadow-xl overflow-hidden">
|
||||
<div className="absolute right-0 z-20 mt-2 w-56 rounded-lg border border-gray-200 bg-white shadow-sm overflow-hidden">
|
||||
<div className="px-4 py-3 border-b border-gray-100 bg-gray-50">
|
||||
<p className="text-[11px] font-bold uppercase tracking-wider text-gray-500">Insert DLT Variable</p>
|
||||
</div>
|
||||
|
|
@ -807,10 +807,10 @@ export default function Events() {
|
|||
type="button"
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => insertVariableToken(event.slug, index, option.token)}
|
||||
className="w-full px-4 py-3 text-left hover:bg-indigo-50 transition flex items-center justify-between gap-3"
|
||||
className="w-full px-4 py-3 text-left hover:bg-refresh-hover transition flex items-center justify-between gap-3"
|
||||
>
|
||||
<span className="text-sm font-semibold text-gray-800">{option.label}</span>
|
||||
<span className="text-xs font-mono text-indigo-700">{option.token}</span>
|
||||
<span className="text-xs font-mono text-primary-dark">{option.token}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -831,10 +831,10 @@ export default function Events() {
|
|||
onSelect={(e) => trackTextareaSelection(variantKey, e.target)}
|
||||
onKeyUp={(e) => trackTextareaSelection(variantKey, e.target)}
|
||||
rows={4}
|
||||
className={`w-full rounded-xl border px-4 py-3 text-sm text-gray-800 font-mono leading-relaxed resize-y focus:outline-none focus:ring-2 ${
|
||||
className={`w-full rounded-lg border px-4 py-3 text-sm text-gray-800 font-mono leading-relaxed resize-y focus:outline-none focus:ring-2 ${
|
||||
isEdited
|
||||
? 'border-amber-200 bg-amber-50/40 focus:ring-amber-200 focus:border-amber-300'
|
||||
: 'border-gray-200 bg-gray-50 focus:ring-indigo-100 focus:border-indigo-300'
|
||||
: 'border-gray-200 bg-gray-50 focus:ring-indigo-100 focus:border-primary-blue'
|
||||
}`}
|
||||
/>
|
||||
|
||||
|
|
@ -847,7 +847,7 @@ export default function Events() {
|
|||
}`}>
|
||||
{currentText.length} / {MAX_SMS_LENGTH}
|
||||
</span>
|
||||
<span className="text-xs font-semibold px-2.5 py-1 rounded-md border bg-indigo-50 border-indigo-200 text-indigo-700">
|
||||
<span className="text-xs font-semibold px-2.5 py-1 rounded-md border bg-refresh-hover border-refresh-active text-primary-dark">
|
||||
DLT vars: {dltTokenCount}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -899,7 +899,7 @@ export default function Events() {
|
|||
<button
|
||||
onClick={() => handleSelect(event.slug, currentText, index)}
|
||||
disabled={isSelectingThis || isSelectingAnotherVariant}
|
||||
className="text-xs px-4 py-2 rounded-md bg-indigo-600 hover:bg-indigo-700 text-white font-bold transition disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
className="text-xs px-4 py-2 rounded-md bg-primary-blue hover:bg-primary-dark text-white font-bold transition disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isSelectingThis ? 'Selecting…' : 'Use this template'}
|
||||
</button>
|
||||
|
|
@ -925,7 +925,7 @@ export default function Events() {
|
|||
<button
|
||||
onClick={() => handleValidateEdit(event.slug, index)}
|
||||
disabled={!canRunCheck || isSelectingThis || isSelectingAnotherVariant}
|
||||
className="text-xs px-4 py-2 rounded-md bg-indigo-600 hover:bg-indigo-700 text-white font-bold transition disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
className="text-xs px-4 py-2 rounded-md bg-primary-blue hover:bg-primary-dark text-white font-bold transition disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
>
|
||||
{validationStatus === 'checking'
|
||||
? 'Checking…'
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ export default function GlobalSms() {
|
|||
|
||||
{/* 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={`p-6 rounded-lg border ${isSetupComplete ? 'border-primary-blue bg-refresh-hover' : 'border-tags-border bg-tags-bg'} 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 ? (
|
||||
|
|
@ -243,7 +243,7 @@ export default function GlobalSms() {
|
|||
</div>
|
||||
|
||||
{!isSetupComplete && (
|
||||
<div className="bg-surface-white p-4 rounded-lg border border-[#d47f45]/30">
|
||||
<div className="bg-surface-white p-4 rounded-lg border border-border-main shadow-sm">
|
||||
<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') && (
|
||||
|
|
@ -280,7 +280,7 @@ export default function GlobalSms() {
|
|||
<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"
|
||||
className="w-full py-2 bg-primary-blue hover:bg-primary-dark text-white text-sm font-bold rounded shadow-sm transition disabled:opacity-50"
|
||||
>
|
||||
{savingProvider ? 'Saving...' : 'Save Required Details'}
|
||||
</button>
|
||||
|
|
@ -310,7 +310,7 @@ export default function GlobalSms() {
|
|||
profiles.map(p => {
|
||||
const isActive = p.id === activeProfileId;
|
||||
return (
|
||||
<div key={p.id} className={`p-5 rounded-xl border ${isActive ? 'border-primary-blue bg-[#f0f2fb]' : 'border-border-main bg-surface-white'} shadow-sm flex flex-col md:flex-row gap-4 items-start md:items-center justify-between transition-colors`}>
|
||||
<div key={p.id} className={`p-5 rounded-lg border ${isActive ? 'border-primary-blue bg-refresh-hover' : 'border-border-main bg-surface-white'} shadow-sm flex flex-col md:flex-row gap-4 items-start md:items-center justify-between transition-colors`}>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<h3 className="font-bold text-text-primary text-base truncate">{p.name}</h3>
|
||||
|
|
@ -358,15 +358,15 @@ export default function GlobalSms() {
|
|||
);
|
||||
})
|
||||
) : (
|
||||
<div className="text-center py-12 bg-surface-white border border-border-dashed rounded-xl">
|
||||
<div className="text-center py-12 bg-surface-white border border-border-main border-dashed rounded-lg">
|
||||
<p className="text-sm font-medium text-text-muted mb-4">No cURL profiles configured yet.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Inline Form (Create / Edit) */}
|
||||
<div className="bg-surface-white border border-border-main rounded-xl shadow-sm overflow-hidden">
|
||||
<div className="px-6 py-4 border-b border-border-main bg-[#fafafa] flex items-center justify-between">
|
||||
<div className="bg-surface-white border border-border-main rounded-lg shadow-sm overflow-hidden">
|
||||
<div className="px-6 py-4 border-b border-border-main bg-table-header flex items-center justify-between">
|
||||
<h3 className="font-bold text-text-primary text-md">
|
||||
{editingId ? 'Edit Profile' : 'Add New Profile'}
|
||||
</h3>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default function Providers() {
|
|||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="w-8 h-8 border-2 border-indigo-200 border-t-indigo-600 rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-refresh-active border-t-indigo-600 rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ export default function Providers() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSave} className="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
|
||||
<form onSubmit={handleSave} className="bg-white border border-gray-200 rounded-lg shadow-sm overflow-hidden">
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<label className={`block text-sm font-semibold mb-1.5 tracking-wide ${!form.providerName ? 'text-error-text' : 'text-text-primary'}`}>
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export default function Templates() {
|
|||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="w-8 h-8 border-2 border-indigo-200 border-t-indigo-600 rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-refresh-active border-t-indigo-600 rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ export default function Templates() {
|
|||
</div>
|
||||
|
||||
{templates.length === 0 ? (
|
||||
<div className="text-center py-16 bg-surface-white border border-border-main rounded-xl shadow-sm">
|
||||
<div className="text-center py-16 bg-surface-white border border-border-main rounded-lg shadow-sm">
|
||||
<div className="w-16 h-16 rounded-full bg-page-bg flex items-center justify-center mx-auto mb-4 border border-border-soft">
|
||||
<svg className="w-8 h-8 text-text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>
|
||||
</div>
|
||||
|
|
@ -160,7 +160,7 @@ export default function Templates() {
|
|||
|
||||
if (visibleTemplates.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-12 bg-surface-white border border-border-dashed rounded-xl">
|
||||
<div className="text-center py-12 bg-surface-white border border-border-dashed rounded-lg">
|
||||
<p className="text-text-muted text-sm font-medium">No templates in {activeTab === 'published' ? 'Published' : 'Pending'}.</p>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -186,7 +186,7 @@ export default function Templates() {
|
|||
delete templateCardRefs.current[tmpl.eventSlug];
|
||||
}
|
||||
}}
|
||||
className={`rounded-xl bg-white border shadow-sm overflow-hidden transition-all duration-300 ${
|
||||
className={`rounded-lg bg-white border shadow-sm overflow-hidden transition-all duration-300 ${
|
||||
highlightedEventSlug === tmpl.eventSlug
|
||||
? 'border-primary-blue ring-2 ring-indigo-200 animate-pulse'
|
||||
: 'border-gray-200'
|
||||
|
|
@ -229,7 +229,7 @@ export default function Templates() {
|
|||
{tmpl.templateId && (
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-gray-500 uppercase tracking-wider mb-2">DLT Template ID</label>
|
||||
<p className="font-mono text-sm text-indigo-700 bg-indigo-50 border border-indigo-100 px-3 py-2 rounded-lg inline-block">
|
||||
<p className="font-mono text-sm text-primary-dark bg-refresh-hover border border-refresh-active px-3 py-2 rounded-lg inline-block">
|
||||
{tmpl.templateId}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -241,7 +241,7 @@ export default function Templates() {
|
|||
<div className="flex flex-wrap gap-2">
|
||||
{Object.entries(tmpl.variableMap).map(([key, val]) => (
|
||||
<div key={key} className="flex items-center gap-2 text-xs bg-gray-50 border border-gray-200 rounded-md px-3 py-1.5">
|
||||
<span className="font-mono text-indigo-700 font-bold">{key}</span>
|
||||
<span className="font-mono text-primary-dark font-bold">{key}</span>
|
||||
<span className="text-gray-400">→</span>
|
||||
<span className="font-medium text-gray-700">{val}</span>
|
||||
</div>
|
||||
|
|
@ -254,7 +254,7 @@ export default function Templates() {
|
|||
{!isBoundProfileMissing && tmpl.status === 'pending_whitelisting' && (
|
||||
<button
|
||||
onClick={() => setWhitelistTarget(tmpl)}
|
||||
className="px-4 py-2 rounded-lg bg-tags-text hover:bg-[#cf7b3f] text-white text-sm font-semibold transition shadow-sm border border-[#d47f45]"
|
||||
className="px-4 py-2 rounded-lg bg-tags-text hover:bg-orange-700 text-white text-sm font-semibold transition shadow-sm border border-orange-600"
|
||||
>
|
||||
Publish
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user