diff --git a/Dockerfile b/Dockerfile index f1afc53..5d74b1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN npm i COPY . . -RUN npm run build +RUN docker build . #--- diff --git a/app/_components/ApexChart.tsx b/app/_components/ApexChart.tsx new file mode 100644 index 0000000..9ae9333 --- /dev/null +++ b/app/_components/ApexChart.tsx @@ -0,0 +1,106 @@ +"use client"; + +import dynamic from "next/dynamic"; +import { useEffect, useState } from "react"; +import { ApexOptions } from "apexcharts"; +const ReactApexChart = dynamic(() => import("react-apexcharts"), { ssr: false }); + +interface ChartState { + series: { + name: string; + data: number[]; + }[]; + options: ApexOptions; +} + +const ApexChart = () => { + const [state, setState] = useState(null); + + useEffect(() => { + const initialState: ChartState = { + series: [ + { + name: "Sales", + data: [ + 182, 192, 230, 198, 243, 234, 244, 210, 274, 220, 225, 289, 265, + 304, + ], + }, + ], + options: { + chart: { + toolbar: { show: false }, + height: 350, + type: "line", + }, + forecastDataPoints: { + count: 0, + }, + stroke: { + width: 6, + curve: "smooth", + }, + fill: { + type: "gradient", + gradient: { + shade: "dark", + type: "horizontal", + shadeIntensity: 0.5, + gradientToColors: ["#99DDE5"], + inverseColors: false, + opacityFrom: 1, + opacityTo: 1, + stops: [0, 100], + }, + }, + colors: ["#0164FF"], + xaxis: { + type: "datetime", + categories: [ + "1/11/2024", + "2/11/2024", + "3/11/2024", + "5/11/2024", + "6/11/2024", + "7/11/2024", + "8/11/2024", + "9/11/2024", + "10/11/2024", + "11/11/2024", + "12/11/2024", + "1/11/2025", + "2/11/2025", + "3/11/2025", + ], + tickAmount: 0, + labels: { + formatter: function (value: any, timestamp: any, opts: any) { + return opts.dateFormatter(new Date(timestamp), "dd MMM"); + }, + }, + }, + }, + }; + setState(initialState); + }, []); + + if (!state) { + return
Loading...
; + } + + return ( +
+
+ +
+
+
+ ); +}; + +export default ApexChart; diff --git a/app/_components/InputGroup.tsx b/app/_components/InputGroup.tsx new file mode 100644 index 0000000..f210685 --- /dev/null +++ b/app/_components/InputGroup.tsx @@ -0,0 +1,64 @@ +"use client"; +import { HTMLInputTypeAttribute, useState } from "react"; +import { RegisterOptions, UseFormRegister } from "react-hook-form"; + +function InputGroup({ + id, + label, + register, + type, + validation, + error, +}: { + label: string; + id: string; + register: UseFormRegister; + type: HTMLInputTypeAttribute; + validation?: RegisterOptions; + error?: string; +}) { + const [isFocused, setIsFocused] = useState(false); + const [hasValue, setHasValue] = useState(false); + + const { onChange, ...rest } = register(id, validation); + + const handleChange = (e: React.ChangeEvent) => { + setHasValue(e.target.value !== ""); + onChange(e); + }; + + return ( +
+
+ setIsFocused(true)} + onBlur={() => setIsFocused(false)} + /> + +
+ {error &&

{error}

} +
+ ); +} + +export default InputGroup; diff --git a/app/contact-us/form.tsx b/app/contact-us/form.tsx index 4fe70a2..98a1b98 100644 --- a/app/contact-us/form.tsx +++ b/app/contact-us/form.tsx @@ -1,8 +1,8 @@ "use client"; import api from "@/service/api"; -import { HTMLInputTypeAttribute } from "react"; -import { RegisterOptions, useForm, UseFormRegister } from "react-hook-form"; +import { useForm } from "react-hook-form"; import { toast } from "react-toastify"; +import InputGroup from "../_components/InputGroup"; type TContactUsForm = { full_name: string; company_name: string; @@ -27,37 +27,29 @@ const ContactUsForm = () => { }; return (
{ }, }} /> - +
+ +
); }; export default ContactUsForm; -function InputGroup({ - icon, - id, - label, - placeholder, - register, - type, - validation, - error, -}: { - label: string; - id: string; - icon: () => React.JSX.Element; - placeholder: string; - register: UseFormRegister; - type: HTMLInputTypeAttribute; - validation?: RegisterOptions; - error?: string; -}) { - return ( - <> - -
- - - {icon()} - -
- {error &&

{error}

} - - ); -} -function UserIcon() { - return ( - - - - ); -} diff --git a/app/contact-us/page.tsx b/app/contact-us/page.tsx index 5bb4a81..c0834cd 100644 --- a/app/contact-us/page.tsx +++ b/app/contact-us/page.tsx @@ -2,25 +2,8 @@ import ContactUsForm from "./form"; const ContactUs = () => { return ( -
-
-
- Contact Us -
- -

Let's get in touch

-
-

- Or just reach out manually to -

- - info@opplens.com - -
-
-
+
+
diff --git a/app/layout.tsx b/app/layout.tsx index e12e3c4..0338475 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,7 +3,7 @@ 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 HeaderButtons from "./_components/HeaderButtons"; +import ContactUs from "./contact-us/page"; import "./globals.css"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -32,7 +32,7 @@ function ContactInfo({ ariaLabel: string; }) { return ( -
+
-
-
- - Opp lens logo - -
- - - -
-
- -
+ className={`${geistSans.variable} ${geistMono.variable} antialiased flex flex-col min-h-screen bg-[url('/main-bg.svg')] bg-cover bg-no-repeat`}> +
+ Shallow + +
+ + + +
{children}
+ +
+
+
- - {children} -
- Shallow -
-
-
- Opp lens logo -
-
- {contactInfo.map((item) => ( - +
+
+ Opp lens logo - ))} -
-
-

- Stockholm, Sweden © 2025 Opplens.com by PBL Partners AB. All - Rights Reserved. Democratizing Tender Access for SMEs. -

+

+ Democratizing Tender Access for SMEs. +

+

+ © 2025 Opplens.com by PBL Partners AB. All Rights Reserved. +

+
+
+ {contactInfo.map((contact, index) => ( + + ))} +
- -
+
+ ); diff --git a/app/page.tsx b/app/page.tsx index a91813f..ea3e953 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from "next"; import Image from "next/image"; - +import ApexChart from "./_components/ApexChart"; export const metadata: Metadata = { title: "Opp lens - AI-Powered Tender Management for SMEs | Win More Tenders", description: @@ -21,109 +21,208 @@ export const metadata: Metadata = { }; export default function Home() { - const centerFrameItems = [ + const centerFrames = [ { - header: 200, - text: "Users", - description: `Lacking the time, dedicated staff, and - expertise to constantly monitor the market - and prepare high-quality bids.`, + src: "/magnifier.svg", + title: "Discover with AI", + alt: "Magnifier", + description: + "Our intelligent platform scours thousands of sources to find the perfect, most relevant tender opportunities for your specific business and expertise", }, { - header: 520, - text: "Contracts", - description: `Navigating complex requirements and -processes designed for large corporations -with dedicated tender departments.`, + src: "/clipboard.svg", + title: "Prepare with Experts", + alt: "Clipboard", + description: + "Gain access to expert support and powerful tools to craft compelling, competitive, and high-quality bids that stand out from the competition.", }, { - 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.`, + src: "/thumbs-up.svg", + title: "Win with a Partner", + alt: "thumbs-up", + description: + "We operate on a partnership model. We only succeed when you win. This aligns our goals and makes us a true partner in your growth.", }, ]; - return ( -
-
-
-

- AI-Powered -

- Opp lens platform interface on mobile device -
-

- Tender Management -

-

Win more tenders, automatically!

-

- 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. -

-
+ <> +
+
+

+ $ + 12,345,678 +

+

total tender amount you missed today

+ +
+
+ phone
- Opp lens platform interface on desktop
-
-

- Platform Statistics -

- {centerFrameItems.map((item, index) => ( +
+

+ democratizing the world of tenders +

+

+ Opplens is your dedicated AI-powered partner, leveling the playing + field so you can win the public and private contracts you deserve. + Compete on your true strengths. +

+
+ +
+
+
+

+ your in-house tender department, powered by + ai +

+ + Opplens is the end-to-end solution that gives you the power to compete + and win. + +
+
+ {centerFrames.map((frame, index) => ( ))}
-
+
+
+

+ Tired of Complex Tenders & Lost Opportunities? +

+

+ The procurement market is tough for SMEs. We get it. You're facing + an uphill battle. +

+
+
+
+
+ clock icon +
+

Resource Drain

+

+ Lacking the time, dedicated staff, and expertise to constantly + monitor the market and prepare high-quality bids. +

+
+
+
+ clipboard icon +
+

Overwhelming Complexity

+

+ Navigating complex requirements and processes designed for large + corporations with dedicated tender departments. +

+
+
+
+ arrow-up icon +
+

Low Win Rates

+

+ Did you know 68% of SMEs in Sweden rarely or never win public + tenders? It's time to change that. +

+
+
+
+
+
+

The Opplens Advantage

+
+
+
+ people icon +
+

A True Partnership Model

+

+ 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. +

+
+
+
+ shield icon +
+

Unmatched Data Security

+

+ 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. +

+
+
+
+
+ ); } function CenterFrame({ - header, - text, + src, + title, description, + alt, }: { - header: number; - text: string; + src: string; + title: string; description: string; + alt: string; }) { return ( -
-

- {header} -

-

{text}

-

{description}

-
+
+ {alt} +

{title}

+

{description}

+
); } diff --git a/package-lock.json b/package-lock.json index e700c70..8f1d9f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "name": "tm-landing", "version": "0.1.0", "dependencies": { + "apexcharts": "^5.3.5", "axios": "^1.12.2", "next": "15.5.5", "react": "19.1.0", + "react-apexcharts": "^1.8.0", "react-dom": "19.1.0", "react-hook-form": "^7.65.0", "react-toastify": "^11.0.5" @@ -672,6 +674,62 @@ "node": ">= 10" } }, + "node_modules/@svgdotjs/svg.draggable.js": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz", + "integrity": "sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==", + "license": "MIT", + "peerDependencies": { + "@svgdotjs/svg.js": "^3.2.4" + } + }, + "node_modules/@svgdotjs/svg.filter.js": { + "version": "3.0.9", + "resolved": "https://registry.npmmirror.com/@svgdotjs/svg.filter.js/-/svg.filter.js-3.0.9.tgz", + "integrity": "sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==", + "license": "MIT", + "dependencies": { + "@svgdotjs/svg.js": "^3.2.4" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@svgdotjs/svg.js": { + "version": "3.2.5", + "resolved": "https://registry.npmmirror.com/@svgdotjs/svg.js/-/svg.js-3.2.5.tgz", + "integrity": "sha512-/VNHWYhNu+BS7ktbYoVGrCmsXDh+chFMaONMwGNdIBcFHrWqk2jY8fNyr3DLdtQUIalvkPfM554ZSFa3dm3nxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Fuzzyma" + } + }, + "node_modules/@svgdotjs/svg.resize.js": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@svgdotjs/svg.resize.js/-/svg.resize.js-2.0.5.tgz", + "integrity": "sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==", + "license": "MIT", + "engines": { + "node": ">= 14.18" + }, + "peerDependencies": { + "@svgdotjs/svg.js": "^3.2.4", + "@svgdotjs/svg.select.js": "^4.0.1" + } + }, + "node_modules/@svgdotjs/svg.select.js": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/@svgdotjs/svg.select.js/-/svg.select.js-4.0.3.tgz", + "integrity": "sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg==", + "license": "MIT", + "engines": { + "node": ">= 14.18" + }, + "peerDependencies": { + "@svgdotjs/svg.js": "^3.2.4" + } + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.15.tgz", @@ -987,6 +1045,26 @@ "@types/react": "^19.2.0" } }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==", + "license": "MIT" + }, + "node_modules/apexcharts": { + "version": "5.3.5", + "resolved": "https://registry.npmmirror.com/apexcharts/-/apexcharts-5.3.5.tgz", + "integrity": "sha512-I04DY/WBZbJgJD2uixeV5EzyiL+J5LgKQXEu8rctqAwyRmKv44aDVeofJoLdTJe3ao4r2KEQfCgtVzXn6pqirg==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@svgdotjs/svg.draggable.js": "^3.0.4", + "@svgdotjs/svg.filter.js": "^3.0.8", + "@svgdotjs/svg.js": "^3.2.4", + "@svgdotjs/svg.resize.js": "^2.0.2", + "@svgdotjs/svg.select.js": "^4.0.1", + "@yr/monotone-cubic-spline": "^1.0.3" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", @@ -1323,6 +1401,12 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, "node_modules/lightningcss": { "version": "1.30.1", "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.30.1.tgz", @@ -1562,6 +1646,18 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/magic-string": { "version": "0.30.19", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.19.tgz", @@ -1723,6 +1819,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", @@ -1758,6 +1863,17 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -1773,6 +1889,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-apexcharts": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/react-apexcharts/-/react-apexcharts-1.8.0.tgz", + "integrity": "sha512-MuEp56gc0NMO2UUgY94fxQzoBE4XEjmcCha4xYY0vJdRrc1yfFFZE4QrCekOt2wcS3nibghzca/q/CbgkAgN5w==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "apexcharts": ">=4.0.0", + "react": ">=0.13" + } + }, "node_modules/react-dom": { "version": "19.1.0", "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.1.0.tgz", @@ -1801,6 +1930,12 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/react-toastify": { "version": "11.0.5", "resolved": "https://registry.npmmirror.com/react-toastify/-/react-toastify-11.0.5.tgz", diff --git a/package.json b/package.json index 24a8ffa..136babb 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,11 @@ "start": "next start" }, "dependencies": { + "apexcharts": "^5.3.5", "axios": "^1.12.2", "next": "15.5.5", "react": "19.1.0", + "react-apexcharts": "^1.8.0", "react-dom": "19.1.0", "react-hook-form": "^7.65.0", "react-toastify": "^11.0.5" diff --git a/public/Landing 11.svg b/public/Landing 11.svg new file mode 100644 index 0000000..374bb97 --- /dev/null +++ b/public/Landing 11.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/Landing 12.svg b/public/Landing 12.svg new file mode 100644 index 0000000..78b3d00 --- /dev/null +++ b/public/Landing 12.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/Landing 13.svg b/public/Landing 13.svg new file mode 100644 index 0000000..be71a4f --- /dev/null +++ b/public/Landing 13.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/arrow-up.svg b/public/arrow-up.svg new file mode 100644 index 0000000..2e182fb --- /dev/null +++ b/public/arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/clipboard.svg b/public/clipboard.svg new file mode 100644 index 0000000..9d37de4 --- /dev/null +++ b/public/clipboard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/clock.svg b/public/clock.svg new file mode 100644 index 0000000..c5db631 --- /dev/null +++ b/public/clock.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/document.svg b/public/document.svg new file mode 100644 index 0000000..72db25e --- /dev/null +++ b/public/document.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/footer-bg.svg b/public/footer-bg.svg new file mode 100644 index 0000000..c84c439 --- /dev/null +++ b/public/footer-bg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/footer-logo.svg b/public/footer-logo.svg new file mode 100644 index 0000000..027e8eb --- /dev/null +++ b/public/footer-logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/images/phone.png b/public/images/phone.png new file mode 100644 index 0000000..e8a3a4d Binary files /dev/null and b/public/images/phone.png differ diff --git a/public/magnifier.svg b/public/magnifier.svg new file mode 100644 index 0000000..a42e321 --- /dev/null +++ b/public/magnifier.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/main-bg.svg b/public/main-bg.svg new file mode 100644 index 0000000..4350d48 --- /dev/null +++ b/public/main-bg.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/mobile.svg b/public/mobile.svg new file mode 100644 index 0000000..2436784 --- /dev/null +++ b/public/mobile.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/people.svg b/public/people.svg new file mode 100644 index 0000000..ef97909 --- /dev/null +++ b/public/people.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/shield-tick.svg b/public/shield-tick.svg new file mode 100644 index 0000000..4c5d0e4 --- /dev/null +++ b/public/shield-tick.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/thumbs-up.svg b/public/thumbs-up.svg new file mode 100644 index 0000000..c0320a9 --- /dev/null +++ b/public/thumbs-up.svg @@ -0,0 +1,5 @@ + + + + +