feat: Refactor layout and add toast notifications
This commit introduces a major structural refactoring by creating a shared application layout and integrates a toast notification system. Key changes: - A new `layout.tsx` file now contains the common Header and Footer components, ensuring a consistent structure across all pages. - The main `page.tsx` has been simplified to only contain its specific content, with layout elements removed. - The `react-toastify` library has been added to provide user feedback through toast notifications. - Custom CSS styles have been applied to the toast notifications for a branded look and feel, including different gradient backgrounds for success, error, warning, and info states. - The `.gitignore` file was updated to explicitly track the `.env` file, likely for an example configuration.
This commit is contained in:
@@ -39,3 +39,4 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
!.env
|
||||
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export default function HeaderButtons() {
|
||||
const pathname = usePathname();
|
||||
return (
|
||||
<div className="flex justify-between items-center gap-5 mt-4 md:mt-0">
|
||||
<Link
|
||||
href={pathname.includes("contact-us") ? "/" : "/contact-us"}
|
||||
className="text-(--primary)">
|
||||
{pathname.includes("contact-us") ? "Home" : "Contact us"}
|
||||
</Link>
|
||||
<Link
|
||||
href={"/login"}
|
||||
className="bg-(--primary) text-white px-12 py-3 rounded-full">
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<TContactUsForm>();
|
||||
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 (
|
||||
<form
|
||||
className="w-full px-4 md:px-60 py-10"
|
||||
onSubmit={handleSubmit(onSubmit)}>
|
||||
<InputGroup
|
||||
icon={UserIcon}
|
||||
id="full_name"
|
||||
label="Full name"
|
||||
placeholder="Enter Full name"
|
||||
register={register}
|
||||
type="text"
|
||||
/>
|
||||
<InputGroup
|
||||
icon={UserIcon}
|
||||
id="company_name"
|
||||
label="Company name"
|
||||
placeholder="Enter Company Name"
|
||||
register={register}
|
||||
type="text"
|
||||
/>
|
||||
<InputGroup
|
||||
icon={UserIcon}
|
||||
id="work_email"
|
||||
label="Work email"
|
||||
placeholder="Enter Work Email"
|
||||
register={register}
|
||||
type="email"
|
||||
/>
|
||||
<InputGroup
|
||||
icon={UserIcon}
|
||||
id="phone_number"
|
||||
label="Phone number"
|
||||
placeholder="Enter Phone Number"
|
||||
register={register}
|
||||
type="text"
|
||||
error={errors.phone_number?.message}
|
||||
validation={{
|
||||
pattern: {
|
||||
value: /^[+\d]+$/,
|
||||
message: "Please enter a valid phone number",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<button className="bg-(--primary) w-full text-white rounded-full py-4">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
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<any>;
|
||||
type: HTMLInputTypeAttribute;
|
||||
validation?: RegisterOptions;
|
||||
error?: string;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}</label>
|
||||
<div className="relative w-full my-3">
|
||||
<input
|
||||
type={type}
|
||||
className={`border ${
|
||||
error ? "border-red-500" : "border-gray-200"
|
||||
} p-2 px-10 rounded-lg w-full`}
|
||||
id={id}
|
||||
{...register(id, validation)}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<span className="absolute left-0 bottom-0 top-0 flex items-center pl-2">
|
||||
{icon()}
|
||||
</span>
|
||||
</div>
|
||||
{error && <p className="text-red-500 text-sm my-3">{error}</p>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
function UserIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M16 6H15.25C15.25 7.79493 13.7949 9.25 12 9.25V10V10.75C14.6234 10.75 16.75 8.62335 16.75 6H16ZM12 10V9.25C10.2051 9.25 8.75 7.79493 8.75 6H8H7.25C7.25 8.62335 9.37665 10.75 12 10.75V10ZM8 6H8.75C8.75 4.20507 10.2051 2.75 12 2.75V2V1.25C9.37665 1.25 7.25 3.37665 7.25 6H8ZM12 2V2.75C13.7949 2.75 15.25 4.20507 15.25 6H16H16.75C16.75 3.37665 14.6234 1.25 12 1.25V2ZM9 13V13.75H15V13V12.25H9V13ZM15 21V20.25H9V21V21.75H15V21ZM9 21V20.25C7.20507 20.25 5.75 18.7949 5.75 17H5H4.25C4.25 19.6234 6.37665 21.75 9 21.75V21ZM19 17H18.25C18.25 18.7949 16.7949 20.25 15 20.25V21V21.75C17.6234 21.75 19.75 19.6234 19.75 17H19ZM15 13V13.75C16.7949 13.75 18.25 15.2051 18.25 17H19H19.75C19.75 14.3766 17.6234 12.25 15 12.25V13ZM9 13V12.25C6.37665 12.25 4.25 14.3766 4.25 17H5H5.75C5.75 15.2051 7.20507 13.75 9 13.75V13Z"
|
||||
fill="#9E9E9E"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import ContactUsForm from "./form";
|
||||
|
||||
const ContactUs = () => {
|
||||
return (
|
||||
<div className="flex flex-col items-center mt-20">
|
||||
<div className="flex flex-col items-center mb-16">
|
||||
<h6 className="text-lg w-fit font-semibold text-(--primary) bg-(--primary)/20 opacity-80 p-2 rounded-full">
|
||||
Contact Us
|
||||
</h6>
|
||||
|
||||
<h1 className="font-extrabold my-6 text-4xl">Let's get in touch</h1>
|
||||
<div className=" text-center block md:flex gap-1">
|
||||
<p className="font-extralight text-xl">
|
||||
Or just reach out manually to
|
||||
</p>
|
||||
<a
|
||||
href="mailto:info@opples.com"
|
||||
className="text-(--primary) font-extralight text-xl">
|
||||
info@opplens.com
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<section className="border border-gray-300 rounded-4xl w-10/12 flex flex-col items-center">
|
||||
<ContactUsForm />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactUs;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+154
-5
@@ -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 (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
|
||||
<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>
|
||||
<div className="hidden absolute -z-20 left-0 md:block">
|
||||
<Image
|
||||
src={"/side-shallow.svg"}
|
||||
alt="Shallow"
|
||||
width={235}
|
||||
height={235}
|
||||
/>
|
||||
</div>
|
||||
<ToastContainer
|
||||
position="top-right"
|
||||
autoClose={2500}
|
||||
theme={"colored"}
|
||||
transition={Zoom}
|
||||
hideProgressBar
|
||||
newestOnTop
|
||||
closeOnClick
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
/>
|
||||
{children}
|
||||
<div className="hidden absolute -z-20 right-0 rotate-y-180 md:block">
|
||||
<Image
|
||||
src={"/side-shallow.svg"}
|
||||
alt="Shallow"
|
||||
width={235}
|
||||
height={235}
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
+57
-192
@@ -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 (
|
||||
<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>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -228,37 +127,3 @@ function CenterFrame({
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Generated
+41
-1
@@ -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",
|
||||
|
||||
+3
-1
@@ -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",
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 4.8 MiB |
@@ -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;
|
||||
Reference in New Issue
Block a user