Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion public/review-image/1.svg

This file was deleted.

Binary file added public/review-image/1.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/review-image/2.svg

This file was deleted.

Binary file added public/review-image/2.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/review-image/3.svg

This file was deleted.

Binary file added public/review-image/3.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/review-image/4.svg

This file was deleted.

Binary file added public/review-image/4.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/review-image/capterra-logo.png
Binary file not shown.
Binary file added public/review-image/capterra-logo.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/review-image/g2-logo.png
Binary file not shown.
Binary file added public/review-image/g2-logo.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/review-image/nevada.svg

This file was deleted.

Binary file added public/review-image/nevada.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/review-image/pexels-ironic.svg

This file was deleted.

Binary file added public/review-image/pexels-ironic.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/review-image/signup-review-1.jpg
Binary file not shown.
Binary file added public/review-image/signup-review-1.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/review-image/signup-review-2.jpg
Binary file not shown.
Binary file added public/review-image/signup-review-2.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import FaqSection from '@/components/faqSection/faqSection';

export default function FeatureContentComp({ featureData }) {
if (featureData && featureData.length > 0)
return (
<div className="container">
<div className="border custom-border p-12 bg-white flex flex-col gap-6">
{featureData?.map((faq, index) => {
return (
<ul className="flex flex-col gap-1" key={index} style={{ listStyleType: 'square' }}>
<li className="font-semibold text-lg">{faq?.question}</li>
<li className="list-none">{faq?.answer}</li>
</ul>
);
})}
</div>
</div>
);
if (!featureData || featureData.length === 0) return null;

// Map incoming data ({question, answer}) to FaqSection format ({que, ans})
const faqData = featureData
.map((item) => {
const que = item?.question || '';
const ans = item?.answer || '';

if (!que || !ans) return null;
return { que, ans };
})
.filter(Boolean);

if (!faqData.length) return null;

return (
<div className="container">
<FaqSection faqData={faqData} />
</div>
);
}

8 changes: 4 additions & 4 deletions src/components/PricingTabs/PricingTabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,28 +296,28 @@ export default function PricingTabsClient({ countries }) {
<div className="flex items-center md:justify-center flex-1 overflow-hidden">
<div className="flex -space-x-5">
<Image
src="/review-image/1.svg"
src="/review-image/1.webp"
alt="Customer support expert avatar"
width={80}
height={80}
className="w-20 h-20"
/>
<Image
src="/review-image/2.svg"
src="/review-image/2.webp"
alt="Technical support expert avatar"
width={80}
height={80}
className="w-20 h-20"
/>
<Image
src="/review-image/3.svg"
src="/review-image/3.webp"
alt="Automation specialist expert avatar"
width={80}
height={80}
className="w-20 h-20"
/>
<Image
src="/review-image/4.svg"
src="/review-image/4.webp"
alt="Integration expert avatar"
width={80}
height={80}
Expand Down
109 changes: 109 additions & 0 deletions src/components/departmentFAQ/DepartmentAppsMarquee.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import Marquee from 'react-fast-marquee';
import { useMemo } from 'react';

// Helper functions moved outside component for better performance
const parseAppsData = (data) => {
if (!data) return [];

// Handle string data
if (typeof data === 'string') {
try {
// Try parsing as JSON first
const jsonData = JSON.parse(data);
return Array.isArray(jsonData) ? jsonData : [jsonData];
} catch {
// If not valid JSON, parse as colon-separated or line-separated
return data
.split(/[,\n]/)
.map(line => line.trim())
.filter(Boolean)
.map(line => {
const parts = line.split(':');
return parts.length >= 2
? { app: parts[0].trim(), domain: parts[1].trim() }
: { app: line };
});
}
}

// Handle array data
if (Array.isArray(data)) return data;

// Handle object data
if (typeof data === 'object' && data !== null) return [data];

return [];
};

const DepartmentAppsMarquee = ({ marque_apps }) => {
// Use useMemo instead of useState + useEffect for better performance
const parsedApps = useMemo(() => parseAppsData(marque_apps), [marque_apps]);

// Early return if no apps to display
if (!parsedApps.length) return null;

// Memoized app item renderer for better performance
const AppItem = ({ app, keyPrefix }) => {
const appName = app.app || '';
const domain = app.domain || '';
const iconSrc = domain ? `https://thingsofbrand.com/api/icon/${domain}` : '';
const firstLetter = appName.charAt(0).toUpperCase();

// Only render if we have an app name
if (!appName) return null;

return (
<div key={`${keyPrefix}-${appName}`} className="flex items-center gap-2 whitespace-nowrap">
{domain && (
<div className="w-8 h-8 relative overflow-hidden rounded-full bg-gray-50 flex items-center justify-center">
{iconSrc ? (
<img
src={iconSrc}
alt={`${appName} icon`}
width={30}
height={30}
className="object-contain w-8 h-8"
onError={(e) => {
e.target.style.display = 'none';
// Use React's way of adding elements instead of direct DOM manipulation
e.target.parentNode.innerHTML = `<span class="text-xs font-bold text-gray-700">${firstLetter}</span>`;
}}
/>
) : (
<span className="text-xs font-bold text-gray-700">{firstLetter}</span>
)}
</div>
)}
<span className="text-gray-800 font-medium">{appName}</span>
</div>
);
};

return (
<div className="flex flex-col gap-6 container py-8 px-4">
<div className="flex flex-col md:flex-row items-center justify-between gap-8">
<p className="text-center text-gray-500 min-w-fit uppercase">Trusted by Teams Using These Apps</p>
<Marquee
direction="left"
speed={40}
autoFill
gradient
gradientColor={[250, 249, 246]}
gradientWidth={96}
>
<div className="flex py-4 gap-20">
{parsedApps.map((app, index) => (
<AppItem
key={`app-${index}`}
app={app}
keyPrefix="left"
/>
))}
</div>
</Marquee>
</div>
</div>
);
};

export default DepartmentAppsMarquee;
95 changes: 95 additions & 0 deletions src/components/departmentFAQ/DepartmentUseCase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useMemo } from 'react';

// Helper function to parse use_cases data
const parseUseCasesData = (data) => {
if (!data) return [];

// Handle string data
if (typeof data === 'string') {
try {
// Try parsing as JSON first
const jsonData = JSON.parse(data);
return Array.isArray(jsonData) ? jsonData : [jsonData];
} catch (error) {
console.error('Failed to parse use_cases string:', error);
return [];
}
}

// Handle array data
if (Array.isArray(data)) return data;

// Handle object data
if (typeof data === 'object' && data !== null) return [data];

return [];
};

const DepartmentUseCase = ({ use_cases }) => {
// Parse the use_cases data
const useCasesData = useMemo(() => parseUseCasesData(use_cases), [use_cases]);

// Early return if no use cases to display
if (!useCasesData.length) return null;

return (
<div className="flex flex-col gap-16 py-8">
{/* <h2 className="h2 text-center">Department Use Cases</h2> */}

{useCasesData.map((useCase, index) => {
// Extract data with fallbacks
const sectionTitle = useCase?.section_title || '';
const sectionDescription = useCase?.section_description || '';
const imageUrl = useCase?.image || '';

// Skip if no content
if (!sectionTitle && !sectionDescription) return null;

return (
<div
key={`use-case-${index}`}
className={`flex flex-col ${index % 2 === 0 ? 'md:flex-row' : 'md:flex-row-reverse'} gap-8 items-center`}
>
{/* Content Section */}
<div className="flex-1 flex flex-col gap-4">
{sectionTitle && <h3 className="h2">{sectionTitle}</h3>}
{sectionDescription && (
<div className="text-gray-700 text-lg">
{sectionDescription.split('\n\n').map((paragraph, pIndex) => (
<p
key={`paragraph-${index}-${pIndex}`}
className={pIndex > 0 ? 'mt-4' : ''}
>
{paragraph}
</p>
))}
</div>
)}
</div>

{/* Image Section */}
{imageUrl && (
<div className="flex-1 flex justify-center">
<div className="relative w-full max-w-md rounded-lg overflow-hidden shadow-md">
<img
src={imageUrl}
alt={sectionTitle || 'Department use case'}
className="object-cover w-full h-auto max-h-[400px]"
loading="lazy"
onError={(e) => {
e.target.onerror = null;
e.target.src =
'https://via.placeholder.com/400x300?text=Image+Not+Available';
}}
/>
</div>
</div>
)}
</div>
);
})}
</div>
);
};

export default DepartmentUseCase;
40 changes: 40 additions & 0 deletions src/components/departmentFAQ/departmentFAQ.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import FaqSection from '../faqSection/faqSection';

const normalizeFaqData = (rawFaqs) => {
if (!rawFaqs) return [];

let parsed = rawFaqs;

if (typeof rawFaqs === 'string') {
try {
parsed = JSON.parse(rawFaqs);
} catch (e) {
return [];
}
}

// If object with faqs key
if (!Array.isArray(parsed) && parsed?.faqs) {
parsed = parsed.faqs;
}

if (!Array.isArray(parsed)) return [];

return parsed
.map((item) => {
const que = item.question || item.que || item.q || '';
const ans = item.answer || item.ans || item.a || '';

if (!que || !ans) return null;
// FaqSection expects objects with que/ans keys
return { que, ans };
})
.filter(Boolean);
};

export default function DepartmentFAQ({ faqJson }) {
const faqData = normalizeFaqData(faqJson);

if (!faqData.length) return null;
return <FaqSection faqData={faqData} />;
}
Loading