Files
tm-landing/app/page.tsx
T
AmirReza Jamali cd471695b7 feat: build initial landing page
This commit introduces the first version of the Opp lens landing page, replacing the default Next.js starter content.

The new page is designed to attract SMEs by showcasing the AI-powered tender management platform. It includes several key sections:
- A hero section with a clear value proposition.
- Sections detailing the problem, how the platform works, its features, and its benefits.
- A call-to-action form for users to sign up for early access.
- A comprehensive footer with contact and social media links.

Key changes include:
- **`app/page.tsx`**: Completely rewritten to build the landing page structure and content.
- **`app/globals.css`**: Updated with a new dark theme, custom brand colors, and a gradient text utility class for headings.
- **Dependencies**: Added `axios` to handle the early access form submission.
- **SEO**: Implemented detailed metadata for improved search engine visibility.
2025-10-14 16:23:00 +03:30

265 lines
7.3 KiB
TypeScript

import { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
export const metadata: Metadata = {
title: "Opp lens - AI-Powered Tender Management for SMEs | Win More Tenders",
description:
"Automated tender management platform for SMEs. Find tailored tenders, automate document handling, and get real-time alerts. Win more opportunities with less effort.",
keywords:
"tender management, AI tender search, SME tenders, public procurement, automated bidding, tender alerts",
authors: [{ name: "PBL Partners AB" }],
openGraph: {
title: "Opp lens - AI-Powered Tender Management",
description: "Win more tenders automatically with our AI-powered platform",
type: "website",
locale: "en_US",
siteName: "Opp lens",
},
icons: {
icon: "/fav-icon.svg",
},
};
export default function Home() {
const centerFrameItems = [
{
header: 200,
text: "Users",
description: `Lacking the time, dedicated staff, and
expertise to constantly monitor the market
and prepare high-quality bids.`,
},
{
header: 520,
text: "Contracts",
description: `Navigating complex requirements and
processes designed for large corporations
with dedicated tender departments.`,
},
{
header: 1500,
text: "Tenders",
description: `Did you know 68% of SMEs in Sweden
rarely or never win public tenders? It's time
to change that.`,
},
];
const contactInfo = [
{
icon: "/Message.svg",
contact: "info@opplens.com",
href: "mailto:info@opplens.com",
ariaLabel: "Email us at info@opplens.com",
},
{
icon: "/Phone.svg",
contact: "(+46) - 761581526",
href: "tel:+46761581526",
ariaLabel: "Call us at +46 761581526",
},
{
icon: "/Location.svg",
contact: "Stockholm, Sweden",
ariaLabel: "Our location in Stockholm, Sweden",
},
];
return (
<div className="container px-4 py-4 m-auto">
<header className="flex justify-between items-center border-b md:border-none border-gray-300 pb-4">
<Link
href="/"
aria-label="Opp lens home">
<Image
src="/main-logo.svg"
alt="Opp lens logo"
width={180}
height={30}
priority
/>
</Link>
<div className="flex gap-5 items-center">
<nav
className="hidden md:flex"
aria-label="Main navigation">
<HeaderButtons />
</nav>
<label
htmlFor="language"
className="sr-only">
Select language
</label>
<select
name="language"
id="language"
aria-label="Language selector">
<option value="en">English</option>
</select>
</div>
</header>
<nav
className="block md:hidden"
aria-label="Mobile navigation">
<HeaderButtons />
</nav>
<main className="mt-8 flex flex-col justify-center items-center gap-14">
<section
className="flex"
aria-labelledby="hero-heading">
<div className="flex flex-col">
<h1
className="gradient-text md:!text-left md:!text-8xl md:mb-10"
id="hero-heading">
AI-Powered
</h1>
<Image
className="block md:hidden"
src={"/images/mobile.png"}
alt="Opp lens platform interface on mobile device"
width={520}
height={280}
priority
/>
<div className="w-full flex flex-col gap-6 text-xl">
<h2 className="text-(--primary) font-bold text-5xl md:!mb-10">
Tender Management
</h2>
<h3 className="font-bold">Win more tenders, automatically!</h3>
<p className="text-(--gray-primary)">
Our platform finds tenders tailored to your business, automates
document handling, and sends real-time alerts. Win more
opportunities with less effort and zero manual search.
</p>
</div>
</div>
<Image
className="hidden md:block"
src={"/images/mobile.png"}
alt="Opp lens platform interface on desktop"
width={550}
height={480}
priority
/>
</section>
<section
className="flex flex-col gap-4 text-center md:flex-row"
aria-labelledby="statistics-heading">
<h2
id="statistics-heading"
className="sr-only">
Platform Statistics
</h2>
{centerFrameItems.map((item, index) => (
<CenterFrame
key={index}
header={item.header}
text={item.text}
description={item.description}
/>
))}
</section>
</main>
<footer className="mt-14">
<div className="flex justify-center items-center border-t pt-5 border-gray-300">
<Image
src="/main-logo.svg"
alt="Opp lens logo"
width={180}
height={30}
/>
</div>
<address className="md:flex md:justify-evenly w-full mt-14 px-5 not-italic">
{contactInfo.map((item) => (
<ContactInfo
key={item.contact}
contact={item.contact}
href={item.href}
icon={item.icon}
ariaLabel={item.ariaLabel}
/>
))}
</address>
<div className="flex justify-center items-center">
<p className="text-center text-(--gray-primary) mt-5 leading-10">
Stockholm, Sweden © 2025 Opplens.com by PBL Partners AB. All Rights
Reserved. Democratizing Tender Access for SMEs.
</p>
</div>
</footer>
</div>
);
}
function HeaderButtons() {
return (
<div className="flex justify-between items-center gap-5 mt-4 md:mt-0">
<Link
href={"/contact-us"}
className="text-(--primary)">
Contact us
</Link>
<Link
href={"/login"}
className="bg-(--primary) text-white px-12 py-3 rounded-full">
Login
</Link>
</div>
);
}
function CenterFrame({
header,
text,
description,
}: {
header: number;
text: string;
description: string;
}) {
return (
<article className="flex flex-col justify-center items-center gap-4">
<p
className="gradient-text"
aria-label={`${header} ${text}`}>
<strong>{header}</strong>
</p>
<h3 className="text-2xl font-semibold">{text}</h3>
<p className="text-(--gray-primary)">{description}</p>
</article>
);
}
function ContactInfo({
icon,
contact,
href,
ariaLabel,
}: {
icon: string;
contact: string;
href?: string;
ariaLabel: string;
}) {
return (
<div className="flex gap-3 my-3">
<Image
src={icon}
width={24}
height={24}
alt=""
aria-hidden="true"
/>
{href ? (
<a
href={href}
className="cursor-pointer"
aria-label={ariaLabel}>
{contact}
</a>
) : (
<p>{contact}</p>
)}
</div>
);
}