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: 1 addition & 0 deletions frontend/public/Logo_Desktop_purple.svg
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 frontend/public/slide4.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 frontend/public/spring.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 frontend/public/static1.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 frontend/public/static4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 127 additions & 0 deletions frontend/src/app/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// components/Carousel.tsx
"use client";
import Image from "next/image";
import { useEffect, useState } from "react";

interface CarouselProps {
slides: string[];
autoPlay?: boolean;
autoPlayInterval?: number;
}

export default function Carousel({
slides,
autoPlay = true,
autoPlayInterval = 3000,
}: CarouselProps) {
const [currentIndex, setCurrentIndex] = useState<number>(0);
const [imageDimensions, setImageDimensions] = useState<
{ width: number; height: number }[]
>([]);
const [containerHeight, setContainerHeight] = useState<number>(400);

// Kunin ang dimensions ng mga images
useEffect(() => {
const loadImageDimensions = async () => {
const dimensions = await Promise.all(
slides.map((src) => {
return new Promise<{ width: number; height: number }>((resolve) => {
const img = document.createElement("img");
img.onload = () => {
resolve({ width: img.width, height: img.height });
};
img.src = src;
});
}),
);
setImageDimensions(dimensions);
};
loadImageDimensions();
}, [slides]);

// I-adjust ang height base sa kasalukuyang image
useEffect(() => {
if (imageDimensions[currentIndex]) {
const { width, height } = imageDimensions[currentIndex];
// Compute aspect ratio at i-set ang height
const containerWidth = window.innerWidth;
const calculatedHeight =
(height / width) * Math.min(containerWidth, 1200);
setContainerHeight(calculatedHeight);
}
}, [currentIndex, imageDimensions]);

// Handle window resize
useEffect(() => {
const handleResize = () => {
if (imageDimensions[currentIndex]) {
const { width, height } = imageDimensions[currentIndex];
const containerWidth = window.innerWidth;
const calculatedHeight =
(height / width) * Math.min(containerWidth, 1200);
setContainerHeight(calculatedHeight);
}
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [currentIndex, imageDimensions]);

useEffect(() => {
if (!autoPlay) return;
const interval = setInterval(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % slides.length);
}, autoPlayInterval);
return () => clearInterval(interval);
}, [autoPlay, autoPlayInterval, slides.length]);

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

return (
<div className="relative w-full overflow-hidden rounded-lg">
{/* Dynamic height container */}
<div
className="relative w-full transition-all duration-300"
style={{ height: `${containerHeight}px` }}
>
{slides.map((slide: string, index: number) => (
<div
key={index}
className={`absolute inset-0 w-full h-full transition-opacity duration-500 ${
index === currentIndex ? "opacity-100" : "opacity-0"
}`}
>
<Image
src={slide}
alt={`Slide ${index + 1}`}
fill
className="object-cover"
priority={index === 0}
sizes="100vw"
/>
</div>
))}
</div>

{/* Dots */}
<div className="absolute bottom-2 sm:bottom-3 md:bottom-4 left-0 right-0 flex justify-center gap-1.5 sm:gap-2 md:gap-2.5 px-2">
{slides.map((_: string, index: number) => (
<button
key={index}
onClick={() => goToSlide(index)}
className={`rounded-full transition-all duration-300 ease-in-out
${
index === currentIndex
? "bg-blue-500 w-1.5 sm:w-2 md:w-2.5 h-1.5 sm:h-2 md:h-2.5"
: "bg-white/50 w-1.5 sm:w-2 md:w-2.5 h-1.5 sm:h-2 md:h-2.5"
}
hover:cursor-pointer
`}
aria-label={`Go to slide ${index + 1}`}
/>
))}
</div>
</div>
);
}
231 changes: 137 additions & 94 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,103 +1,146 @@
import Image from "next/image";
"use client";
import Carousel from "@/app/Carousel";

import { useState } from "react";

export default function Home() {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
src/app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<>
{/* Navbar */}
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
<nav className="w-full flex items-center justify-between px-8 py-4 bg-white">
{/* Logo */}
<div className="flex items-center gap-1">
<span className="text-purple-600 text-2xl font-bold tracking-tight">
moola
</span>
</div>

<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
{/* Desktop Nav Links */}
<div className="hidden md:flex items-center gap-8 text-sm font-medium text-gray-700 font-semibold">
<a href="#" className="hover:text-purple-600 transition-colors">
Shop
</a>
<a href="#" className="hover:text-purple-600 transition-colors">
Winter Holiday Sale
</a>
<a href="#" className="hover:text-purple-600 transition-colors">
How it Works
</a>
<a href="#" className="hover:text-purple-600 transition-colors">
Refer &amp; Earn
</a>
<a href="#" className="hover:text-purple-600 transition-colors">
Merchant Solutions
</a>
</div>

{/* Right Side */}
<div className="flex items-center gap-4">
{/* Desktop Download Button */}
<button className="hidden md:block bg-purple-600 hover:bg-purple-700 text-white text-sm px-3 py-2 rounded-full transition-colors">
DOWNLOAD NOW
</button>

{/* Hamburger Button */}
<button
className="md:hidden flex flex-col justify-center items-center w-8 h-8 gap-1.5"
onClick={() => setIsOpen(!isOpen)}
aria-label="Toggle menu"
>
<span
className={`block w-6 h-0.5 bg-purple-600 transition-all duration-300 ${isOpen ? "rotate-45 translate-y-2" : ""}`}
/>
<span
className={`block w-6 h-0.5 bg-purple-600 transition-all duration-300 ${isOpen ? "opacity-0" : ""}`}
/>
<span
className={`block w-6 h-0.5 bg-purple-600 transition-all duration-300 ${isOpen ? "-rotate-45 -translate-y-2" : ""}`}
/>
</button>
</div>
</nav>

{/* Mobile Dropdown */}
<div
className={`md:hidden overflow-hidden transition-all duration-300 ${isOpen ? "max-h-96 opacity-100" : "max-h-0 opacity-0"}`}
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
<div className="flex flex-col bg-white px-8 pb-4 gap-4 text-sm font-medium text-gray-700">
<a
href="#"
className="hover:text-purple-600 transition-colors py-2 border-b border-gray-100"
>
Shop
</a>
<a
href="#"
className="hover:text-purple-600 transition-colors py-2 border-b border-gray-100"
>
Winter Holiday Sale
</a>
<a
href="#"
className="hover:text-purple-600 transition-colors py-2 border-b border-gray-100"
>
How it Works
</a>
<a
href="#"
className="hover:text-purple-600 transition-colors py-2 border-b border-gray-100"
>
Refer &amp; Earn
</a>
<a
href="#"
className="hover:text-purple-600 transition-colors py-2 border-b border-gray-100"
>
Merchant Solutions
</a>
<button className="mt-2 bg-purple-600 hover:bg-purple-700 text-white text-sm font-semibold px-5 py-2 rounded-full transition-colors w-full">
DOWNLOAD NOW
</button>
</div>
</div>
</div>

{/* Search Bar */}
<div className="w-full bg-purple-600 px-8 py-4">
<div className="flex items-center bg-white rounded-full overflow-hidden max-w-4xl mx-auto">
<input
type="text"
placeholder="Search over 250 gift card brands"
className="flex-1 px-6 py-3 text-sm text-gray-700 outline-none bg-transparent"
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
<button className="bg-purple-800 hover:bg-purple-900 px-5 py-3 transition-colors">
<svg
xmlns="http://www.w3.org/2000/svg"
className="w-5 h-5 text-white"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"
/>
</svg>
</button>
</div>
</div>

{/* Carousel */}
<div className="container mx-auto px-4 sm:px-6 lg:px-8 mt-8">
<div className="rounded-lg overflow-hidden bg-white shadow-lg">
<Carousel
slides={["/slide4.png", "/spring.png"]}
autoPlay={true}
autoPlayInterval={3000}
/>
Go to nextjs.org →
</a>
</footer>
</div>
</div>
</div>
</>
);
}