Fallback changes, API is not working

This commit is contained in:
Ritul Jadhav 2026-03-31 14:08:10 +05:30
parent b5ebb830d8
commit 37792c1704
13 changed files with 159 additions and 214 deletions

View File

@ -42,7 +42,7 @@ function BusinessGuard({ children, isGlobalSmsRoute }) {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-page-bg"> <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> </div>
); );
} }

View File

@ -37,14 +37,14 @@ export default function ImagePicker({ currentImage, onSelect }) {
onClick={() => onSelect(img.url)} onClick={() => onSelect(img.url)}
className={`relative rounded-lg overflow-hidden border-2 aspect-video transition-all ${ className={`relative rounded-lg overflow-hidden border-2 aspect-video transition-all ${
isSelected 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' : '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" /> <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 && ( {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> <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> </div>
)} )}

View File

@ -1,16 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import apiClient from '../api/client'; import apiClient from '../api/client';
import {
getBusinessDomain,
getBusinessImage,
getBusinessName,
getBusinessTagline,
} from '../utils/businessProfile';
export default function RegisterBusinessModal({ onClose }) { export default function RegisterBusinessModal({ onClose }) {
const [url, setUrl] = useState(''); const [url, setUrl] = useState('');
const [status, setStatus] = useState('idle'); const [status, setStatus] = useState('idle');
const [createdBusiness, setCreatedBusiness] = useState(null);
const [error, setError] = useState(''); const [error, setError] = useState('');
async function handleSubmit(e) { async function handleSubmit(e) {
@ -21,10 +14,9 @@ export default function RegisterBusinessModal({ onClose }) {
setError(''); setError('');
try { try {
const res = await apiClient.post('/api/businesses', { await apiClient.post('/api/businesses', {
websiteUrl: url.trim(), websiteUrl: url.trim(),
}); });
setCreatedBusiness(res.data);
setStatus('success'); setStatus('success');
} catch (err) { } catch (err) {
setError(err.response?.data?.error || 'Something went wrong. Please try again.'); 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 ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm"> <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' && ( {status === 'success' && (
<div className="text-center"> <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> <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> <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> <p className="text-gray-500 text-sm mb-6 font-medium">Your business has been registered successfully.</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>
<button <button
onClick={onClose} 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 Done
</button> </button>
@ -82,7 +47,7 @@ export default function RegisterBusinessModal({ onClose }) {
<div className="mb-6"> <div className="mb-6">
<h2 className="text-xl font-bold text-gray-900 mb-2 tracking-tight">Add a Business</h2> <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"> <p className="text-gray-500 text-sm leading-relaxed">
Enter the storefront website URL and we&apos;ll scrape it to detect the brand, images, and copy you need for onboarding. Enter the storefront website URL and we&apos;ll scrape it to detect the brand and set up your business.
</p> </p>
</div> </div>
@ -95,7 +60,7 @@ export default function RegisterBusinessModal({ onClose }) {
onChange={(e) => setUrl(e.target.value)} onChange={(e) => setUrl(e.target.value)}
placeholder="https://yourstore.com" placeholder="https://yourstore.com"
disabled={status === 'loading'} 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 required
/> />
</div> </div>
@ -116,7 +81,7 @@ export default function RegisterBusinessModal({ onClose }) {
<button <button
type="submit" type="submit"
disabled={status === 'loading' || !url.trim()} 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' ? ( {status === 'loading' ? (
<><span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> Analysing</> <><span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> Analysing</>

View File

@ -22,7 +22,7 @@ const SVG_ICONS = {
function TopLevelStatus({ done, active }) { function TopLevelStatus({ done, active }) {
if (done) { if (done) {
return ( 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"> <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" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M5 13l4 4L19 7" />
</svg> </svg>
@ -31,7 +31,7 @@ function TopLevelStatus({ done, active }) {
} }
if (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" />; 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 }) { function StageMarker({ done, active, enabled }) {
if (done) { if (done) {
return ( 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"> <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" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M5 13l4 4L19 7" />
</svg> </svg>
@ -49,14 +49,14 @@ function StageMarker({ done, active, enabled }) {
} }
if (active) { 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) { 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-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() { export default function Sidebar() {
@ -133,7 +133,7 @@ export default function Sidebar() {
</button> </button>
{activeBusiness && ( {activeBusiness && (
<div className="mt-4 flex items-center gap-3"> <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'} {activeBusiness.brandName?.[0]?.toUpperCase() || 'B'}
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
@ -161,7 +161,7 @@ export default function Sidebar() {
{item.enabled ? ( {item.enabled ? (
<NavLink <NavLink
to={item.to} 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 item.active
? 'bg-gray-100/70 text-gray-900' ? 'bg-gray-100/70 text-gray-900'
: 'text-gray-500 hover:text-gray-900 hover:bg-gray-50' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-50'
@ -186,7 +186,7 @@ export default function Sidebar() {
) : ( ) : (
<div <div
aria-disabled="true" 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]} {SVG_ICONS[item.id]}
<span className="flex-1 truncate">{item.label}</span> <span className="flex-1 truncate">{item.label}</span>
@ -203,12 +203,12 @@ export default function Sidebar() {
{item.substeps.map((substep) => ( {item.substeps.map((substep) => (
<div key={substep.id} className="relative flex items-center pr-3 group cursor-default"> <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"> <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>
<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 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' : 'text-gray-500 font-medium hover:text-gray-900 hover:bg-gray-50'
}`} }`}
> >

View File

@ -28,7 +28,7 @@ export default function TestSmsModal({ businessId, template, onClose }) {
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm"> <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"> <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> <span className="text-xl">📱</span>
</div> </div>
@ -52,7 +52,7 @@ export default function TestSmsModal({ businessId, template, onClose }) {
value={toNumber} value={toNumber}
onChange={e => setToNumber(e.target.value)} onChange={e => setToNumber(e.target.value)}
placeholder="e.g. 919876543210 (with country code)" 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 autoFocus
required required
/> />

View File

@ -104,7 +104,7 @@ export default function WhitelistModal({ businessId, template, boundProfile, onC
return ( 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="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"> <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> <span className="text-xl"></span>
</div> </div>

View File

@ -3,38 +3,38 @@
@import "tailwindcss"; @import "tailwindcss";
@theme { @theme {
--color-primary-blue: #4F5BD5; --color-primary-blue: #3838C4;
--color-primary-dark: #2F3DB9; --color-primary-dark: #2B2BA3;
--color-link-blue: #5B72D7; --color-link-blue: #4E4ED6;
--color-page-bg: #F5F6F8; --color-page-bg: #F5F6F8;
--color-surface-white: #FFFFFF; --color-surface-white: #FFFFFF;
--color-table-header: #F4F5F7; --color-table-header: #F9FAFB;
--color-pagination-bg: #F8F9FB; --color-pagination-bg: #FFFFFF;
--color-refresh-hover: #F4F6FF; --color-refresh-hover: #F0F0FA;
--color-refresh-active: #E9EDFF; --color-refresh-active: #E0E0F5;
--color-row-hover: #EEF0F8; --color-row-hover: #F9FAFB;
--color-text-primary: #3F434C; --color-text-primary: #1F2937;
--color-text-muted: #8B9098; --color-text-muted: #6B7280;
--color-header-text: #4B4F57; --color-header-text: #4B5563;
--color-channel-name: #61656D; --color-channel-name: #4B5563;
--color-border-main: #E2E5EA; --color-border-main: #E5E7EB;
--color-border-soft: #ECEFF3; --color-border-soft: #F3F4F6;
--color-item-border: #ECEEF2; --color-item-border: #E5E7EB;
--color-badge-bg: #F0FAEF; --color-badge-bg: #F0FDF4;
--color-badge-text: #5FA05A; --color-badge-text: #166534;
--color-badge-border: #9FCF9B; --color-badge-border: #BBF7D0;
--color-delayed-bg: #FFF1F0; --color-delayed-bg: #FEF2F2;
--color-delayed-text: #D94F4F; --color-delayed-text: #991B1B;
--color-delayed-border: #F5A5A5; --color-delayed-border: #FECACA;
--color-tags-bg: #FFF8F3; --color-tags-bg: #FFFBEB;
--color-tags-text: #E58D4F; --color-tags-text: #92400E;
--color-tags-border: #F2B17F; --color-tags-border: #FDE68A;
--color-error-text: #C62828; --color-error-text: #DC2626;
--color-sla-green: #4CAF50; --color-sla-green: #22C55E;
--color-sla-grey: #BDBDBD; --color-sla-grey: #D1D5DB;
--color-spinner-track: #D8DEFE; --color-spinner-track: #E0E7FF;
--color-placeholder-bg: #E0E0E0; --color-placeholder-bg: #D1D5DB;
--color-empty-bg: #FAFAFA; --color-empty-bg: #F9FAFB;
} }
:root { :root {

View File

@ -13,7 +13,7 @@ const NAV_CARDS = [
function DeleteConfirmModal({ brandName, onCancel, onConfirm, deleting }) { function DeleteConfirmModal({ brandName, onCancel, onConfirm, deleting }) {
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm"> <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"> <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> <span className="text-xl">🗑</span>
</div> </div>
@ -65,7 +65,7 @@ export default function Brand() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gray-50"> <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> </div>
); );
} }
@ -75,7 +75,7 @@ export default function Brand() {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gray-50"> <div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="text-center max-w-lg px-8"> <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 S
</div> </div>
<h1 className="text-3xl font-bold text-gray-900 mb-3 tracking-tight">SMS Template Extension</h1> <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> </p>
<button <button
onClick={() => setShowModal(true)} 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 Register Your Business
</button> </button>
@ -100,13 +100,13 @@ export default function Brand() {
<div className="max-w-4xl mx-auto"> <div className="max-w-4xl mx-auto">
{/* Brand header card */} {/* 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 items-start justify-between gap-4">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h1 className="text-3xl font-bold text-gray-900 tracking-tight truncate">{brand.brandName}</h1> <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> <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"> <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} {brand.tone}
</span> </span>
<span className="text-xs text-gray-400 font-medium tracking-wide"> <span className="text-xs text-gray-400 font-medium tracking-wide">
@ -156,7 +156,7 @@ export default function Brand() {
{/* Brand images */} {/* Brand images */}
{brand.relevantImagePaths?.length > 0 && ( {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> <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"> <div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
{brand.relevantImagePaths.map((url, i) => ( {brand.relevantImagePaths.map((url, i) => (
@ -182,7 +182,7 @@ export default function Brand() {
<Link <Link
key={card.to} key={card.to}
to={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> <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> <p className="font-semibold text-gray-900 mb-1">{card.label}</p>

View File

@ -15,7 +15,7 @@ import {
function DeleteConfirmModal({ businessName, onCancel, onConfirm, deleting }) { function DeleteConfirmModal({ businessName, onCancel, onConfirm, deleting }) {
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm"> <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"> <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> <span className="text-xl">🗑</span>
</div> </div>
@ -52,21 +52,21 @@ function BusinessCreatedModal({ business, onClose }) {
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm"> <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> <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> <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> <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="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 ? ( {image ? (
<img src={image} alt={name} className="w-full h-full object-cover" /> <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>
<div className="min-w-0"> <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>} {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>} {tagline && <p className="text-sm text-gray-700 mt-2 leading-relaxed line-clamp-2">{tagline}</p>}
</div> </div>
@ -74,7 +74,7 @@ function BusinessCreatedModal({ business, onClose }) {
</div> </div>
<button <button
onClick={onClose} 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 Done
</button> </button>
@ -89,13 +89,13 @@ function SalesChannelCard({ channel, disabled, onImport }) {
const image = getBusinessImage(channel); const image = getBusinessImage(channel);
return ( 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="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 ? ( {image ? (
<img src={image} alt={name} className="w-full h-full object-cover" /> <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>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
@ -126,7 +126,6 @@ export default function Businesses() {
const [salesChannels, setSalesChannels] = useState([]); const [salesChannels, setSalesChannels] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [salesChannelsStatus, setSalesChannelsStatus] = useState('loading'); const [salesChannelsStatus, setSalesChannelsStatus] = useState('loading');
const [salesChannelsError, setSalesChannelsError] = useState('');
const [salesChannelQuery, setSalesChannelQuery] = useState(''); const [salesChannelQuery, setSalesChannelQuery] = useState('');
const [selectingBusinessId, setSelectingBusinessId] = useState(''); const [selectingBusinessId, setSelectingBusinessId] = useState('');
const [creatingSalesChannelId, setCreatingSalesChannelId] = useState(''); const [creatingSalesChannelId, setCreatingSalesChannelId] = useState('');
@ -147,6 +146,7 @@ export default function Businesses() {
const availableSalesChannels = useMemo(() => ( const availableSalesChannels = useMemo(() => (
salesChannels.filter((channel) => !configuredApplicationIds.has(getChannelId(channel))) salesChannels.filter((channel) => !configuredApplicationIds.has(getChannelId(channel)))
), [configuredApplicationIds, salesChannels]); ), [configuredApplicationIds, salesChannels]);
const showSalesChannelsSection = salesChannelsStatus === 'success' && availableSalesChannels.length > 0;
const filteredSalesChannels = useMemo(() => { const filteredSalesChannels = useMemo(() => {
const query = salesChannelQuery.trim().toLowerCase(); const query = salesChannelQuery.trim().toLowerCase();
@ -174,7 +174,6 @@ export default function Businesses() {
const load = useCallback(async () => { const load = useCallback(async () => {
setLoading(true); setLoading(true);
setError(''); setError('');
setSalesChannelsError('');
try { try {
const [businessesRes, salesChannelsRes] = await Promise.allSettled([ const [businessesRes, salesChannelsRes] = await Promise.allSettled([
@ -189,9 +188,6 @@ export default function Businesses() {
if (salesChannelsRes.status === 'rejected') { if (salesChannelsRes.status === 'rejected') {
setSalesChannels([]); setSalesChannels([]);
setSalesChannelsStatus('error'); setSalesChannelsStatus('error');
setSalesChannelsError(
salesChannelsRes.reason?.message || 'Active sales channels could not be loaded right now.'
);
} }
} finally { } finally {
setLoading(false); setLoading(false);
@ -218,13 +214,12 @@ export default function Businesses() {
if (!applicationId) return; if (!applicationId) return;
if (!channel.websiteUrl) { 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; return;
} }
setCreatingSalesChannelId(applicationId); setCreatingSalesChannelId(applicationId);
setError(''); setError('');
setSalesChannelsError('');
try { try {
const res = await apiClient.post('/api/businesses', { const res = await apiClient.post('/api/businesses', {
@ -258,7 +253,7 @@ export default function Businesses() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gray-50"> <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> </div>
); );
} }
@ -272,12 +267,14 @@ export default function Businesses() {
{businesses.length > 0 ? 'Your Businesses' : 'Set Up Your First Business'} {businesses.length > 0 ? 'Your Businesses' : 'Set Up Your First Business'}
</h1> </h1>
<p className="text-sm text-gray-500 mt-1"> <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 well scrape it to set up your business.'}
</p> </p>
</div> </div>
<button <button
onClick={() => setShowModal(true)} 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 + Add Business
</button> </button>
@ -301,7 +298,7 @@ export default function Businesses() {
{businesses.map((biz) => ( {businesses.map((biz) => (
<div <div
key={biz.businessId} 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 <button
className="w-full text-left p-6" className="w-full text-left p-6"
@ -309,11 +306,11 @@ export default function Businesses() {
disabled={selectingBusinessId === biz.businessId} disabled={selectingBusinessId === biz.businessId}
> >
<div className="flex items-start gap-4"> <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) ? ( {getBusinessImage(biz) ? (
<img src={getBusinessImage(biz)} alt={getBusinessName(biz)} className="w-full h-full object-cover" /> <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'} {getBusinessName(biz)?.[0]?.toUpperCase() || 'B'}
</span> </span>
)} )}
@ -330,8 +327,8 @@ export default function Businesses() {
Added {new Date(biz.createdAt).toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' })} Added {new Date(biz.createdAt).toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' })}
</p> </p>
{selectingBusinessId === biz.businessId && ( {selectingBusinessId === biz.businessId && (
<div className="mt-3 inline-flex items-center gap-2 text-xs text-indigo-600 font-semibold"> <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-indigo-200 border-t-indigo-600 rounded-full animate-spin" /> <span className="w-3.5 h-3.5 border-2 border-refresh-active border-t-indigo-600 rounded-full animate-spin" />
Opening Opening
</div> </div>
)} )}
@ -339,7 +336,7 @@ export default function Businesses() {
</div> </div>
</button> </button>
<div className="px-6 py-3 bg-gray-50 border-t border-gray-100 flex justify-between items-center"> <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 <button
onClick={(e) => { e.stopPropagation(); setDeleteTarget(biz); }} onClick={(e) => { e.stopPropagation(); setDeleteTarget(biz); }}
className="text-xs text-red-500 hover:text-red-700 font-medium transition" className="text-xs text-red-500 hover:text-red-700 font-medium transition"
@ -351,13 +348,14 @@ export default function Businesses() {
))} ))}
</div> </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-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> </div>
)} )}
</section> </section>
{showSalesChannelsSection && (
<section> <section>
<div className="flex items-end justify-between gap-4 mb-4"> <div className="flex items-end justify-between gap-4 mb-4">
<div> <div>
@ -366,25 +364,12 @@ export default function Businesses() {
</div> </div>
<button <button
onClick={() => setShowModal(true)} 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 Use website URL fallback
</button> </button>
</div> </div>
<div className="rounded-lg border border-gray-200 bg-white p-5 shadow-sm">
{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>
)}
{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>
) : availableSalesChannels.length > 0 ? (
<div className="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
<div className="mb-4"> <div className="mb-4">
<label className="block text-sm font-semibold text-gray-700 mb-1.5">Search Sales Channels</label> <label className="block text-sm font-semibold text-gray-700 mb-1.5">Search Sales Channels</label>
<input <input
@ -392,7 +377,7 @@ export default function Businesses() {
value={salesChannelQuery} value={salesChannelQuery}
onChange={(e) => setSalesChannelQuery(e.target.value)} onChange={(e) => setSalesChannelQuery(e.target.value)}
placeholder="Search by channel name or domain" 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" 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> </div>
@ -411,19 +396,14 @@ export default function Businesses() {
</div> </div>
{filteredSalesChannels.length === 0 && ( {filteredSalesChannels.length === 0 && (
<div className="mt-4 rounded-xl border border-dashed border-gray-300 bg-gray-50 p-6 text-center"> <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 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> <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> </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>
)}
</section> </section>
)}
</div> </div>
{showModal && ( {showModal && (

View File

@ -507,7 +507,7 @@ export default function Events() {
if (loading) { if (loading) {
return ( return (
<div className="flex items-center justify-center h-64"> <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> </div>
); );
} }
@ -533,7 +533,7 @@ export default function Events() {
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search events" 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 && ( {searchTerm && (
<button <button
@ -550,7 +550,7 @@ export default function Events() {
</div> </div>
<div className="flex items-center gap-3"> <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 {totalVisibleEvents} visible
</span> </span>
<button <button
@ -578,18 +578,18 @@ export default function Events() {
)} )}
{showAddForm && ( {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 <input
value={newLabel} value={newLabel}
onChange={(e) => setNewLabel(e.target.value)} onChange={(e) => setNewLabel(e.target.value)}
placeholder="Event name (e.g. Return Initiated)" 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 autoFocus
/> />
<button <button
type="submit" type="submit"
disabled={addingEvent || !newLabel.trim()} 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'} {addingEvent ? 'Adding…' : 'Add'}
</button> </button>
@ -597,7 +597,7 @@ export default function Events() {
)} )}
{groupedEvents.length === 0 ? ( {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="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> <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> </div>
@ -607,7 +607,7 @@ export default function Events() {
const isExpanded = searchTerm.trim() ? true : !!expandedGroups[group.id]; const isExpanded = searchTerm.trim() ? true : !!expandedGroups[group.id];
return ( 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 <button
type="button" type="button"
onClick={() => toggleGroup(group.id)} onClick={() => toggleGroup(group.id)}
@ -615,7 +615,7 @@ export default function Events() {
> >
<div className="flex min-w-0 items-start gap-4"> <div className="flex min-w-0 items-start gap-4">
<div className={`mt-1 h-3 w-3 rounded-full shadow-sm ${ <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 === 'delivery' ? 'bg-sky-500' :
group.id === 'cancellations' ? 'bg-rose-500' : group.id === 'cancellations' ? 'bg-rose-500' :
group.id === 'returns' ? 'bg-amber-500' : group.id === 'returns' ? 'bg-amber-500' :
@ -653,7 +653,7 @@ export default function Events() {
const canViewTemplate = templateStatus !== 'unselected'; const canViewTemplate = templateStatus !== 'unselected';
return ( 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 flex-col sm:flex-row sm:items-center justify-between px-6 py-5 gap-4">
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
{event.isDefault ? ( {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 ${ 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' state === 'done' || state === 'selected'
? 'bg-white border border-gray-300 text-gray-700 hover:bg-gray-50' ? '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' ? ( {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' ? ( ) : state === 'done' || state === 'selected' ? (
<> Regenerate</> <> Regenerate</>
) : ( ) : (
@ -743,9 +743,9 @@ export default function Events() {
return ( return (
<div <div
key={variantKey} 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 isSelectingThis
? 'border-indigo-300 ring-2 ring-indigo-100' ? 'border-primary-blue ring-2 ring-indigo-100'
: 'border-gray-200 hover:border-gray-300' : 'border-gray-200 hover:border-gray-300'
}`} }`}
> >
@ -790,13 +790,13 @@ export default function Events() {
onMouseDown={(e) => e.preventDefault()} onMouseDown={(e) => e.preventDefault()}
onClick={() => handleVariableMenuToggle(variantKey)} onClick={() => handleVariableMenuToggle(variantKey)}
disabled={!canInsertVariable} 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 # Add Variable
</button> </button>
{openVariableMenuKey === variantKey && ( {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"> <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> <p className="text-[11px] font-bold uppercase tracking-wider text-gray-500">Insert DLT Variable</p>
</div> </div>
@ -807,10 +807,10 @@ export default function Events() {
type="button" type="button"
onMouseDown={(e) => e.preventDefault()} onMouseDown={(e) => e.preventDefault()}
onClick={() => insertVariableToken(event.slug, index, option.token)} 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-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> </button>
))} ))}
</div> </div>
@ -831,10 +831,10 @@ export default function Events() {
onSelect={(e) => trackTextareaSelection(variantKey, e.target)} onSelect={(e) => trackTextareaSelection(variantKey, e.target)}
onKeyUp={(e) => trackTextareaSelection(variantKey, e.target)} onKeyUp={(e) => trackTextareaSelection(variantKey, e.target)}
rows={4} 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 isEdited
? 'border-amber-200 bg-amber-50/40 focus:ring-amber-200 focus:border-amber-300' ? '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} {currentText.length} / {MAX_SMS_LENGTH}
</span> </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} DLT vars: {dltTokenCount}
</span> </span>
</div> </div>
@ -899,7 +899,7 @@ export default function Events() {
<button <button
onClick={() => handleSelect(event.slug, currentText, index)} onClick={() => handleSelect(event.slug, currentText, index)}
disabled={isSelectingThis || isSelectingAnotherVariant} 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'} {isSelectingThis ? 'Selecting…' : 'Use this template'}
</button> </button>
@ -925,7 +925,7 @@ export default function Events() {
<button <button
onClick={() => handleValidateEdit(event.slug, index)} onClick={() => handleValidateEdit(event.slug, index)}
disabled={!canRunCheck || isSelectingThis || isSelectingAnotherVariant} 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' {validationStatus === 'checking'
? 'Checking…' ? 'Checking…'

View File

@ -209,7 +209,7 @@ export default function GlobalSms() {
{/* Active Profile Setup Review Block */} {/* Active Profile Setup Review Block */}
{activeProfile && ( {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"> <div className="flex items-center gap-3 mb-4">
<h3 className="font-bold text-text-primary text-lg">Active Setup: {activeProfile.name}</h3> <h3 className="font-bold text-text-primary text-lg">Active Setup: {activeProfile.name}</h3>
{isSetupComplete ? ( {isSetupComplete ? (
@ -243,7 +243,7 @@ export default function GlobalSms() {
</div> </div>
{!isSetupComplete && ( {!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> <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"> <form onSubmit={handleProviderSubmit} className="space-y-3">
{missingFields.includes('providerName') && ( {missingFields.includes('providerName') && (
@ -280,7 +280,7 @@ export default function GlobalSms() {
<button <button
type="submit" type="submit"
disabled={savingProvider} 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'} {savingProvider ? 'Saving...' : 'Save Required Details'}
</button> </button>
@ -310,7 +310,7 @@ export default function GlobalSms() {
profiles.map(p => { profiles.map(p => {
const isActive = p.id === activeProfileId; const isActive = p.id === activeProfileId;
return ( 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-1 overflow-hidden">
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
<h3 className="font-bold text-text-primary text-base truncate">{p.name}</h3> <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> <p className="text-sm font-medium text-text-muted mb-4">No cURL profiles configured yet.</p>
</div> </div>
)} )}
</div> </div>
{/* Inline Form (Create / Edit) */} {/* Inline Form (Create / Edit) */}
<div className="bg-surface-white border border-border-main rounded-xl shadow-sm overflow-hidden"> <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-[#fafafa] flex items-center justify-between"> <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"> <h3 className="font-bold text-text-primary text-md">
{editingId ? 'Edit Profile' : 'Add New Profile'} {editingId ? 'Edit Profile' : 'Add New Profile'}
</h3> </h3>

View File

@ -73,7 +73,7 @@ export default function Providers() {
if (loading) { if (loading) {
return ( return (
<div className="flex items-center justify-center h-64"> <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> </div>
); );
} }
@ -103,7 +103,7 @@ export default function Providers() {
</div> </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 className="p-6 space-y-6">
<div> <div>
<label className={`block text-sm font-semibold mb-1.5 tracking-wide ${!form.providerName ? 'text-error-text' : 'text-text-primary'}`}> <label className={`block text-sm font-semibold mb-1.5 tracking-wide ${!form.providerName ? 'text-error-text' : 'text-text-primary'}`}>

View File

@ -103,7 +103,7 @@ export default function Templates() {
if (loading) { if (loading) {
return ( return (
<div className="flex items-center justify-center h-64"> <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> </div>
); );
} }
@ -146,7 +146,7 @@ export default function Templates() {
</div> </div>
{templates.length === 0 ? ( {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"> <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> <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> </div>
@ -160,7 +160,7 @@ export default function Templates() {
if (visibleTemplates.length === 0) { if (visibleTemplates.length === 0) {
return ( 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> <p className="text-text-muted text-sm font-medium">No templates in {activeTab === 'published' ? 'Published' : 'Pending'}.</p>
</div> </div>
); );
@ -186,7 +186,7 @@ export default function Templates() {
delete templateCardRefs.current[tmpl.eventSlug]; 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 highlightedEventSlug === tmpl.eventSlug
? 'border-primary-blue ring-2 ring-indigo-200 animate-pulse' ? 'border-primary-blue ring-2 ring-indigo-200 animate-pulse'
: 'border-gray-200' : 'border-gray-200'
@ -229,7 +229,7 @@ export default function Templates() {
{tmpl.templateId && ( {tmpl.templateId && (
<div> <div>
<label className="block text-xs font-bold text-gray-500 uppercase tracking-wider mb-2">DLT Template ID</label> <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} {tmpl.templateId}
</p> </p>
</div> </div>
@ -241,7 +241,7 @@ export default function Templates() {
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{Object.entries(tmpl.variableMap).map(([key, val]) => ( {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"> <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="text-gray-400"></span>
<span className="font-medium text-gray-700">{val}</span> <span className="font-medium text-gray-700">{val}</span>
</div> </div>
@ -254,7 +254,7 @@ export default function Templates() {
{!isBoundProfileMissing && tmpl.status === 'pending_whitelisting' && ( {!isBoundProfileMissing && tmpl.status === 'pending_whitelisting' && (
<button <button
onClick={() => setWhitelistTarget(tmpl)} 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 Publish
</button> </button>