Skip to content

Commit

Permalink
feat: migrate layout and campaigns (#2743)
Browse files Browse the repository at this point in the history
Co-authored-by: Akshat Nema <[email protected]>%0ACo-authored-by: akshatnema <[email protected]>%0ACo-authored-by: aks-nema <[email protected]>%0ACo-authored-by: Ansh Goyal <[email protected]>
  • Loading branch information
Shurtu-gal and anshgoyalevil authored Mar 28, 2024
1 parent 6760c9d commit ce3446d
Show file tree
Hide file tree
Showing 23 changed files with 1,021 additions and 126 deletions.
4 changes: 3 additions & 1 deletion components/AuthorAvatars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export default function AuthorAvatars({ authors = [] }: AuthorAvatarsProps) {
<img
key={index}
title={author.name}
className={`${index > 0 ? `left- absolute${index * 7} top-0` : `mr- relative${(authors.length - 1) * 7}`} z-${(authors.length - 1 - index) * 10} size-10 rounded-full border-2 border-white object-cover hover:z-50`}
className={`${index > 0 ? `left- absolute${index * 7} top-0` : `mr- relative${(authors.length - 1) * 7}`}
z-${(authors.length - 1 - index) * 10} size-10 rounded-full border-2
border-white object-cover hover:z-50`}
src={author.photo}
loading='lazy'
data-testid='AuthorAvatars-img'
Expand Down
88 changes: 88 additions & 0 deletions components/Head.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import Head from 'next/head';
import { useContext } from 'react';
import ReactGA from 'react-ga';
import TagManager from 'react-gtm-module';

import AppContext from '../context/AppContext';

interface IHeadProps {
title: string;
description?: string;
image: string;
rssTitle?: string;
rssLink?: string;
}

/**
* @param {string} props.title - The title of the page
* @param {string} props.description - The description of the page
* @param {string} props.image - The image of the page
* @param {string} props.rssTitle - The RSS title of the page
* @param {string} props.rssLink - The RSS link of the page
* @description The head of the page with the meta tags
*/
export default function HeadComponent({
title,
description = `Open source tools to easily build and maintain your event-driven architecture.
All powered by the AsyncAPI specification, the industry standard for defining asynchronous APIs.`,
image = '/img/social/website-card.jpg',
rssTitle = 'RSS Feed for AsyncAPI Initiative Blog',
rssLink = '/rss.xml'
}: IHeadProps) {
const url = process.env.DEPLOY_PRIME_URL || process.env.DEPLOY_URL || 'http://localhost:3000';
const appContext = useContext(AppContext);
const { path = '' } = appContext || {};
let currImage = image;

const permalink = `${url}${path}`;
let type = 'website';

if (path.startsWith('/docs') || path.startsWith('/blog')) {
type = 'article';
}

if (!image.startsWith('http') && !image.startsWith('https')) {
currImage = `${url}${image}`;
}

const permTitle = 'AsyncAPI Initiative for event-driven APIs';

const currTitle = title ? `${title} | ${permTitle}` : permTitle;

// enable google analytics
if (typeof window !== 'undefined' && window.location.hostname.includes('asyncapi.com')) {
TagManager.initialize({ gtmId: 'GTM-T58BTVQ' });
ReactGA.initialize('UA-109278936-1');
ReactGA.pageview(window.location.pathname + window.location.search);
}

return (
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no' />
<meta httpEquiv='x-ua-compatible' content='ie=edge' />
<meta httpEquiv='Content-Type' content='text/html; charset=utf-8' />
<meta name='description' content={description} />
<link rel='alternate' type='application/rss+xml' title={rssTitle} href={rssLink} />

{/* Google / Search Engine Tags */}
<meta itemProp='name' content={title} />
<meta itemProp='description' content={description} />
<meta itemProp='image' content={currImage} />

{/* Twitter Card data */}
<meta name='twitter:card' content='summary_large_image' />
<meta name='twitter:title' content={title} />
<meta name='twitter:description' content={description} />
<meta name='twitter:image' content={currImage} />

{/* Open Graph data */}
<meta property='og:title' content={title} />
<meta property='og:type' content={type} />
<meta property='og:url' content={permalink} />
<meta property='og:image' content={currImage} />
<meta property='og:description' content={description} />

<title>{currTitle}</title>
</Head>
);
}
99 changes: 99 additions & 0 deletions components/TOC.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useState } from 'react';
import Scrollspy from 'react-scrollspy';
import { twMerge } from 'tailwind-merge';

import ArrowRight from './icons/ArrowRight';

interface ITOCProps {
className?: string;
cssBreakingPoint?: string;
toc: {
lvl: number;
content: string;
slug: string;
}[];
contentSelector?: string;
depth?: number;
}

/**
* @description The table of contents
* @param {string} props.className - The class name of the component
* @param {string} props.cssBreakingPoint - The CSS breaking point
* @param {Array} props.toc - The table of contents
* @param {string} props.contentSelector - The content selector
* @param {number} props.depth - The depth of the table of contents
*/
export default function TOC({ className, cssBreakingPoint = 'xl', toc, contentSelector, depth = 2 }: ITOCProps) {
const [open, setOpen] = useState(false);

if (!toc || !toc.length) return null;
const minLevel = toc.reduce((mLevel, item) => (!mLevel || item.lvl < mLevel ? item.lvl : mLevel), 0);
const tocItems = toc
.filter((item) => item.lvl <= minLevel + depth)
.map((item) => ({
...item,
content: item.content.replace(/[\s]?\{#[\w\d\-_]+\}$/, '').replace(/(<([^>]+)>)/gi, ''),
// For TOC rendering in specification files in the spec repo we have "a" tags added manually to the spec
// markdown document MDX takes these "a" tags and uses them to render the "id" for headers like
// a-namedefinitionsapplicationaapplication slugWithATag contains transformed heading name that is later used
// for scroll spy identification
slugWithATag: item.content
.replace(/[<>?!:`'."\\/=]/gi, '')
.replace(/\s/gi, '-')
.toLowerCase()
}));

return (
<div
className={twMerge(`${className} ${tocItems.length ? '' : 'hidden'}
${cssBreakingPoint === 'xl' ? 'xl:block' : 'lg:block'} md:top-24 md:max-h-(screen-14) z-20`)}
onClick={() => setOpen(!open)}
>
<div
className={`flex cursor-pointer ${tocItems.length ? '' : 'hidden'}
${cssBreakingPoint === 'xl' ? 'xl:cursor-auto' : 'lg:cursor-auto'} xl:mt-2`}
>
<h5
className={twMerge(`${open && 'mb-4'} flex-1 text-primary-500 font-medium uppercase tracking-wide
text-sm font-sans antialiased ${
cssBreakingPoint === 'xl'
? `xl:mb-4 xl:text-xs xl:text-gray-900
xl:font-bold`
: 'lg:mb-4 lg:text-xs lg:text-gray-900 lg:font-bold'
}`)}
data-testid='TOC-Heading'
>
On this page
</h5>
<div className={`text-underline p4 text-center ${cssBreakingPoint === 'xl' ? 'xl:hidden' : 'lg:hidden'}`}>
<ArrowRight
className={`${open ? '-rotate-90' : 'rotate-90'} -mt-0.5 h-6
text-primary-500 transition duration-200 ease-in-out`}
/>
</div>
</div>
<div className={`${!open && 'hidden'} ${cssBreakingPoint === 'xl' ? 'xl:block' : 'lg:block'}`}>
<Scrollspy
items={tocItems.map((item) => item.slug)}
currentClassName='text-primary-500 font-bold'
componentTag='div'
rootEl={contentSelector}
offset={-120}
>
{tocItems.map((item, index) => (
<a
className={`pl-${2 ** (item.lvl - 1)} font-normal mb-1 block font-sans text-sm
text-gray-900 antialiased transition duration-100 ease-in-out hover:underline`}
href={`#${item.slug}`}
key={index}
data-testid='TOC-Link'
>
{item.content.replaceAll('`', '')}
</a>
))}
</Scrollspy>
</div>
</div>
);
}
69 changes: 69 additions & 0 deletions components/campaigns/AnnouncementBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react';

import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
import { ParagraphTypeStyle } from '@/types/typography/Paragraph';

import Button from '../buttons/Button';
import Heading from '../typography/Heading';
import Paragraph from '../typography/Paragraph';
import AnnouncementRemainingDays from './AnnouncementRemainingDays';

interface BannerProps {
title: string;
dateLocation: string;
cfaText: string;
eventName: string;
cfpDeadline: string;
link: string;
city: string;
activeBanner: boolean;
small: boolean;
className: string;
}

/**
* @description The banner to use for Announcement
* @param {string} props.title - The title of the banner
* @param {string} props.dateLocation - The date and location of the banner
* @param {string} props.cfaText - The call for action text
* @param {string} props.eventName - The name of the event
* @param {string} props.cfpDeadline - The deadline for the call for speakers
* @param {string} props.link - The link of the banner
* @param {string} props.city - The city of the banner
* @param {Boolean} props.activeBanner - Whether the banner is active
* @param {Boolean} props.small - Whether the banner is small
* @param {string} props.className - The class name of the banner
*/
export default function Banner({
title,
dateLocation,
cfaText,
eventName,
cfpDeadline,
link,
city,
activeBanner,
small,
className
}: BannerProps) {
return (
<div
className={`absolute size-full rounded border border-gray-200 bg-gray-50 py-6
transition-transform${className} ${small ? 'mb-4' : 'mx-3 mb-6 mt-3 p-3'}
${activeBanner ? 'z-10 scale-100 opacity-100' : 'z-0 scale-90 opacity-0'}`}
data-testid='AnnouncementHero-main-div'
>
<Heading className='countdown-text-gradient' level={HeadingLevel.h2} typeStyle={HeadingTypeStyle.lg}>
{title}
</Heading>
<Heading className='countdown-text-gradient' level={HeadingLevel.h2} typeStyle={HeadingTypeStyle.md}>
{city}
</Heading>
<Paragraph typeStyle={ParagraphTypeStyle.lg}>{dateLocation}</Paragraph>
<AnnouncementRemainingDays dateTime={cfpDeadline} eventName={eventName} />
<div className='mt-6 space-x-2 pb-2'>
<Button href={link} target='_blank' text={cfaText} data-testid='AnnouncementHero-submit-session' />
</div>
</div>
);
}
98 changes: 98 additions & 0 deletions components/campaigns/AnnouncementHero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useEffect, useState } from 'react';

import ArrowLeft from '../icons/ArrowLeft';
import ArrowRight from '../icons/ArrowRight';
import Container from '../layout/Container';
import Banner from './AnnouncementBanner';
import { banners } from './banners';

interface IAnnouncementHeroProps {
className?: string;
small?: boolean;
}

/**
* @param {string} props.className - The class name of the announcement hero
* @param {Boolean} props.small - Whether the banner is small
* @param {Boolean} props.hideVideo - Whether the video should be hidden
* @description The announcement hero
*/
export default function AnnouncementHero({ className = '', small = false }: IAnnouncementHeroProps) {
const [activeIndex, setActiveIndex] = useState(0);

const len = banners.length;

const goToPrevious = () => {
setActiveIndex((prevIndex) => (prevIndex === 0 ? len - 1 : prevIndex - 1));
};

const goToNext = () => {
setActiveIndex((prevIndex) => (prevIndex === len - 1 ? 0 : prevIndex + 1));
};

const goToIndex = (index: number) => {
setActiveIndex(index);
};

useEffect(() => {
const interval = setInterval(() => setActiveIndex((index) => index + 1), 5000);

return () => {
clearInterval(interval);
};
}, [activeIndex]);

return (
<Container as='section' padding='' className={'text-center'}>
<div className='relative flex flex-row items-center justify-center overflow-x-hidden md:gap-4'>
<div
className={`absolute left-0 top-1/2 z-10 mb-2 flex size-8 -translate-y-1/2 cursor-pointer
items-center justify-center rounded-full bg-primary-500 opacity-50 hover:bg-primary-600 md:opacity-100`}
onClick={goToPrevious}
>
<ArrowLeft className='w-4 text-white' />
</div>
<div className='relative flex w-5/6 flex-col items-center justify-center gap-2 pr-3'>
<div className='relative h-72 w-full overflow-hidden lg:h-[17rem] lg:w-[38rem]'>
{banners.map(
(banner, index) =>
banner.show && (
<Banner
key={index}
title={banner.title}
dateLocation={banner.dateLocation}
cfaText={banner.cfaText}
eventName={banner.eventName}
cfpDeadline={banner.cfpDeadline}
link={banner.link}
city={banner.city}
activeBanner={index === activeIndex % len}
className={className}
small={small}
/>
)
)}
</div>
<div className='m-auto flex justify-center'>
{banners.map((banner, index) => (
<div
key={index}
className={`mx-1 size-2 cursor-pointer rounded-full ${
activeIndex % len === index ? 'bg-primary-500' : 'bg-gray-300'
}`}
onClick={() => goToIndex(index)}
/>
))}
</div>
</div>
<div
className={`absolute right-0 top-1/2 z-10 mb-2 size-8 -translate-y-1/2 cursor-pointer
rounded-full bg-primary-500 opacity-50 hover:bg-primary-600 md:opacity-100`}
onClick={goToNext}
>
<ArrowRight className='text-white' />
</div>
</div>
</Container>
);
}
Loading

0 comments on commit ce3446d

Please sign in to comment.