diff --git a/.env b/.env
new file mode 100644
index 0000000..fe58093
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+NEXT_PUBLIC_API_URL=https://opplens.com/api/v1/
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 5ef6a52..5f4f88b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,4 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+!.env
\ No newline at end of file
diff --git a/app/_components/HeaderButtons.tsx b/app/_components/HeaderButtons.tsx
new file mode 100644
index 0000000..197168f
--- /dev/null
+++ b/app/_components/HeaderButtons.tsx
@@ -0,0 +1,21 @@
+"use client";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+
+export default function HeaderButtons() {
+ const pathname = usePathname();
+ return (
+
+
+ {pathname.includes("contact-us") ? "Home" : "Contact us"}
+
+
+ Login
+
+
+ );
+}
diff --git a/app/contact-us/form.tsx b/app/contact-us/form.tsx
new file mode 100644
index 0000000..4fe70a2
--- /dev/null
+++ b/app/contact-us/form.tsx
@@ -0,0 +1,133 @@
+"use client";
+import api from "@/service/api";
+import { HTMLInputTypeAttribute } from "react";
+import { RegisterOptions, useForm, UseFormRegister } from "react-hook-form";
+import { toast } from "react-toastify";
+type TContactUsForm = {
+ full_name: string;
+ company_name: string;
+ work_email: string;
+ phone_number: string;
+};
+const ContactUsForm = () => {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm();
+ const onSubmit = async (data: TContactUsForm) => {
+ try {
+ const response = (await api.post("inquiries", { ...data })).data;
+ toast.success(response.message);
+ } catch (error) {
+ console.log(error);
+ toast.error("Something went wrong");
+ throw error;
+ }
+ };
+ 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 (
+ <>
+ {label}
+
+
+
+ {icon()}
+
+
+ {error && {error}
}
+ >
+ );
+}
+function UserIcon() {
+ return (
+
+
+
+ );
+}
diff --git a/app/contact-us/page.tsx b/app/contact-us/page.tsx
new file mode 100644
index 0000000..5bb4a81
--- /dev/null
+++ b/app/contact-us/page.tsx
@@ -0,0 +1,30 @@
+import ContactUsForm from "./form";
+
+const ContactUs = () => {
+ return (
+
+
+
+ Contact Us
+
+
+
Let's get in touch
+
+
+
+
+ );
+};
+
+export default ContactUs;
diff --git a/app/globals.css b/app/globals.css
index 1d41861..f3e3d78 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -14,3 +14,41 @@
.gradient-text {
@apply text-transparent bg-clip-text bg-[radial-gradient(circle,rgba(1,100,255,1)_0%,rgba(111,255,255,1)_100%)] w-fit text-6xl font-extrabold;
}
+
+.Toastify__toast {
+ padding: 10px !important;
+ border-radius: 24px !important;
+ font-family: inherit !important;
+ display: flex !important;
+ align-items: center !important;
+
+ box-shadow: 0px 12px 34px 0px rgba(13, 10, 44, 0.05) !important;
+}
+
+.dark .Toastify__toast {
+ box-shadow: 0 20px 25px -5px rgb(255 255 255 / 0.07),
+ 0 8px 10px -6px rgb(255 255 255 / 0.05) !important;
+}
+
+.Toastify__close-button {
+ display: none;
+}
+.Toastify__close-button {
+ display: none;
+}
+
+.Toastify__toast--success {
+ background: linear-gradient(to bottom right, #82e6ac, #1a8245) !important;
+}
+
+.Toastify__toast--error {
+ background: linear-gradient(to bottom right, #f89090, #e10e0e) !important;
+}
+
+.Toastify__toast--warning {
+ background: linear-gradient(to bottom right, #f59e0b, #d97706) !important;
+}
+
+.Toastify__toast--info {
+ background: linear-gradient(to bottom right, #8099ec, #1c3fb7) !important;
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index f7fa87e..e12e3c4 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,7 +1,10 @@
import type { Metadata } from "next";
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 "./globals.css";
-
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
@@ -17,17 +20,163 @@ export const metadata: Metadata = {
description: "Generated by create next app",
};
-export default function RootLayout({
+function ContactInfo({
+ icon,
+ contact,
+ href,
+ ariaLabel,
+}: {
+ icon: string;
+ contact: string;
+ href?: string;
+ ariaLabel: string;
+}) {
+ return (
+
+ );
+}
+
+export default async function RootLayout({
children,
}: Readonly<{
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",
+ },
+ ];
+
return (
- {children}
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
);
diff --git a/app/page.tsx b/app/page.tsx
index 4ce78d3..a91813f 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,6 +1,5 @@
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",
@@ -45,165 +44,65 @@ 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 (
-
-
-
-
-
-
-
-
-
- AI-Powered
-
-
-
-
- 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.
-
-
-
-
-
-
-
- Platform Statistics
-
- {centerFrameItems.map((item, index) => (
-
- ))}
-
-
-
-
- );
-}
-function HeaderButtons() {
return (
-
-
- Contact us
-
-
- Login
-
-
+
+
+
+
+ AI-Powered
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+ Platform Statistics
+
+ {centerFrameItems.map((item, index) => (
+
+ ))}
+
+
);
}
@@ -228,37 +127,3 @@ function CenterFrame({
);
}
-
-function ContactInfo({
- icon,
- contact,
- href,
- ariaLabel,
-}: {
- icon: string;
- contact: string;
- href?: string;
- ariaLabel: string;
-}) {
- return (
-
- );
-}
diff --git a/assets/css/style.css b/assets/css/style.css
index bdc66ae..3fc04de 100644
--- a/assets/css/style.css
+++ b/assets/css/style.css
@@ -892,3 +892,42 @@ html body a:hover {
}
/*# sourceMappingURL=style.css.map */
+
+.Toastify__toast {
+ padding: 10px !important;
+ border-radius: 24px !important;
+ font-family: inherit !important;
+ display: flex !important;
+ align-items: center !important;
+
+ box-shadow: 0px 12px 34px 0px rgba(13, 10, 44, 0.05) !important;
+}
+
+.dark .Toastify__toast {
+ box-shadow:
+ 0 20px 25px -5px rgb(255 255 255 / 0.07),
+ 0 8px 10px -6px rgb(255 255 255 / 0.05) !important;
+}
+
+.Toastify__close-button {
+ display: none;
+}
+.Toastify__close-button {
+ display: none;
+}
+
+.Toastify__toast--success {
+ background: linear-gradient(to bottom right, #82e6ac, #1a8245) !important;
+}
+
+.Toastify__toast--error {
+ background: linear-gradient(to bottom right, #f89090, #e10e0e) !important;
+}
+
+.Toastify__toast--warning {
+ background: linear-gradient(to bottom right, #f59e0b, #d97706) !important;
+}
+
+.Toastify__toast--info {
+ background: linear-gradient(to bottom right, #8099ec, #1c3fb7) !important;
+}
diff --git a/package-lock.json b/package-lock.json
index 054bb44..e700c70 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,9 @@
"axios": "^1.12.2",
"next": "15.5.5",
"react": "19.1.0",
- "react-dom": "19.1.0"
+ "react-dom": "19.1.0",
+ "react-hook-form": "^7.65.0",
+ "react-toastify": "^11.0.5"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
@@ -1051,6 +1053,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -1774,6 +1785,35 @@
"react": "^19.1.0"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.65.0",
+ "resolved": "https://registry.npmmirror.com/react-hook-form/-/react-hook-form-7.65.0.tgz",
+ "integrity": "sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/react-toastify": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmmirror.com/react-toastify/-/react-toastify-11.0.5.tgz",
+ "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19",
+ "react-dom": "^18 || ^19"
+ }
+ },
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.26.0.tgz",
diff --git a/package.json b/package.json
index ee80422..24a8ffa 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,9 @@
"axios": "^1.12.2",
"next": "15.5.5",
"react": "19.1.0",
- "react-dom": "19.1.0"
+ "react-dom": "19.1.0",
+ "react-hook-form": "^7.65.0",
+ "react-toastify": "^11.0.5"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
diff --git a/public/side-shallow.svg b/public/side-shallow.svg
new file mode 100644
index 0000000..26f5141
--- /dev/null
+++ b/public/side-shallow.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/service/api.ts b/service/api.ts
new file mode 100644
index 0000000..ea6c091
--- /dev/null
+++ b/service/api.ts
@@ -0,0 +1,10 @@
+import axios from "axios";
+
+const api = axios.create({
+ baseURL: process.env.NEXT_PUBLIC_API_URL,
+ timeout: 10000,
+ headers: {
+ "Content-Type": "application/json",
+ },
+});
+export default api;