Skip to content

Commit

Permalink
Hello section (#47)
Browse files Browse the repository at this point in the history
[Delivers #21]

Co-authored-by: Joslyn Manzi Karenzi <j.karenzi@alustudent.com>
  • Loading branch information
ambroisegithub and jkarenzi authored Jun 25, 2024
1 parent d464f0e commit cbd7020
Show file tree
Hide file tree
Showing 13 changed files with 5,541 additions and 3,031 deletions.
8 changes: 8 additions & 0 deletions .hintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": [
"development"
],
"hints": {
"no-inline-styles": "off"
}
}
8,200 changes: 5,189 additions & 3,011 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"@types/react-router-dom": "^5.3.3",
"axios": "^1.7.2",
"dotenv": "^16.4.5",
"hero-slider": "^3.2.1",
"jest": "^29.7.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.2.1",
Expand Down
18 changes: 0 additions & 18 deletions src/__test__/App.test.tsx

This file was deleted.

111 changes: 111 additions & 0 deletions src/__test__/Hellosection.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
render,
screen,
fireEvent,
waitFor,
act,
} from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import HelloSection from '@/components/HelloSection/HelloSection';

describe('HelloSection Component', () => {
it('renders the first slide initially', async () => {
render(
<MemoryRouter>
<HelloSection />
</MemoryRouter>
);

await waitFor(() => {
const images = screen.getAllByAltText((content, element) => {
return (
element !== null &&
element.tagName.toLowerCase() === 'img' &&
content.includes('Slide Images -')
);
});
expect(images[0]).toBeInTheDocument();
expect(
screen.getByText('Absolutely hot collections🔥')
).toBeInTheDocument();
});
});

it('navigates to the next slide on button click', async () => {
render(
<MemoryRouter>
<HelloSection />
</MemoryRouter>
);

const nextButton = screen.getByRole('button', { name: /next slide/i });
fireEvent.click(nextButton);

await waitFor(() => {
expect(screen.getByText('Exclusive Summer Sale☀️')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /previous slide/i })
).toBeEnabled();
});
});

it('navigates to the previous slide on button click', async () => {
render(
<MemoryRouter>
<HelloSection />
</MemoryRouter>
);

const nextButton = screen.getByRole('button', { name: /next slide/i });
fireEvent.click(nextButton);
fireEvent.click(nextButton);

const prevButton = screen.getByRole('button', { name: /previous slide/i });
fireEvent.click(prevButton);

await waitFor(() => {
expect(
screen.getByText('Absolutely hot collections🔥')
).toBeInTheDocument();
});
});

it('updates the active slide indicator', async () => {
render(
<MemoryRouter>
<HelloSection />
</MemoryRouter>
);

await waitFor(() => {
expect(screen.getByTestId('active-indicator-0')).toHaveClass(
'bg-[#6D31ED]'
);

const nextButton = screen.getByRole('button', { name: /next slide/i });
fireEvent.click(nextButton);

expect(screen.getByTestId('active-indicator-1')).toHaveClass(
'bg-[#6D31ED]'
);
});
});

it('automatically changes slides after interval', async () => {
render(
<MemoryRouter>
<HelloSection />
</MemoryRouter>
);

await act(async () => {
// eslint-disable-next-line no-promise-executor-return
await new Promise((resolve) => setTimeout(resolve, 6000));
});

await waitFor(() => {
expect(screen.getByText('Exclusive Summer Sale☀️')).toBeInTheDocument();
});
}, 10000);
});
Binary file added src/assets/welcome.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/welcome1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/welcome2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
164 changes: 164 additions & 0 deletions src/components/HelloSection/HelloSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/* eslint-disable react/no-array-index-key */
import { useState, useEffect } from 'react';
import { SlArrowLeft, SlArrowRight } from 'react-icons/sl';
import HSButton from '@/components/form/HSButton';
import HelloImage from '@/assets/welcome.jpg';
import HelloImage1 from '@/assets/welcome1.png';
import HelloImage2 from '@/assets/welcome2.png';

interface SlideProps {
title: string;
mainText: string[];
buttonText: string;
img: string;
}

export const slides: SlideProps[] = [
{
title: 'Absolutely hot collections🔥',
mainText: ['The Best Place To', 'Find And Buyer', 'Amazing Product'],
buttonText: 'Shop now!',
img: HelloImage,
},
{
title: 'Exclusive Summer Sale☀️',
mainText: ['Unbeatable Deals', 'On All Your', 'Favorite Items'],
buttonText: 'Discover Now!',
img: HelloImage1,
},
{
title: 'New Arrivals✨',
mainText: ['Fresh Styles', 'Just Landed', 'Shop Today'],
buttonText: 'Explore New!',
img: HelloImage2,
},
];

function Slide({ slide, isActive }: { slide: SlideProps; isActive: boolean }) {
return (
<div
className={`w-full h-auto flex flex-col p-2 gap-10 bg-white lg:flex-row lg:p-5 md:p-5 md:flex-row ${isActive ? 'animate-slide-up' : ''}`}
>
<div className="w-full pl-0 pt-1 md:w-1/2 flex h-auto flex-col flex-1 lg:pl-10 md:pl-0 lg:pt-10 md:pt-10">
<h2 className="flex justify-center text-[#171A1F] text-[18px] mb-4 lg:justify-start items-start">
{slide.title}
</h2>
<div>
{slide.mainText.map((text, textIndex) => (
<p
key={`mainText-${slide.title}-${text}-${textIndex}`}
className="text-[28px] flex justify-center items-center md:text-[40px] font-extrabold lg:justify-start lg:items-start"
>
{textIndex === 2 ? (
<>
<span className="mr-2">{text.split(' ')[0]}</span>
<span className="text-[#15ABFF]">{text.split(' ')[1]}</span>
</>
) : (
text
)}
</p>
))}
</div>
<div className="flex justify-center lg:justify-start">
<HSButton
title={slide.buttonText}
styles="hidden bg-[#6D31ED] w-auto text-white p-3 outline-none rounded-md mt-4 lg:flex md:flex"
/>
</div>
</div>
<div className="lg:w-1/2 md:w-1/2 h-auto flex-1 lg:flex md:flex ">
<img
src={slide.img}
alt={`Slide Images - ${slide.title}`}
className="h-full"
/>
</div>
</div>
);
}

function HelloSection() {
const [currentIndex, setCurrentIndex] = useState(0);

useEffect(() => {
const interval = setInterval(() => {
setCurrentIndex((prevIndex) =>
prevIndex === slides.length - 1 ? 0 : prevIndex + 1
);
}, 5000);

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

const handlePrev = () => {
setCurrentIndex((prevIndex) =>
prevIndex === 0 ? slides.length - 1 : prevIndex - 1
);
};

const handleNext = () => {
setCurrentIndex((prevIndex) =>
prevIndex === slides.length - 1 ? 0 : prevIndex + 1
);
};

const handleDotClick = (index: number) => {
setCurrentIndex(index);
};

return (
<div className="relative w-full overflow-hidden">
<div
className="flex transition-transform duration-300 ease-in-out"
style={{ transform: `translateX(-${currentIndex * 100}%)` }}
>
{slides.map((slide, index) => (
<div key={`slide-${slide.title}-${index}`} className="min-w-full">
<Slide slide={slide} isActive={index === currentIndex} />
</div>
))}
</div>

<div className="flex justify-center lg:justify-start">
<HSButton
title={slides[currentIndex].buttonText}
styles="flex bg-[#6D31ED] w-auto text-white outline-none rounded-md lg:hidden md:hidden"
/>
</div>

<button
onClick={handlePrev}
type="button"
className="absolute top-1/2 left-[-19px] transform -translate-y-1/2 p-4 outline-none font-bold text-[14px] lg:left-[-10px] md:left-[-10px] lg:font-bold lg:text-[20px] md:font-bold md:text-[20px]"
title="Previous Slide"
aria-label="Previous Slide"
>
<SlArrowLeft />
</button>
<button
onClick={handleNext}
type="button"
className="absolute top-1/2 right-[-19px] transform -translate-y-1/2 p-4 outline-none font-bold text-[14px] lg:right-[-10px] md:right-[-10px] lg:font-bold lg:text-[20px] md:font-bold md:text-[20px]"
title="Next Slide"
aria-label="Next Slide"
>
<SlArrowRight />
</button>
<div className="relative bottom-[0] left-1/2 transform -translate-x-1/2 space-x-2 p-5 flex flex-row justify-center items-center">
{slides.map((_, index) => (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div
key={`indicator-${index}`}
className={`w-2 h-2 rounded-full cursor-pointer ${currentIndex === index ? 'bg-[#6D31ED]' : 'bg-gray-300'}`}
data-testid={`active-indicator-${index}`}
aria-label={`Slide indicator ${index + 1}`}
onClick={() => handleDotClick(index)}
/>
))}
</div>
</div>
);
}

export default HelloSection;
45 changes: 45 additions & 0 deletions src/components/form/HSButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Link } from 'react-router-dom';

interface MyButtonProps {
path?: string;
title: string;
styles?: string;
click?: () => void;
icon?: JSX.Element;
target?: '_blank' | '_self' | '_parent' | '_top';
onChange?: React.ChangeEventHandler<HTMLAnchorElement>;
}

function HSButton({
path = '',
click,
title,
icon,
styles,
target,
onChange,
}: MyButtonProps) {
return (
<Link
target={target}
onChange={onChange}
rel="noopener noreferrer"
to={path}
onClick={click}
className={`${styles} bg-primary text-white px-6 py-3 rounded-md flex justify-center items-center gap-2 text-sm hover:text-gray-200 hover:shadow-lg hover:scale-105 transition-all duration-300 ease-in-out`}
>
{title} {icon}
</Link>
);
}

HSButton.defaultProps = {
path: '',
styles: '',
click: undefined,
icon: null,
target: '_self',
onChange: undefined,
};

export default HSButton;
11 changes: 10 additions & 1 deletion src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import HelloSection from '../components/HelloSection/HelloSection';

function Home() {
return <p className="text-blueBg">Welcome to Dynamites E-commerce</p>;
return (
<main className=" relative w-full h-auto p-2 bg-violeteBg lg:p-10 md:p-10">
<div>
<HelloSection />
</div>
{/* Add more componets as you wish!!! */}
</main>
);
}

export default Home;
11 changes: 11 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// tailwind.config.js

export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
Expand Down Expand Up @@ -25,6 +27,15 @@ export default {
fontFamily: {
Lexend: ['Lexend'],
},
animation: {
'slide-up': 'slideUp 0.5s ease-out',
},
keyframes: {
slideUp: {
'0%': { transform: 'translateY(20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
},
},
},
plugins: [],
Expand Down
Loading

0 comments on commit cbd7020

Please sign in to comment.