diff --git a/frontend/components/BlogCard.tsx b/frontend/components/BlogCard.tsx
new file mode 100644
index 0000000..e422435
--- /dev/null
+++ b/frontend/components/BlogCard.tsx
@@ -0,0 +1,268 @@
+//blog cards
+"use client";
+
+import { useState } from "react";
+import {
+ Center,
+ VStack,
+ Box,
+ Button,
+ Icon,
+ Text,
+ Heading,
+ HStack,
+ useColorModeValue,
+ AspectRatio,
+} from "@chakra-ui/react";
+import Link from "next/link";
+import Image from "next/image";
+import { motion, useReducedMotion } from "framer-motion";
+import { FaBookOpen, FaCalendarAlt, FaClock, FaArrowRight } from "react-icons/fa";
+
+const MotionBox = motion(Box);
+
+interface Blog {
+ id: number;
+ title: string;
+ description: string;
+ published_on: string;
+ image: string | null;
+ related_link: string | null;
+ markdown_content: string;
+ page_url: string;
+ cover_image?: { src: string; alt: string; caption?: string } | null;
+ authors?: Array<{ name: string; affiliationId?: string }>;
+ affiliations?: Array<{ id: string; name: string }>;
+ publication_links?: Array<{ text: string; url: string; icon?: string }> | null;
+ sections?: Array<{
+ type: string;
+ heading?: string;
+ content?: string;
+ headers?: string[];
+ rows?: string[][];
+ items?: Array<{ id: string; prompt: string; response: string }>;
+ image?: { src: string; alt?: string; caption?: string };
+ }>;
+ team?: {
+ students?: Array<{ name: string }>;
+ advisors?: Array<{ name: string }>;
+ contacts?: Array<{ name: string; email?: string }>;
+ };
+ bibtex?: string;
+}
+
+// Helper functions for blog content processing
+function getSectionsArray(sections: any): any[] {
+ if (Array.isArray(sections)) {
+ return sections;
+ }
+ if (typeof sections === 'string') {
+ try {
+ const parsed = JSON.parse(sections);
+ return Array.isArray(parsed) ? parsed : [];
+ } catch (e) {
+ return [];
+ }
+ }
+ return [];
+}
+
+function getReadingTime(content: string, sections?: any[]): string {
+ const WORDS_PER_MINUTE = 225;
+ let totalText = content || '';
+
+ if (sections && Array.isArray(sections)) {
+ sections.forEach(section => {
+ if (section.heading) totalText += ' ' + section.heading;
+ if (section.content) totalText += ' ' + section.content;
+ if (section.type === 'table') {
+ if (section.headers && Array.isArray(section.headers)) {
+ totalText += ' ' + section.headers.join(' ');
+ }
+ if (section.rows && Array.isArray(section.rows)) {
+ section.rows.forEach((row: string[]) => {
+ if (Array.isArray(row)) totalText += ' ' + row.join(' ');
+ });
+ }
+ }
+ if (section.type === 'examples' && section.items && Array.isArray(section.items)) {
+ section.items.forEach((item: { id: string; prompt: string; response: string }) => {
+ if (item.prompt) totalText += ' ' + item.prompt;
+ if (item.response) totalText += ' ' + item.response;
+ });
+ }
+ if (section.image && section.image.caption) totalText += ' ' + section.image.caption;
+ });
+ }
+
+ if (!totalText || totalText.trim().length === 0) return "1 min read";
+
+ const cleanText = totalText
+ .replace(/[#*_`~\[\]()]/g, ' ')
+ .replace(/<[^>]*>/g, ' ')
+ .replace(/\s+/g, ' ')
+ .trim();
+
+ const words = cleanText.split(/\s+/).filter(word => word.length > 0);
+ const wordCount = words.length;
+ const minutes = Math.max(1, Math.ceil(wordCount / WORDS_PER_MINUTE));
+ return `${minutes} min read`;
+}
+
+function formatDate(dateString: string): string {
+ return new Date(dateString).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ });
+}
+
+// BlogCard Component with Framer Motion animations
+export default function BlogCard({ blog, index }: { blog: Blog; index: number }) {
+ const shouldReduceMotion = useReducedMotion();
+ const [imageError, setImageError] = useState(false);
+
+ const cardBg = useColorModeValue("white", "gray.700");
+ const borderColor = useColorModeValue("orange.100", "orange.700");
+ const textColor = useColorModeValue("gray.600", "gray.300");
+ const headingColor = useColorModeValue("gray.800", "white");
+ const hoverBorderColor = useColorModeValue("orange.300", "orange.500");
+
+ const readingTime = getReadingTime(blog.markdown_content, getSectionsArray(blog.sections));
+ const authorNames = blog.authors?.map(author => author.name).join(", ") || "AI4Bharat Team";
+ const coverImageSrc = blog.image || blog.cover_image?.src;
+
+ return (
+
+
+
+ {coverImageSrc && !imageError ? (
+ setImageError(true)}
+ quality={75}
+ />
+ ) : (
+
+
+
+
+ Article
+
+
+
+ )}
+
+
+
+
+
+
+ {formatDate(blog.published_on)}
+
+
+
+ {readingTime}
+
+
+
+
+ {blog.title}
+
+
+
+ {blog.description}
+
+
+
+ By {authorNames.length > 30 ? `${authorNames.substring(0, 30)}...` : authorNames}
+
+
+ }
+ _hover={{
+ transform: shouldReduceMotion ? 'none' : 'translateY(-1px)',
+ boxShadow: "md"
+ }}
+ mt="auto"
+ >
+ Read Article
+
+
+
+
+ );
+}
diff --git a/frontend/components/ClientBlogsRenderer.tsx b/frontend/components/ClientBlogsRenderer.tsx
new file mode 100644
index 0000000..d1dfcb3
--- /dev/null
+++ b/frontend/components/ClientBlogsRenderer.tsx
@@ -0,0 +1,178 @@
+//blogs rendering
+"use client";
+
+import { useState, useMemo } from "react";
+import {
+ Center,
+ VStack,
+ Box,
+ Button,
+ Icon,
+ Input,
+ InputGroup,
+ InputLeftElement,
+ SimpleGrid,
+ useBreakpointValue,
+ Text,
+ Heading,
+ HStack,
+ useColorModeValue,
+} from "@chakra-ui/react";
+import { AnimatePresence } from "framer-motion";
+import { FaSearch, FaBookOpen } from "react-icons/fa";
+import BlogCard from "./BlogCard"; // Import BlogCard component
+
+// Blog interface (same as in Server Component)
+interface Blog {
+ id: number;
+ title: string;
+ description: string;
+ published_on: string;
+ image: string | null;
+ related_link: string | null;
+ markdown_content: string;
+ page_url: string;
+ cover_image?: { src: string; alt: string; caption?: string } | null;
+ authors?: Array<{ name: string; affiliationId?: string }>;
+ affiliations?: Array<{ id: string; name: string }>;
+ publication_links?: Array<{ text: string; url: string; icon?: string }> | null;
+ sections?: Array<{
+ type: string;
+ heading?: string;
+ content?: string;
+ headers?: string[];
+ rows?: string[][];
+ items?: Array<{ id: string; prompt: string; response: string }>;
+ image?: { src: string; alt?: string; caption?: string };
+ }>;
+ team?: {
+ students?: Array<{ name: string }>;
+ advisors?: Array<{ name: string }>;
+ contacts?: Array<{ name: string; email?: string }>;
+ };
+ bibtex?: string;
+}
+
+// Main Client Component for rendering blogs with interactivity and animations
+export default function ClientBlogsRenderer({
+ initialBlogs,
+ headingColor,
+ textColor
+}: {
+ initialBlogs: Blog[];
+ headingColor: string;
+ textColor: string;
+}) {
+ const [searchTerm, setSearchTerm] = useState("");
+ const gridColumns = useBreakpointValue({
+ base: 1,
+ md: 2,
+ lg: 3,
+ xl: 3
+ });
+ const inputBg = useColorModeValue("white", "gray.700");
+ const borderColor = useColorModeValue("orange.200", "orange.600");
+
+ const filteredBlogs = useMemo(() => {
+ if (!searchTerm.trim()) return initialBlogs;
+ const searchLower = searchTerm.toLowerCase();
+ return initialBlogs.filter(blog =>
+ blog.title.toLowerCase().includes(searchLower) ||
+ blog.description.toLowerCase().includes(searchLower) ||
+ blog.authors?.some(author =>
+ author.name.toLowerCase().includes(searchLower)
+ )
+ );
+ }, [initialBlogs, searchTerm]);
+
+ return (
+ <>
+ {initialBlogs.length > 0 ? (
+ <>
+ {/* Search and Filter UI */}
+
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ bg={inputBg}
+ borderColor={borderColor}
+ borderWidth="1px"
+ borderRadius="md"
+ fontSize="md"
+ _hover={{ borderColor: "orange.300" }}
+ _focus={{
+ borderColor: "orange.400",
+ boxShadow: "0 0 0 1px orange.400"
+ }}
+ _placeholder={{ color: textColor }}
+ />
+
+
+
+
+
+
+ {filteredBlogs.length} {filteredBlogs.length === 1 ? 'article' : 'articles'}
+
+
+
+
+
+
+ {filteredBlogs.length > 0 ? (
+
+
+ {filteredBlogs.map((blog, index) => (
+
+ ))}
+
+
+ ) : (
+
+
+
+
+
+ No articles found
+
+
+ Try adjusting your search terms or browse all articles.
+
+
+
+
+
+ )}
+ >
+ ) : (
+
+
+
+
+
+ Coming Soon
+
+
+ We're working on bringing you amazing research content.
+ Check back soon for the latest insights and innovations.
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/frontend/src/app/blog/[slug]/BlogContentDisplay.tsx b/frontend/src/app/blog/[slug]/BlogContentDisplay.tsx
index 442a07a..12943a1 100644
--- a/frontend/src/app/blog/[slug]/BlogContentDisplay.tsx
+++ b/frontend/src/app/blog/[slug]/BlogContentDisplay.tsx
@@ -1,3 +1,4 @@
+//blog layout
"use client";
import React, { useState, useEffect, useCallback } from 'react';
diff --git a/frontend/src/app/blog/[slug]/page.tsx b/frontend/src/app/blog/[slug]/page.tsx
index 5590783..ddb0789 100644
--- a/frontend/src/app/blog/[slug]/page.tsx
+++ b/frontend/src/app/blog/[slug]/page.tsx
@@ -1,12 +1,10 @@
+//main pages
import React from 'react';
import { API_URL } from '../../config';
import { notFound } from 'next/navigation';
import { Metadata } from 'next';
import BlogContentDisplay from './BlogContentDisplay';
-export const dynamicParams = true;
-export const revalidate = 3600;
-
interface Blog {
id: number;
title: string;
@@ -37,105 +35,44 @@ interface Blog {
bibtex?: string;
}
-let cachedBlogs: Blog[] | null = null;
-let cacheTimestamp: number = 0;
-const CACHE_DURATION = 10 * 60 * 1000;
-
-async function getAllBlogPostsCached(): Promise {
- const now = Date.now();
-
- if (cachedBlogs && (now - cacheTimestamp) < CACHE_DURATION) {
- return cachedBlogs;
- }
-
- const endpoint = `${API_URL}/news/`;
-
- try {
- const res = await fetch(endpoint, {
- headers: {
- 'Content-Type': 'application/json',
- },
- next: {
- revalidate: 3600,
- tags: ['blog-list']
- }
- });
-
- if (!res.ok) {
- return cachedBlogs || [];
- }
-
- const data = await res.json();
- const blogs = Array.isArray(data) ? data : [];
-
- cachedBlogs = blogs;
- cacheTimestamp = now;
-
- return blogs;
- } catch (error) {
- return cachedBlogs || [];
- }
-}
-
async function getIdFromPageUrl(pageUrl: string): Promise {
- const blogs = await getAllBlogPostsCached();
- const blog = blogs.find(blog => blog.page_url === pageUrl);
-
+ const res = await fetch(`${API_URL}/news/`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ cache: 'no-store',
+ });
+ if (!res.ok) {
+ notFound();
+ }
+ const blogs = await res.json();
+ const blog = blogs.find((b: Blog) => b.page_url === pageUrl);
if (!blog) {
notFound();
}
-
return blog.id;
}
async function getBlogPostById(id: number): Promise {
- const endpoint = `${API_URL}/news/${id}`;
-
- try {
- const res = await fetch(endpoint, {
- headers: {
- 'Content-Type': 'application/json',
- },
- next: {
- revalidate: 3600,
- tags: [`blog-${id}`]
- }
- });
-
- if (!res.ok) {
- notFound();
- }
-
- const blog = await res.json();
- return blog;
- } catch (error) {
+ const res = await fetch(`${API_URL}/news/${id}`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ cache: 'no-store',
+ });
+ if (!res.ok) {
notFound();
}
-}
-
-export async function generateStaticParams() {
- try {
- const blogs = await getAllBlogPostsCached();
-
- const params = blogs
- .filter(blog => blog.page_url && blog.page_url.trim() !== '')
- .map((blog) => ({ slug: blog.page_url }));
-
- return params;
- } catch (error) {
- return [];
- }
+ return await res.json();
}
export async function generateMetadata({ params }: { params: { slug: string } }): Promise {
try {
const blogId = await getIdFromPageUrl(params.slug);
const blog = await getBlogPostById(blogId);
-
const imageUrl = blog.image || blog.cover_image?.src;
const images = imageUrl ? [imageUrl] : [];
-
- const metadata: Metadata = {
+ return {
title: `${blog.title} | AI4Bharat Blog`,
description: blog.description,
keywords: [
@@ -183,8 +120,6 @@ export async function generateMetadata({ params }: { params: { slug: string } })
},
},
};
-
- return metadata;
} catch (error) {
return {
title: 'Blog Post | AI4Bharat',
@@ -197,7 +132,6 @@ export default async function BlogDetailPage({ params }: { params: { slug: strin
try {
const blogId = await getIdFromPageUrl(params.slug);
const blog = await getBlogPostById(blogId);
-
return ;
} catch (error) {
notFound();
diff --git a/frontend/src/app/blog/page.tsx b/frontend/src/app/blog/page.tsx
index a55def0..7de883a 100644
--- a/frontend/src/app/blog/page.tsx
+++ b/frontend/src/app/blog/page.tsx
@@ -1,51 +1,23 @@
-"use client";
-
-import { useQuery } from "react-query";
+//blogs sections page
import {
Container,
Heading,
Text,
- Spinner,
Center,
VStack,
Box,
Button,
- useColorModeValue,
- Skeleton,
- Badge,
- Flex,
- HStack,
Icon,
- Input,
- InputGroup,
- InputLeftElement,
- SimpleGrid,
- useBreakpointValue,
- Divider,
Alert,
AlertIcon,
AlertTitle,
AlertDescription,
- useToast,
- IconButton,
- Tooltip,
- AspectRatio,
} from "@chakra-ui/react";
-import Link from "next/link";
-import Image from "next/image";
-import { motion, useReducedMotion, AnimatePresence } from "framer-motion";
-import { useState, useMemo, useCallback } from "react";
-import {
- FaSearch,
- FaCalendarAlt,
- FaUser,
- FaArrowRight,
- FaBookOpen,
- FaTags,
- FaHome,
- FaClock
-} from "react-icons/fa";
-import { API_URL } from "../config";
+import { FaHome } from "react-icons/fa";
+import { API_URL } from "../config";
+import ClientBlogsRenderer from "../../../components/ClientBlogsRenderer";
+
+export const revalidate = 60;
interface Blog {
id: number;
@@ -77,437 +49,39 @@ interface Blog {
bibtex?: string;
}
-const MotionBox = motion(Box);
-const MotionContainer = motion(Container);
-
-function getSectionsArray(sections: any): any[] {
- if (Array.isArray(sections)) {
- return sections;
- }
- if (typeof sections === 'string') {
- try {
- const parsed = JSON.parse(sections);
- return Array.isArray(parsed) ? parsed : [];
- } catch (e) {
- return [];
+async function fetchBlogList(): Promise {
+ const endpoint = `${API_URL}/news/`;
+ try {
+ const response = await fetch(endpoint, {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ next: { revalidate: 60 }
+ });
+ if (!response.ok) {
+ throw new Error(`Failed to fetch blog list: ${response.status} ${response.statusText}`);
}
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error("Error fetching blogs:", error);
+ throw error;
}
- return [];
}
-function getReadingTime(content: string, sections?: any[]): string {
- const WORDS_PER_MINUTE = 225;
-
- let totalText = content || '';
-
- if (sections && Array.isArray(sections)) {
- sections.forEach(section => {
- if (section.heading) {
- totalText += ' ' + section.heading;
- }
-
- if (section.content) {
- totalText += ' ' + section.content;
- }
-
- if (section.type === 'table') {
- if (section.headers && Array.isArray(section.headers)) {
- totalText += ' ' + section.headers.join(' ');
- }
- if (section.rows && Array.isArray(section.rows)) {
- section.rows.forEach((row: string[]) => {
- if (Array.isArray(row)) {
- totalText += ' ' + row.join(' ');
- }
- });
-}
+export default async function BlogsPage() {
+ let blogList: Blog[] = [];
+ let error: Error | null = null;
- }
-
- if (section.type === 'examples' && section.items && Array.isArray(section.items)) {
- section.items.forEach((item: { id: string; prompt: string; response: string }) => {
- if (item.prompt) totalText += ' ' + item.prompt;
- if (item.response) totalText += ' ' + item.response;
- });
-}
-
- if (section.image && section.image.caption) {
- totalText += ' ' + section.image.caption;
- }
- });
+ try {
+ blogList = await fetchBlogList();
+ } catch (err) {
+ error = err instanceof Error ? err : new Error('Unknown error');
}
-
- if (!totalText || totalText.trim().length === 0) {
- return "1 min read";
- }
-
- const cleanText = totalText
- .replace(/[#*_`~\[\]()]/g, ' ')
- .replace(/<[^>]*>/g, ' ')
- .replace(/\s+/g, ' ')
- .trim();
-
- const words = cleanText.split(/\s+/).filter(word => word.length > 0);
- const wordCount = words.length;
-
- const minutes = Math.max(1, Math.ceil(wordCount / WORDS_PER_MINUTE));
-
- return `${minutes} min read`;
-}
-
-function formatDate(dateString: string): string {
- return new Date(dateString).toLocaleDateString('en-US', {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- });
-}
-
-function BlogCardSkeleton() {
- const cardBg = useColorModeValue("white", "gray.700");
- const borderColor = useColorModeValue("orange.100", "orange.800");
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-function SearchAndFilter({
- searchTerm,
- onSearchChange,
- totalCount
-}: {
- searchTerm: string;
- onSearchChange: (value: string) => void;
- totalCount: number;
-}) {
- const inputBg = useColorModeValue("white", "gray.700");
- const borderColor = useColorModeValue("orange.200", "orange.600");
- const textColor = useColorModeValue("gray.600", "gray.300");
-
- return (
-
-
-
-
-
-
- onSearchChange(e.target.value)}
- bg={inputBg}
- borderColor={borderColor}
- borderWidth="1px"
- borderRadius="md"
- fontSize="md"
- _hover={{ borderColor: "orange.300" }}
- _focus={{
- borderColor: "orange.400",
- boxShadow: "0 0 0 1px orange.400"
- }}
- _placeholder={{ color: textColor }}
- />
-
-
-
-
-
-
- {totalCount} {totalCount === 1 ? 'article' : 'articles'}
-
-
- {searchTerm && (
- <>
-
-
- Results for: "{searchTerm}"
-
- >
- )}
-
-
-
- );
-}
-
-function BlogCard({ blog, index }: { blog: Blog; index: number }) {
- const shouldReduceMotion = useReducedMotion();
- const [imageError, setImageError] = useState(false);
-
- const cardBg = useColorModeValue("white", "gray.700");
- const borderColor = useColorModeValue("orange.100", "orange.700");
- const textColor = useColorModeValue("gray.600", "gray.300");
- const headingColor = useColorModeValue("gray.800", "white");
- const hoverBorderColor = useColorModeValue("orange.300", "orange.500");
-
- const readingTime = getReadingTime(blog.markdown_content, getSectionsArray(blog.sections));
- const authorNames = blog.authors?.map(author => author.name).join(", ") || "AI4Bharat Team";
- const coverImageSrc = blog.image || blog.cover_image?.src;
- return (
-
-
-
- {coverImageSrc && !imageError ? (
- setImageError(true)}
- quality={75}
- />
- ) : (
-
-
-
-
- Article
-
-
-
- )}
-
-
-
-
-
-
- {formatDate(blog.published_on)}
-
-
-
- {readingTime}
-
-
-
-
- {blog.title}
-
-
-
- {blog.description}
-
-
-
- By {authorNames.length > 30 ? `${authorNames.substring(0, 30)}...` : authorNames}
-
-
- }
- _hover={{
- transform: shouldReduceMotion ? 'none' : 'translateY(-1px)',
- boxShadow: "md"
- }}
- mt="auto"
- >
- Read Article
-
-
-
-
- );
-}
-
-const fetchBlogList = async (): Promise => {
- const endpoint = `${API_URL}/news/`;
-
- const response = await fetch(endpoint, {
- next: { revalidate: 3600 },
- headers: {
- 'Content-Type': 'application/json',
- }
- });
-
- if (!response.ok) {
- throw new Error(`Failed to fetch blog list: ${response.status} ${response.statusText}`);
- }
-
- const data = await response.json();
- return data;
-};
-
-export default function BlogsPage() {
- const [searchTerm, setSearchTerm] = useState("");
- const shouldReduceMotion = useReducedMotion();
- const toast = useToast();
-
- const { data: blogList, isLoading, error, refetch } = useQuery(
- ["fetchBlogList"],
- fetchBlogList,
- {
- staleTime: 5 * 60 * 1000,
- cacheTime: 10 * 60 * 1000,
- onError: () => {
- toast({
- title: "Failed to load articles",
- description: "Please check your connection and try again.",
- status: "error",
- duration: 5000,
- isClosable: true,
- });
- }
- }
- );
-
- const filteredBlogs = useMemo(() => {
- if (!blogList) return [];
- if (!searchTerm.trim()) return blogList;
-
- const searchLower = searchTerm.toLowerCase();
- return blogList.filter(blog =>
- blog.title.toLowerCase().includes(searchLower) ||
- blog.description.toLowerCase().includes(searchLower) ||
- blog.authors?.some(author =>
- author.name.toLowerCase().includes(searchLower)
- )
- );
- }, [blogList, searchTerm]);
-
- const bgColor = useColorModeValue("orange.50", "gray.900");
- const textColor = useColorModeValue("gray.700", "gray.300");
- const headingColor = useColorModeValue("gray.900", "white");
-
- const gridColumns = useBreakpointValue({
- base: 1,
- md: 2,
- lg: 3,
- xl: 3
- });
-
- if (isLoading) {
- return (
-
-
-
-
- AI4Bharat Blog
-
-
- Discover cutting-edge research and insights from the AI4Bharat community
-
-
-
-
- {Array.from({ length: 6 }).map((_, index) => (
-
- ))}
-
-
-
- );
- }
+ const bgColor = "orange.50";
+ const textColor = "gray.700";
+ const headingColor = "gray.900";
if (error) {
return (
@@ -536,12 +110,13 @@ export default function BlogsPage() {
@@ -552,114 +127,37 @@ export default function BlogsPage() {
return (
-
+
-
-
- AI4Bharat Blog
-
-
-
-
+
-
- Discover cutting-edge research and insights from the AI4Bharat community.
- Explore our latest work in AI for Indian languages and beyond.
-
-
+ Discover cutting-edge research and insights from the AI4Bharat community.
+ Explore our latest work in AI for Indian languages and beyond.
+
- {blogList && blogList.length > 0 && (
-
-
-
- )}
-
- {blogList && blogList.length > 0 ? (
- filteredBlogs.length > 0 ? (
-
-
- {filteredBlogs.map((blog, index) => (
-
- ))}
-
-
- ) : (
-
-
-
-
-
- No articles found
-
-
- Try adjusting your search terms or browse all articles.
-
-
-
-
-
- )
- ) : (
-
-
-
-
-
- Coming Soon
-
-
- We're working on bringing you amazing research content.
- Check back soon for the latest insights and innovations.
-
-
-
-
- )}
-
+
+
);
}