Bind cms page

This commit is contained in:
AmirReza Jamali
2025-11-15 09:43:45 +03:30
parent efbdb82535
commit 4839c325a5
5 changed files with 593 additions and 448 deletions
+1
View File
@@ -0,0 +1 @@
{}
+28
View File
@@ -0,0 +1,28 @@
"use client";
import Image, { ImageProps } from "next/image";
import { useState } from "react";
interface ImageWithFallbackProps extends Omit<ImageProps, "src" | "alt"> {
src: string;
alt: string;
fallbackSrc?: string;
}
export default function ImageWithFallback({
src,
alt,
fallbackSrc = "/clipboard.svg",
...props
}: ImageWithFallbackProps) {
const [imgSrc, setImgSrc] = useState(src);
return (
<Image
{...props}
src={imgSrc}
alt={alt}
onError={() => setImgSrc(fallbackSrc)}
/>
);
}
+148 -33
View File
@@ -1,8 +1,11 @@
import ApexChart from "@/app/_components/ApexChart"; import ApexChart from "@/app/_components/ApexChart";
import FooterForm from "@/app/_components/FooterForm";
import ImageWithFallback from "@/app/_components/ImageWithFallback";
import { cmsService } from "@/hooks/useCmsQueries"; import { cmsService } from "@/hooks/useCmsQueries";
import { CmsResponse } from "@/types/TCms"; import { CmsResponse } from "@/types/TCms";
import { Metadata } from "next"; import { Metadata } from "next";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Opp lens - Marketing", title: "Opp lens - Marketing",
@@ -63,6 +66,20 @@ export default async function Home({ params }: IProps) {
return ( return (
<> <>
<header className="flex justify-center pt-8 lg:justify-between items-center pb-4 container px-4 py-4 m-auto">
<Link
href="/"
aria-label="Opp lens home">
<Image
src="/main-logo.svg"
alt="Opp lens logo"
width={180}
height={30}
priority
/>
</Link>
</header>
<main className="container px-4 py-4 m-auto flex-grow">
<section className="block lg:flex justify-around items-center"> <section className="block lg:flex justify-around items-center">
<div className="flex gap-4 w-full lg:w-1/2 font-bold text-4xl text-center flex-col bg-gray-400/10 p-10 rounded-[40px] h-fit mb-16 backdrop-blur-2xl"> <div className="flex gap-4 w-full lg:w-1/2 font-bold text-4xl text-center flex-col bg-gray-400/10 p-10 rounded-[40px] h-fit mb-16 backdrop-blur-2xl">
<div className="flex flex-col gap-8"> <div className="flex flex-col gap-8">
@@ -97,19 +114,18 @@ export default async function Home({ params }: IProps) {
<section className="text-left flex flex-col gap-6 bg-gradient-to-b from-[#011132] to-[#012B80] text-white rounded-[56px] relative p-6 lg:p-14 -mt-16 z-20 lg:ml-28 "> <section className="text-left flex flex-col gap-6 bg-gradient-to-b from-[#011132] to-[#012B80] text-white rounded-[56px] relative p-6 lg:p-14 -mt-16 z-20 lg:ml-28 ">
<hgroup> <hgroup>
<h1 className="capitalize font-extrabold text-4xl mb-6 md:text-5xl"> <h1 className="capitalize font-extrabold text-4xl mb-6 md:text-5xl">
democratizing the world of tenders {cmsData?.data.hero.Title}
</h1> </h1>
<p className="text-[16px] text-[#dadada]"> <p className="text-[16px] text-[#dadada]">
Opplens is your dedicated AI-powered partner, leveling the playing {cmsData?.data.hero.Description}
field so you can win the public and private contracts you deserve.
<br />
Compete on your true strengths.
</p> </p>
</hgroup> </hgroup>
<div className="flex justify-end"> <div className="flex justify-end">
<button className="w-full lg:w-fit capitalize bg-(--primary) rounded-4xl py-4 px-6"> <Link
Get early access className="w-full lg:w-fit capitalize bg-(--primary) rounded-4xl py-4 px-6"
</button> href={cmsData?.data.hero.ButtonLink ?? "#"}>
{cmsData?.data.hero.ButtonText}
</Link>
</div> </div>
</section> </section>
<article className="mt-40"> <article className="mt-40">
@@ -168,7 +184,7 @@ export default async function Home({ params }: IProps) {
</li> </li>
<li className="flex flex-col gap-4 lg:w-80"> <li className="flex flex-col gap-4 lg:w-80">
<div className="p-4"> <div className="p-4">
<Image <ImageWithFallback
src={"/clipboard.svg"} src={"/clipboard.svg"}
alt="clipboard icon" alt="clipboard icon"
width={36} width={36}
@@ -183,7 +199,7 @@ export default async function Home({ params }: IProps) {
</li> </li>
<li className="flex flex-col gap-4 lg:w-80"> <li className="flex flex-col gap-4 lg:w-80">
<div className="p-4"> <div className="p-4">
<Image <ImageWithFallback
src={"/arrow-up.svg"} src={"/arrow-up.svg"}
alt="arrow-up icon" alt="arrow-up icon"
width={36} width={36}
@@ -201,44 +217,143 @@ export default async function Home({ params }: IProps) {
<section className="flex flex-col mt-36 gap-10 px-2 lg:w-3/4 m-auto"> <section className="flex flex-col mt-36 gap-10 px-2 lg:w-3/4 m-auto">
<div> <div>
<h2 className="font-bold text-2xl lg:text-4xl"> <h2 className="font-bold text-2xl lg:text-4xl">
The Opplens Advantage {cmsData?.data.advantages.Title}
</h2> </h2>
</div> </div>
<section className="flex flex-col gap-10 lg:flex-row"> <section className="flex flex-col gap-10 lg:flex-row">
<div className="flex flex-col gap-7 bg-[#FFF7EB] p-10 rounded-3xl lg:pb-20"> {cmsData?.data.advantages.Cards.map((card) => (
<Image <div
src={"/people.svg"} className="flex flex-col gap-7 bg-[#FFF7EB] p-10 rounded-3xl lg:pb-20"
key={card.Title}>
<ImageWithFallback
src={card.Icon ?? "/people.svg"}
width={48} width={48}
height={48} height={48}
alt="people icon" alt="people icon"
unoptimized
/> />
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<h2 className="font-bold text-xl">A True Partnership Model</h2> <h2 className="font-bold text-xl">{card.Title}</h2>
<p> <p>{card.Description}</p>
Lower your risk with our performance-based options. Our success
is directly tied to yours, making us a dedicated partner
invested in your growth, not just a software provider.
</p>
</div> </div>
</div> </div>
<div className="flex flex-col gap-7 bg-[#E5EFFF] p-10 rounded-3xl lg:pb-20"> ))}
</section>
</section>
</main>
<footer className="relative h-full bg-[url('/footer-bg.svg')] bg-cover bg-no-repeat mt-96">
<div
className="absolute w-full -top-[355px] lg:-top-72 h-[600px] z-[-10] bg-[url('/bottom-bg.svg')] bg-contain bg-no-repeat bg-right"
aria-hidden="true"
/>
<FooterForm />
<div className="relative z-10 container px-4 py-4 m-auto flex flex-col justify-center items-center mt-80 ">
<div className="flex justify-between flex-col lg:flex-row gap-14 w-full lg:w-1/2">
<div className="flex flex-col gap-10">
<Image <Image
src={"/shield-tick.svg"} src={"/footer-logo.svg"}
width={48} alt="Opp lens logo"
height={48} width={180}
alt="shield icon" height={30}
priority
/> />
<div className="flex flex-col gap-4"> <div>
<h2 className="font-bold text-xl">Unmatched Data Security</h2> <p className="text-white my-2">
<p> {cmsData?.data.footer.Tagline}
Your business data is critical. Our unique on premise AI engine
ensures your information remains 100% private and secure, unlike
cloud based services. Fully GDPR compliant by design.
</p> </p>
<small className="text-white my-2">
{cmsData?.data.footer.Copyright}
</small>
</div> </div>
</div> </div>
</section> <address className="not-italic">
</section> <ul>
<li className="flex gap-3 my-3 text-white">
<Image
src={"/Phone.svg"}
width={24}
height={24}
alt=""
aria-hidden="true"
/>
<a href={`tel:${cmsData?.data.footer.Phone}`}>
{cmsData?.data.footer.Phone}
</a>
</li>
<li className="flex gap-3 my-3 text-white">
<Image
src={"/Message.svg"}
width={24}
height={24}
alt=""
aria-hidden="true"
/>
<a href={`mailto:${cmsData?.data.footer.Email}`}>
{cmsData?.data.footer.Email}
</a>
</li>
<li className="flex gap-3 my-3 text-white">
<Image
src={"/Location.svg"}
width={24}
height={24}
alt=""
aria-hidden="true"
/>
<p>{cmsData?.data.footer.Location}</p>
</li>
</ul>
</address>
</div>
</div>
</footer>
</> </>
); );
} }
function ContactInfo({
icon,
contact,
href,
ariaLabel,
contactIconSrc,
}: {
icon: string;
contact: string;
href?: string;
ariaLabel: string;
contactIconSrc?: string;
}) {
return (
<li className="flex gap-3 my-3 text-white">
<Image
src={icon}
width={24}
height={24}
alt=""
aria-hidden="true"
/>
{href ? (
<a
href={href}
className="cursor-pointer min-w-fit"
aria-label={ariaLabel}>
{contact}
</a>
) : (
<p className="flex gap-3">
{!!contactIconSrc && (
<Image
src={contactIconSrc}
width={20}
height={20}
alt="flag"
/>
)}
{contact}
</p>
)}
</li>
);
}
+1 -129
View File
@@ -1,9 +1,6 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google"; import { Geist, Geist_Mono } from "next/font/google";
import Image from "next/image";
import Link from "next/link";
import { ToastContainer, Zoom } from "react-toastify"; import { ToastContainer, Zoom } from "react-toastify";
import FooterForm from "../_components/FooterForm";
import "../globals.css"; import "../globals.css";
const geistSans = Geist({ const geistSans = Geist({
variable: "--font-geist-sans", variable: "--font-geist-sans",
@@ -24,96 +21,15 @@ export const metadata: Metadata = {
}, },
}; };
function ContactInfo({
icon,
contact,
href,
ariaLabel,
contactIconSrc,
}: {
icon: string;
contact: string;
href?: string;
ariaLabel: string;
contactIconSrc?: string;
}) {
return (
<li className="flex gap-3 my-3 text-white">
<Image
src={icon}
width={24}
height={24}
alt=""
aria-hidden="true"
/>
{href ? (
<a
href={href}
className="cursor-pointer min-w-fit"
aria-label={ariaLabel}>
{contact}
</a>
) : (
<p className="flex gap-3">
{!!contactIconSrc && (
<Image
src={contactIconSrc}
width={20}
height={20}
alt="flag"
/>
)}
{contact}
</p>
)}
</li>
);
}
export default async function RootLayout({ export default async function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
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",
contactIconSrc: "/sweden.svg",
},
];
return ( return (
<html lang="en"> <html lang="en">
<body <body
className={`${geistSans.variable} ${geistMono.variable} antialiased flex flex-col min-h-screen bg-[url('/header-bg.svg')] bg-top-right bg-no-repeat bg-contain`}> className={`${geistSans.variable} ${geistMono.variable} antialiased flex flex-col min-h-screen bg-[url('/header-bg.svg')] bg-top-right bg-no-repeat bg-contain`}>
<header className="flex justify-center pt-8 lg:justify-between items-center pb-4 container px-4 py-4 m-auto">
<Link
href="/"
aria-label="Opp lens home">
<Image
src="/main-logo.svg"
alt="Opp lens logo"
width={180}
height={30}
priority
/>
</Link>
</header>
<ToastContainer <ToastContainer
position="top-right" position="top-right"
autoClose={2500} autoClose={2500}
@@ -127,51 +43,7 @@ export default async function RootLayout({
pauseOnHover pauseOnHover
/> />
<main className="container px-4 py-4 m-auto flex-grow">{children}</main> {children}
<footer className="relative h-full bg-[url('/footer-bg.svg')] bg-cover bg-no-repeat mt-96">
<div
className="absolute w-full -top-[355px] lg:-top-72 h-[600px] z-[-10] bg-[url('/bottom-bg.svg')] bg-contain bg-no-repeat bg-right"
aria-hidden="true"
/>
<FooterForm />
<div className="relative z-10 container px-4 py-4 m-auto flex flex-col justify-center items-center mt-80 ">
<div className="flex justify-between flex-col lg:flex-row gap-14 w-full lg:w-1/2">
<div className="flex flex-col gap-10">
<Image
src={"/footer-logo.svg"}
alt="Opp lens logo"
width={180}
height={30}
priority
/>
<div>
<p className="text-white my-2">
Democratizing Tender Access for SMEs.
</p>
<small className="text-white my-2">
© 2025 Opplens.com by PBL Partners AB. All Rights Reserved.
</small>
</div>
</div>
<address className="not-italic">
<ul>
{contactInfo.map((contact, index) => (
<ContactInfo
key={index}
icon={contact.icon}
contact={contact.contact}
href={contact.href}
ariaLabel={contact.ariaLabel}
contactIconSrc={contact.contactIconSrc}
/>
))}
</ul>
</address>
</div>
</div>
</footer>
</body> </body>
</html> </html>
); );
+137 -8
View File
@@ -1,5 +1,7 @@
import { Metadata } from "next"; import { Metadata } from "next";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link";
import FooterForm from "../_components/FooterForm";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Opp lens - Marketing", title: "Opp lens - Marketing",
description: description:
@@ -19,7 +21,73 @@ export const metadata: Metadata = {
}, },
}; };
function ContactInfo({
icon,
contact,
href,
ariaLabel,
contactIconSrc,
}: {
icon: string;
contact: string;
href?: string;
ariaLabel: string;
contactIconSrc?: string;
}) {
return (
<li className="flex gap-3 my-3 text-white">
<Image
src={icon}
width={24}
height={24}
alt=""
aria-hidden="true"
/>
{href ? (
<a
href={href}
className="cursor-pointer min-w-fit"
aria-label={ariaLabel}>
{contact}
</a>
) : (
<p className="flex gap-3">
{!!contactIconSrc && (
<Image
src={contactIconSrc}
width={20}
height={20}
alt="flag"
/>
)}
{contact}
</p>
)}
</li>
);
}
export default function Home() { export default function Home() {
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",
contactIconSrc: "/sweden.svg",
},
];
const centerFrames = [ const centerFrames = [
{ {
src: "/magnifier.svg", src: "/magnifier.svg",
@@ -45,6 +113,20 @@ export default function Home() {
]; ];
return ( return (
<> <>
<header className="flex justify-center pt-8 lg:justify-between items-center pb-4 container px-4 py-4 m-auto">
<Link
href="/"
aria-label="Opp lens home">
<Image
src="/main-logo.svg"
alt="Opp lens logo"
width={180}
height={30}
priority
/>
</Link>
</header>
<main className="container px-4 py-4 m-auto flex-grow">
<section className="flex flex-col justify-center lg:justify-between lg:flex-row items-center"> <section className="flex flex-col justify-center lg:justify-between lg:flex-row items-center">
<section className="text-left lg:w-1/2 flex flex-col gap-6 bg-gradient-to-b rounded-[56px] relative p-6 lg:p-14 z-20 lg:ml-28"> <section className="text-left lg:w-1/2 flex flex-col gap-6 bg-gradient-to-b rounded-[56px] relative p-6 lg:p-14 z-20 lg:ml-28">
<hgroup> <hgroup>
@@ -52,8 +134,9 @@ export default function Home() {
democratizing the world of tenders democratizing the world of tenders
</h1> </h1>
<p className="text-[16px]"> <p className="text-[16px]">
Opplens is your dedicated AI-powered partner, leveling the playing Opplens is your dedicated AI-powered partner, leveling the
field so you can win the public and private contracts you deserve. playing field so you can win the public and private contracts
you deserve.
<br /> <br />
Compete on your true strengths. Compete on your true strengths.
</p> </p>
@@ -186,9 +269,9 @@ export default function Home() {
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<h3 className="font-bold text-xl">A True Partnership Model</h3> <h3 className="font-bold text-xl">A True Partnership Model</h3>
<p> <p>
Lower your risk with our performance-based options. Our success Lower your risk with our performance-based options. Our
is directly tied to yours, making us a dedicated partner success is directly tied to yours, making us a dedicated
invested in your growth, not just a software provider. partner invested in your growth, not just a software provider.
</p> </p>
</div> </div>
</article> </article>
@@ -202,14 +285,60 @@ export default function Home() {
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<h3 className="font-bold text-xl">Unmatched Data Security</h3> <h3 className="font-bold text-xl">Unmatched Data Security</h3>
<p> <p>
Your business data is critical. Our unique on premise AI engine Your business data is critical. Our unique on premise AI
ensures your information remains 100% private and secure, unlike engine ensures your information remains 100% private and
cloud based services. Fully GDPR compliant by design. secure, unlike cloud based services. Fully GDPR compliant by
design.
</p> </p>
</div> </div>
</article> </article>
</div> </div>
</section> </section>
</main>
<footer className="relative h-full bg-[url('/footer-bg.svg')] bg-cover bg-no-repeat mt-96">
<div
className="absolute w-full -top-[355px] lg:-top-72 h-[600px] z-[-10] bg-[url('/bottom-bg.svg')] bg-contain bg-no-repeat bg-right"
aria-hidden="true"
/>
<FooterForm />
<div className="relative z-10 container px-4 py-4 m-auto flex flex-col justify-center items-center mt-80 ">
<div className="flex justify-between flex-col lg:flex-row gap-14 w-full lg:w-1/2">
<div className="flex flex-col gap-10">
<Image
src={"/footer-logo.svg"}
alt="Opp lens logo"
width={180}
height={30}
priority
/>
<div>
<p className="text-white my-2">
Democratizing Tender Access for SMEs.
</p>
<small className="text-white my-2">
© 2025 Opplens.com by PBL Partners AB. All Rights Reserved.
</small>
</div>
</div>
<address className="not-italic">
<ul>
{contactInfo.map((contact, index) => (
<ContactInfo
key={index}
icon={contact.icon}
contact={contact.contact}
href={contact.href}
ariaLabel={contact.ariaLabel}
contactIconSrc={contact.contactIconSrc}
/>
))}
</ul>
</address>
</div>
</div>
</footer>
</> </>
); );
} }