2026-02-03 23:16:16 +05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
import { Post, NewsArticle, ResearchPaper, BusinessStory, ServiceItemData, ClientLogo, BusinessCourse, StudentProgram, AcceleratorProject, InvestmentProject, Vacancy, GalleryItem } from './types';
|
|
|
|
|
|
2026-02-04 20:49:18 +05:00
|
|
|
const STRAPI_URL = 'https://n8n.iieasy.ru';
|
2026-02-03 23:16:16 +05:00
|
|
|
const API_TOKEN = '91344ae88ae3e496f72d6ae9c157a3e929c3078b6269b57d3751d81026880cca6e7dcec074bffd77c8fe853dce9308f8d7dd494cf7b8c804a2875f0f4c24c0419cee196adf247d01cd5ddd5893d325bff34ad236febe0b508114ec906b12423e658d7210be94f2fcc37184334e11065538531c6d935de5becf81f2a48fdd6318';
|
|
|
|
|
|
|
|
|
|
interface StrapiResponse<T> {
|
|
|
|
|
data: StrapiDataItem<T>[];
|
|
|
|
|
meta: {
|
|
|
|
|
pagination: {
|
|
|
|
|
page: number;
|
|
|
|
|
pageSize: number;
|
|
|
|
|
pageCount: number;
|
|
|
|
|
total: number;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface StrapiDataItem<T_Attributes> {
|
|
|
|
|
id: number;
|
|
|
|
|
attributes: T_Attributes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface StrapiImageAttributes {
|
|
|
|
|
id?: number;
|
|
|
|
|
url: string;
|
|
|
|
|
name?: string;
|
|
|
|
|
alternativeText?: string | null;
|
|
|
|
|
caption?: string | null;
|
|
|
|
|
width?: number;
|
|
|
|
|
height?: number;
|
|
|
|
|
formats?: {
|
|
|
|
|
thumbnail?: StrapiImageFormat;
|
|
|
|
|
small?: StrapiImageFormat;
|
|
|
|
|
medium?: StrapiImageFormat;
|
|
|
|
|
large?: StrapiImageFormat;
|
|
|
|
|
};
|
|
|
|
|
hash?: string;
|
|
|
|
|
ext?: string;
|
|
|
|
|
mime?: string;
|
|
|
|
|
size?: number;
|
|
|
|
|
provider?: string;
|
|
|
|
|
provider_metadata?: any;
|
|
|
|
|
createdAt?: string;
|
|
|
|
|
updatedAt?: string;
|
|
|
|
|
publishedAt?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface StrapiImageFormat {
|
|
|
|
|
name: string;
|
|
|
|
|
hash: string;
|
|
|
|
|
ext: string;
|
|
|
|
|
mime: string;
|
|
|
|
|
path: string | null;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
size: number;
|
|
|
|
|
sizeInBytes?: number;
|
|
|
|
|
url: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface StrapiMediaPopulated {
|
|
|
|
|
data: {
|
|
|
|
|
id: number;
|
|
|
|
|
attributes: StrapiImageAttributes;
|
|
|
|
|
} | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface StrapiMultipleMediaPopulated {
|
|
|
|
|
data: {
|
|
|
|
|
id: number;
|
|
|
|
|
attributes: StrapiImageAttributes;
|
|
|
|
|
}[] | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getFullImageUrl = (mediaFieldData?: StrapiImageAttributes | StrapiMediaPopulated): string => {
|
|
|
|
|
const placeholderBase = 'https://picsum.photos/seed/';
|
|
|
|
|
const placeholderFallback = `${placeholderBase}placeholder-fallback/600/400`;
|
|
|
|
|
|
|
|
|
|
if (!mediaFieldData) {
|
|
|
|
|
console.warn('getFullImageUrl called with undefined mediaFieldData');
|
|
|
|
|
return placeholderFallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle case where mediaFieldData is already attributes
|
|
|
|
|
if ('url' in mediaFieldData && typeof mediaFieldData.url === 'string' && !('data' in mediaFieldData)) {
|
|
|
|
|
if (mediaFieldData.url.startsWith('http://') || mediaFieldData.url.startsWith('https://')) {
|
|
|
|
|
return mediaFieldData.url;
|
|
|
|
|
}
|
|
|
|
|
return `${STRAPI_URL}${mediaFieldData.url}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle case where mediaFieldData has a 'data' property (populated media field)
|
|
|
|
|
if ('data' in mediaFieldData && mediaFieldData.data?.attributes?.url) {
|
|
|
|
|
const url = mediaFieldData.data.attributes.url;
|
|
|
|
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
return `${STRAPI_URL}${url}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.warn('getFullImageUrl could not extract URL from mediaFieldData:', mediaFieldData);
|
|
|
|
|
return `${placeholderBase}no-url-extracted/600/400`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Updated rich text renderer to allow raw HTML
|
|
|
|
|
const transformRichTextToString = (nodes: any[] | undefined): string => {
|
|
|
|
|
if (!nodes || !Array.isArray(nodes)) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper to escape attributes to prevent some forms of XSS.
|
|
|
|
|
const escapeAttribute = (text: string) =>
|
|
|
|
|
(text || '').replace(/"/g, '"');
|
|
|
|
|
|
|
|
|
|
// Helper to escape HTML for code blocks intended for display.
|
|
|
|
|
const escapeHtmlForCodeDisplay = (text: string) =>
|
|
|
|
|
(text || '')
|
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
.replace(/"/g, '"')
|
|
|
|
|
.replace(/'/g, ''');
|
|
|
|
|
|
|
|
|
|
const serializeNodeToHtml = (node: any): string => {
|
|
|
|
|
// For text nodes, we now trust the content and do not escape it.
|
|
|
|
|
// This allows users to write raw HTML directly in the editor.
|
|
|
|
|
if (node.type === 'text') {
|
|
|
|
|
let text = node.text || ''; // Raw text, not escaped.
|
|
|
|
|
if (node.bold) text = `<strong>${text}</strong>`;
|
|
|
|
|
if (node.italic) text = `<em>${text}</em>`;
|
|
|
|
|
if (node.underline) text = `<u>${text}</u>`;
|
|
|
|
|
if (node.strikethrough) text = `<s>${text}</s>`;
|
|
|
|
|
if (node.code) text = `<code>${text}</code>`;
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const childrenHtml = node.children?.map(serializeNodeToHtml).join('') || '';
|
|
|
|
|
|
|
|
|
|
switch (node.type) {
|
|
|
|
|
case 'heading':
|
|
|
|
|
return `<h${node.level || 1}>${childrenHtml}</h${node.level || 1}>`;
|
|
|
|
|
case 'paragraph':
|
|
|
|
|
return `<p>${childrenHtml || ''}</p>`;
|
|
|
|
|
case 'quote':
|
|
|
|
|
return `<blockquote>${childrenHtml}</blockquote>`;
|
|
|
|
|
case 'list':
|
|
|
|
|
const listTag = node.format === 'ordered' ? 'ol' : 'ul';
|
|
|
|
|
return `<${listTag}>${childrenHtml}</${listTag}>`;
|
|
|
|
|
case 'list-item':
|
|
|
|
|
return `<li>${childrenHtml}</li>`;
|
|
|
|
|
case 'link':
|
|
|
|
|
return `<a href="${escapeAttribute(node.url)}" target="_blank" rel="noopener noreferrer">${childrenHtml}</a>`;
|
|
|
|
|
case 'image':
|
|
|
|
|
if (!node.image?.url) return '';
|
|
|
|
|
const imageUrl = getFullImageUrl(node.image);
|
|
|
|
|
const altText = node.image.alternativeText || '';
|
|
|
|
|
const caption = node.image.caption ? `<figcaption>${node.image.caption}</figcaption>` : ''; // Caption is not escaped
|
|
|
|
|
return `<figure><img src="${imageUrl}" alt="${escapeAttribute(altText)}" loading="lazy"/>${caption}</figure>`;
|
|
|
|
|
case 'code':
|
|
|
|
|
// For code blocks, we explicitly escape the content for display.
|
|
|
|
|
const codeContent = node.children?.[0]?.text || '';
|
|
|
|
|
return `<pre><code class="language-${node.language || ''}">${escapeHtmlForCodeDisplay(codeContent)}</code></pre>`;
|
|
|
|
|
default:
|
|
|
|
|
// For any other block types that just wrap children
|
|
|
|
|
return childrenHtml;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return nodes.map(serializeNodeToHtml).join('');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const transformRichTextToListArray = (richTextField: any[] | undefined): string[] => {
|
|
|
|
|
if (!richTextField || !Array.isArray(richTextField)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
const result: string[] = [];
|
|
|
|
|
richTextField.forEach(block => {
|
|
|
|
|
if (block.type === 'paragraph' && Array.isArray(block.children)) {
|
|
|
|
|
const paragraphText = block.children
|
|
|
|
|
.filter((child: any) => child.type === 'text' && typeof child.text === 'string')
|
|
|
|
|
.map((child: any) => child.text)
|
|
|
|
|
.join('');
|
|
|
|
|
if (paragraphText) {
|
|
|
|
|
paragraphText.split('\n').forEach(line => {
|
|
|
|
|
const trimmedLine = line.trim();
|
|
|
|
|
if (trimmedLine) result.push(trimmedLine);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else if (block.type === 'list' && Array.isArray(block.children)) {
|
|
|
|
|
block.children.forEach((listItem: any) => {
|
|
|
|
|
if (listItem.type === 'list-item' && Array.isArray(listItem.children)) {
|
|
|
|
|
const listItemText = listItem.children
|
|
|
|
|
.filter((child: any) => child.type === 'text' && typeof child.text === 'string')
|
|
|
|
|
.map((child: any) => child.text)
|
|
|
|
|
.join('')
|
|
|
|
|
.trim();
|
|
|
|
|
if (listItemText) result.push(listItemText);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformGalleryItems = (galleryData: StrapiMultipleMediaPopulated | undefined): GalleryItem[] => {
|
|
|
|
|
if (!galleryData || !Array.isArray(galleryData.data)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
return galleryData.data.map((mediaItem: StrapiDataItem<StrapiImageAttributes>) => {
|
|
|
|
|
const attributes = mediaItem.attributes;
|
|
|
|
|
const type = attributes.mime && attributes.mime.startsWith('video') ? 'video' : 'image';
|
|
|
|
|
return {
|
|
|
|
|
id: mediaItem.id.toString(),
|
|
|
|
|
type: type,
|
|
|
|
|
url: getFullImageUrl(attributes),
|
|
|
|
|
altText: attributes.alternativeText || undefined,
|
|
|
|
|
caption: attributes.caption || undefined,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const transformPost = (item: any): Post => {
|
|
|
|
|
return {
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
title: item.title || 'Untitled Post',
|
|
|
|
|
category: item.category || 'Uncategorized',
|
|
|
|
|
date: item.date || (item.publishedAt ? new Date(item.publishedAt).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }) : undefined),
|
|
|
|
|
publishedAt: item.publishedAt,
|
|
|
|
|
readTime: item.readTime,
|
|
|
|
|
imageUrl: getFullImageUrl(item.image),
|
|
|
|
|
videoUrl: item.videoUrl,
|
|
|
|
|
isFeatured: item.isFeatured || false,
|
|
|
|
|
description: item.description,
|
|
|
|
|
fullContent: Array.isArray(item.fullContent) ? transformRichTextToString(item.fullContent) : (item.fullContent || ''),
|
|
|
|
|
gallery: transformGalleryItems(item.gallery as StrapiMultipleMediaPopulated | undefined),
|
|
|
|
|
href: item.slug || `post-${item.id}`,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformNewsArticle = (item: any): NewsArticle => {
|
|
|
|
|
return {
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
title: item.title || 'Untitled News Article',
|
|
|
|
|
category: item.category || 'General News',
|
|
|
|
|
date: item.date || (item.publishedAt ? new Date(item.publishedAt).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }) : undefined),
|
|
|
|
|
publishedAt: item.publishedAt,
|
|
|
|
|
imageUrl: getFullImageUrl(item.image),
|
|
|
|
|
href: item.slug || `news-${item.id}`,
|
|
|
|
|
description: item.description,
|
|
|
|
|
fullContent: Array.isArray(item.fullContent) ? transformRichTextToString(item.fullContent) : (item.fullContent || ''),
|
|
|
|
|
gallery: transformGalleryItems(item.gallery as StrapiMultipleMediaPopulated | undefined),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformResearchPaper = (item: any): ResearchPaper => {
|
|
|
|
|
let authorsArray: string[] | undefined = undefined;
|
|
|
|
|
if (Array.isArray(item.authors)) {
|
|
|
|
|
authorsArray = item.authors.map((author: any) => {
|
|
|
|
|
if (typeof author === 'string') return author;
|
|
|
|
|
if (typeof author === 'object' && author !== null && typeof author.name === 'string') return author.name;
|
|
|
|
|
return 'Unknown Author';
|
|
|
|
|
});
|
|
|
|
|
} else if (typeof item.authors === 'string') {
|
|
|
|
|
authorsArray = [item.authors];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
title: item.title || 'Untitled Research Paper',
|
|
|
|
|
category: item.category || 'Research',
|
|
|
|
|
date: item.date || (item.publishedAt ? new Date(item.publishedAt).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }) : undefined),
|
|
|
|
|
publishedAt: item.publishedAt,
|
|
|
|
|
imageUrl: getFullImageUrl(item.image),
|
|
|
|
|
href: item.slug || `research-${item.id}`,
|
|
|
|
|
authors: authorsArray,
|
|
|
|
|
abstract: item.abstract,
|
|
|
|
|
fullContent: Array.isArray(item.fullContent) ? transformRichTextToString(item.fullContent) : (item.fullContent || ''),
|
|
|
|
|
gallery: transformGalleryItems(item.gallery as StrapiMultipleMediaPopulated | undefined),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformBusinessStory = (item: any): BusinessStory => {
|
|
|
|
|
return {
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
title: item.title || 'Untitled Business Story',
|
|
|
|
|
category: item.category || 'Case Study',
|
|
|
|
|
imageUrl: getFullImageUrl(item.image),
|
|
|
|
|
href: item.slug || `biz-${item.id}`,
|
|
|
|
|
description: item.description,
|
|
|
|
|
fullContent: Array.isArray(item.fullContent) ? transformRichTextToString(item.fullContent) : (item.fullContent || ''),
|
|
|
|
|
gallery: transformGalleryItems(item.gallery as StrapiMultipleMediaPopulated | undefined),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformServiceItem = (item: any): ServiceItemData => {
|
|
|
|
|
return {
|
|
|
|
|
icon: item.icon || 'CpuChipIcon',
|
|
|
|
|
title: item.title || 'Untitled Service',
|
|
|
|
|
description: item.description || '',
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformClientLogo = (item: any): ClientLogo => {
|
|
|
|
|
return {
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
name: item.name || 'Unnamed Client',
|
|
|
|
|
imageUrl: getFullImageUrl(item.image),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformBusinessCourse = (item: any): BusinessCourse => {
|
|
|
|
|
return {
|
|
|
|
|
icon: item.icon || 'AcademicCapIcon',
|
|
|
|
|
title: item.title || 'Untitled Business Course',
|
|
|
|
|
description: item.description || '',
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformStudentProgram = (item: any): StudentProgram => {
|
|
|
|
|
return {
|
|
|
|
|
icon: item.icon || 'AcademicCapIcon',
|
|
|
|
|
title: item.title || 'Untitled Student Program',
|
|
|
|
|
targetAudience: item.targetAudience || '',
|
|
|
|
|
description: item.description || '',
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformAcceleratorProject = (item: any): AcceleratorProject => {
|
|
|
|
|
return {
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
name: item.name || 'Untitled Accelerator Project',
|
|
|
|
|
tagline: item.tagline || '',
|
|
|
|
|
description: Array.isArray(item.description_rt) ? transformRichTextToString(item.description_rt) : (typeof item.description === 'string' ? item.description : ''),
|
|
|
|
|
imageUrl: getFullImageUrl(item.image),
|
|
|
|
|
category: item.category || 'General AI',
|
|
|
|
|
websiteUrl: item.websiteUrl,
|
|
|
|
|
statuspro: item.statuspro || 'Active',
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformInvestmentProject = (item: any): InvestmentProject => {
|
|
|
|
|
return {
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
name: item.name || 'Untitled Investment Project',
|
|
|
|
|
industry: item.industry || 'AI Sector',
|
|
|
|
|
problem: Array.isArray(item.problem_rt) ? transformRichTextToString(item.problem_rt) : (typeof item.problem === 'string' ? item.problem : ''),
|
|
|
|
|
solution: Array.isArray(item.solution_rt) ? transformRichTextToString(item.solution_rt) : (typeof item.solution === 'string' ? item.solution : ''),
|
|
|
|
|
teamSize: item.teamSize || 1,
|
|
|
|
|
fundingSought: item.fundingSought || 'N/A',
|
|
|
|
|
imageUrl: getFullImageUrl(item.image),
|
|
|
|
|
contactEmail: item.contactEmail,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const transformVacancy = (item: any): Vacancy => {
|
|
|
|
|
return {
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
title: item.title || 'Untitled Vacancy',
|
|
|
|
|
department: item.department || 'N/A',
|
|
|
|
|
location: item.location || 'N/A',
|
|
|
|
|
type: item.jobType || 'Full-time',
|
|
|
|
|
description: item.shortDescription || '',
|
|
|
|
|
fullDescription: transformRichTextToString(item.fullDescription_rt),
|
|
|
|
|
responsibilities: transformRichTextToListArray(item.responsibilities_rt),
|
|
|
|
|
qualifications: transformRichTextToListArray(item.qualifications_rt),
|
|
|
|
|
offer: transformRichTextToListArray(item.offer_rt),
|
|
|
|
|
href: item.slug || item.id.toString(),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function fetchData<T_Transformed>(
|
|
|
|
|
endpoint: string,
|
|
|
|
|
transformFn: (item: any) => T_Transformed
|
|
|
|
|
): Promise<T_Transformed[]> {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(`${STRAPI_URL}/api/${endpoint}?populate=*`, {
|
|
|
|
|
headers: {
|
|
|
|
|
'Authorization': `Bearer ${API_TOKEN}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
let errorBody = "Could not read error body.";
|
|
|
|
|
try { errorBody = await response.text(); } catch (e) { /* ignore */ }
|
|
|
|
|
console.error(`Strapi fetch error for ${endpoint}: ${response.status} ${response.statusText}. Body: ${errorBody}`);
|
|
|
|
|
throw new Error(`Failed to fetch ${endpoint}: ${response.status}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let jsonResponse;
|
|
|
|
|
try {
|
|
|
|
|
jsonResponse = await response.json();
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.error(`Error parsing JSON for ${endpoint}:`, e.message);
|
|
|
|
|
try {
|
|
|
|
|
const textResponse = await response.text();
|
|
|
|
|
console.error(`Raw response text for ${endpoint}: ${textResponse.substring(0, 500)}...`);
|
|
|
|
|
} catch { /* ignore if reading text also fails */ }
|
|
|
|
|
throw new Error(`Failed to parse JSON for ${endpoint}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!jsonResponse.data) {
|
|
|
|
|
console.warn(`No 'data' field in Strapi response for ${endpoint}:`, jsonResponse);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
if (!Array.isArray(jsonResponse.data)) {
|
|
|
|
|
console.warn(`Data for ${endpoint} is not an array:`, jsonResponse.data);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const transformedData: T_Transformed[] = [];
|
|
|
|
|
for (const item of jsonResponse.data) {
|
|
|
|
|
try {
|
|
|
|
|
transformedData.push(transformFn(item.attributes ? { id: item.id, ...item.attributes } : item));
|
|
|
|
|
} catch (transformError: any) {
|
|
|
|
|
console.error(`Error transforming item with id ${item?.id || 'unknown'} from ${endpoint}: ${transformError.message}`, item, transformError.stack);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return transformedData;
|
|
|
|
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error(`Critical error fetching or transforming data for ${endpoint}: ${error.message}`, error.stack);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const fetchPosts = (): Promise<Post[]> => fetchData('posts', transformPost);
|
|
|
|
|
export const fetchNewsArticles = (): Promise<NewsArticle[]> => fetchData('news-articles', transformNewsArticle);
|
|
|
|
|
export const fetchResearchPapers = (): Promise<ResearchPaper[]> => fetchData('research-papers', transformResearchPaper);
|
|
|
|
|
export const fetchBusinessStories = (): Promise<BusinessStory[]> => fetchData('business-stories', transformBusinessStory);
|
|
|
|
|
export const fetchServiceItems = (): Promise<ServiceItemData[]> => fetchData('services', transformServiceItem);
|
|
|
|
|
export const fetchClientLogos = (): Promise<ClientLogo[]> => fetchData('client-logos', transformClientLogo);
|
|
|
|
|
export const fetchBusinessCourses = (): Promise<BusinessCourse[]> => fetchData('business-courses', transformBusinessCourse);
|
|
|
|
|
export const fetchStudentPrograms = (): Promise<StudentProgram[]> => fetchData('student-programs', transformStudentProgram);
|
|
|
|
|
export const fetchAcceleratorProjects = (): Promise<AcceleratorProject[]> => fetchData('accelerator-projects', transformAcceleratorProject);
|
|
|
|
|
export const fetchInvestmentProjects = (): Promise<InvestmentProject[]> => fetchData('investment-opportunities', transformInvestmentProject);
|
|
|
|
|
export const fetchVacancies = (): Promise<Vacancy[]> => fetchData('vacancies', transformVacancy);
|