diff --git a/src/components/layouts/aboutUs/OurServices.tsx b/src/components/layouts/aboutUs/OurServices.tsx
index 9d02a5c89..b6670e6dd 100644
--- a/src/components/layouts/aboutUs/OurServices.tsx
+++ b/src/components/layouts/aboutUs/OurServices.tsx
@@ -1,44 +1,35 @@
"use client";
+import { useTranslations } from "next-intl";
import Image from "next/image";
-import Link from "next/link";
const OurServices = () => {
+ const t = useTranslations("ourServices");
return (
-
- Our Services
+
+ {t("title")}
-
- Trained to Give You The Best
+
+ {t("description")}
-
-
- Contact Us
-
- {`Since our founding in, Hng Boilerplate has been dedicated to
- constantly evolving to stay ahead of the curve. Our agile mindset
- and relentless pursuit of innovation ensure that you're always
- equipped with the most effective tools and strategies.`}
+ {t("details")}
diff --git a/src/components/layouts/aboutUs/Vision.tsx b/src/components/layouts/aboutUs/Vision.tsx
deleted file mode 100644
index 075071b77..000000000
--- a/src/components/layouts/aboutUs/Vision.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import Image from "next/image";
-
-const Vision = () => {
- return (
-
-
-
-
- Our Vision
-
-
- Leading the Way, Redefining Industries
-
-
- At Hng Boilerplate, Our vision is to revolutionize the industry
- landscape by continuously pushing the boundaries of innovation,
- setting new standards, and inspiring others to follow.
-
-
-
-
-
-
-
-
- );
-};
-
-export default Vision;
diff --git a/src/components/layouts/accordion/FaqAccordion.tsx b/src/components/layouts/accordion/FaqAccordion.tsx
new file mode 100644
index 000000000..de0ab3991
--- /dev/null
+++ b/src/components/layouts/accordion/FaqAccordion.tsx
@@ -0,0 +1,57 @@
+import clsx from "clsx";
+
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "~/components/ui/accordion";
+
+interface FaqAccordionProperties {
+ faqs: { question: string; answer: string }[];
+ containerClassName?: string;
+ triggerClassName?: string;
+ contentClassName?: string;
+}
+
+const FaqAccordion = ({
+ faqs,
+ triggerClassName,
+ contentClassName,
+ containerClassName,
+}: FaqAccordionProperties) => {
+ return (
+
+
+ {faqs?.map((faq, index) => (
+
+
+ {faq.question}
+
+
+ {faq.answer}
+
+
+ ))}
+
+
+ );
+};
+
+export default FaqAccordion;
diff --git a/src/components/layouts/accordion/FaqsAccordion.tsx b/src/components/layouts/accordion/FaqsAccordion.tsx
index 35841a8ac..22c994db0 100644
--- a/src/components/layouts/accordion/FaqsAccordion.tsx
+++ b/src/components/layouts/accordion/FaqsAccordion.tsx
@@ -1,4 +1,5 @@
import clsx from "clsx";
+import { useTranslations } from "next-intl";
import {
Accordion,
@@ -20,6 +21,7 @@ const FaqAccordion = ({
contentClassName,
containerClassName,
}: FaqAccordionProperties) => {
+ const t = useTranslations("faq");
return (
- {faq.question}
+ {t(`${faq.question}`)}
- {faq.content}
+ {t(`${faq.content}`)}
))}
diff --git a/src/components/layouts/footer/footer.test.tsx b/src/components/layouts/footer/footer.test.tsx
index 5edf885ce..4fa8c349b 100644
--- a/src/components/layouts/footer/footer.test.tsx
+++ b/src/components/layouts/footer/footer.test.tsx
@@ -1,12 +1,12 @@
import Footer from ".";
-import { render } from "~/test/utils";
+import { renderWithIntl } from "~/test/utils";
describe("page tests", () => {
it("footer renders", () => {
expect.assertions(1);
- const view = render(
);
+ const view = renderWithIntl(
);
expect(view.baseElement).toBeInTheDocument();
});
diff --git a/src/components/layouts/footer/index.tsx b/src/components/layouts/footer/index.tsx
index 2c343981b..b5de8c731 100644
--- a/src/components/layouts/footer/index.tsx
+++ b/src/components/layouts/footer/index.tsx
@@ -1,3 +1,6 @@
+"use client";
+
+import axios from "axios";
import {
Copyright,
Facebook,
@@ -6,38 +9,119 @@ import {
XIcon,
Youtube,
} from "lucide-react";
+import { useTranslations } from "next-intl";
import Link from "next/link";
+import { useState } from "react";
+import { getApiUrl } from "~/actions/getApiUrl";
import CustomButton from "~/components/common/common-button/common-button";
import { Input } from "~/components/common/input";
+import LoadingSpinner from "~/components/miscellaneous/loading-spinner";
+import { Toaster } from "~/components/ui/toaster";
+import { useToast } from "~/components/ui/use-toast";
const Footer = () => {
+ const [email, setEmail] = useState("");
+ const [error, setError] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const { toast } = useToast();
+ const t = useTranslations("footer");
+ const locale = localStorage.getItem("preferredLanguage");
+ const toastDesc =
+ locale === "fr"
+ ? "Veuillez fournir votre e-mail"
+ : locale === "es"
+ ? "Por favor, proporcione su correo electrónico"
+ : "Please provide a valid email";
+
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ const isValidEmail = (email: string): boolean => emailRegex.test(email);
+
+ const handleSubmit = async () => {
+ if (!isValidEmail(email)) {
+ setError(true);
+
+ toast({
+ title: "Error",
+ description: toastDesc,
+ variant: "destructive",
+ });
+ return;
+ }
+ setLoading(true);
+
+ const apiUrl = await getApiUrl();
+ await axios
+ .post(
+ `${apiUrl}/api/v1/newsletter-subscription`,
+ { email },
+ {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ },
+ )
+ .then(() => {
+ toast({
+ title: "Thank you for subscribing!",
+ description:
+ "You've successfully joined our newsletter. We're excited to keep you updated with our latest news and offers!",
+ variant: "default",
+ });
+ setLoading(false);
+ setEmail("");
+ })
+ .catch((error) => {
+ if (error?.response) {
+ const errorData = error.response.data;
+ if (errorData.status_code === 400) {
+ toast({
+ title: "You're already subscribed!",
+ description:
+ "It looks like you're already on our list. Thank you for being part of our community!",
+ variant: "default",
+ });
+ } else {
+ toast({
+ title: "Oops! Something went wrong.",
+ description:
+ "We encountered an issue while trying to subscribe you to our newsletter. Check your internet connection or contact support if the problem persists.",
+ variant: "destructive",
+ });
+ setLoading(false);
+ }
+ setLoading(false);
+ return;
+ }
+ });
+ };
+
const footerLinks = [
{
- title: "Navigation",
+ title: t("navigation"),
links: [
- { route: "Home", link: "/" },
- { route: "About us", link: "/about-us" },
- { route: "Career", link: "/career" },
- { route: "Feature updates", link: "/" },
- { route: "Blog", link: "/blog" },
+ { route: "home", link: "/" },
+ { route: "aboutUs", link: "/about-us" },
+ { route: "career", link: "/career" },
+ { route: "features", link: "/" },
+ { route: "blog", link: "/blog" },
],
},
{
- title: "Support",
+ title: t("support"),
links: [
- { route: "Help center", link: "/help-center" },
- { route: "FAQ", link: "/faqs" },
- { route: "Waiting List", link: "/waitlist" },
- { route: "Pricing Experience", link: "/pricing" },
- { route: "Contact Us", link: "/contact-us" },
+ { route: "helpCenter", link: "/help-center" },
+ { route: "faq", link: "/faqs" },
+ { route: "waitingList", link: "/waitlist" },
+ { route: "pricingExperience", link: "/pricing" },
+ { route: "contactUs", link: "/contact-us" },
],
},
{
- title: "Legal",
+ title: t("legal"),
links: [
- { route: "Privacy Policy", link: "/privacy-policy" },
- { route: "Terms and condition", link: "/terms-and-conditions" },
+ { route: "privacyPolicy", link: "/privacy-policy" },
+ { route: "termsAndConditions", link: "/terms-and-conditions" },
],
},
];
@@ -66,9 +150,12 @@ const Footer = () => {
];
const footerBottom = [
- { route: "Privacy Policy", link: "/" },
- { route: "Terms of Use", link: "/" },
+ { route: "privacyPolicy", link: "/" },
+ { route: "termsOfUse", link: "/" },
];
+
+ //
+
return (
);
})}
+
- Sign Up For Newsletter
+ {t("newsletterSignUp")}
-
-
-
- Subscibe
-
+
+
+
+ setEmail(event.target.value)}
+ value={email}
+ onBlur={() =>
+ email.length === 0 ? setError(true) : setError(false)
+ }
+ />
+
+ {loading ? (
+
+ ) : (
+ "Subscribe"
+ )}
+
+
+ {error && (
+
+ Please provide your email
+
+ )}
+
- Follow Us
+ {t("followUs")}
{socialLinks.map((item, index) => {
return (
@@ -164,7 +296,7 @@ const Footer = () => {
return (
@@ -174,7 +306,7 @@ const Footer = () => {
- 2024 All Rights Reserved
+ {t("footerBottom.copyright")}
@@ -185,7 +317,7 @@ const Footer = () => {
href={item.link}
className="cursor-pointer text-sm text-neutral-dark-2 transition-colors duration-300 hover:text-primary hover:underline"
>
- {item.route}
+ {t(`footerBottom.${item.route}`)}
);
@@ -194,6 +326,8 @@ const Footer = () => {
+
+
);
};
diff --git a/src/components/layouts/heading/index.tsx b/src/components/layouts/heading/index.tsx
new file mode 100644
index 000000000..a10e4d821
--- /dev/null
+++ b/src/components/layouts/heading/index.tsx
@@ -0,0 +1,49 @@
+interface Properties {
+ tag: string;
+ title: string;
+ content: string;
+}
+
+const renderTitle = (title: string) => {
+ const parts = title.split(/({{[^}]+}})/).filter(Boolean);
+
+ return parts.map((part, index) => {
+ if (part.startsWith("{{") && part.endsWith("}}")) {
+ const styledText = part.slice(2, -2);
+ return (
+
+ {styledText}
+
+ );
+ }
+ return
{part} ;
+ });
+};
+
+const Heading = (properties: Properties) => {
+ return (
+
+
+ {properties?.tag}
+
+
+
+ {renderTitle(properties.title)}
+
+
+ {properties?.content}
+
+
+ );
+};
+
+export default Heading;
diff --git a/src/components/layouts/homepage/Hero.tsx b/src/components/layouts/homepage/Hero.tsx
index cd6e88113..43b911e18 100644
--- a/src/components/layouts/homepage/Hero.tsx
+++ b/src/components/layouts/homepage/Hero.tsx
@@ -1,19 +1,16 @@
"use client";
+import { useTranslations } from "next-intl";
import Image from "next/image";
+import Link from "next/link";
import { A11y, Autoplay, Pagination, Scrollbar } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import { HeroBoilerPlate, HeroChat, HeroCheckMark } from "./svgs";
-// Import Swiper styles
-import "swiper/css";
-import "swiper/css/navigation";
-import "swiper/css/pagination";
-import "swiper/css/scrollbar";
-
-import Link from "next/link";
const Hero = () => {
+ const t = useTranslations("hero");
+
//
return (
@@ -21,8 +18,8 @@ const Hero = () => {
-
- Focus on What Matters. We've Got the Foundation Covered.
+
+ {t("headline")}
@@ -36,16 +33,15 @@ const Hero = () => {
- Streamline your processes with a boilerplate built for efficiency
- and optimal productivity.
+ {t("description")}
- Get Started
+ {t("cta")}
@@ -77,11 +73,11 @@ const Hero = () => {
spaceBetween={1}
slidesPerView={1}
autoplay={{
- delay: 3000,
+ delay: 5000,
disableOnInteraction: false,
}}
loop={true}
- speed={4000}
+ speed={1000}
data-testid="swiper"
>
diff --git a/src/components/layouts/homepage/HowItWorks.tsx b/src/components/layouts/homepage/HowItWorks.tsx
index ba5f78bb5..5bc3fb226 100644
--- a/src/components/layouts/homepage/HowItWorks.tsx
+++ b/src/components/layouts/homepage/HowItWorks.tsx
@@ -1,17 +1,20 @@
+import { useTranslations } from "next-intl";
+
import { Easy, Prebuilt, Scalable } from "./svgs";
const HowItWorks = () => {
+ const t = useTranslations("howItWorks");
return (
-
- How It Works: Experience the
- benefits of using our product with every step.
+
+ {t("howItWorksTitlePrefix")} {" "}
+ {t("howItWorksTitleHighlight")}
-
- {` We designed our product to simplify your life. It offers a comprehensive solution. Here's how it works and how it benefits you at each stage.`}
+
+ {t("howItWorksDescription")}
@@ -22,16 +25,16 @@ const HowItWorks = () => {
- Pre-Built Sections
+ {t("prebuiltTitle")}
- {` Leverage pre-built sections like "Features," "Benefits," "Pricing," and "Testimonials" to showcase your product effectively.`}
+ {t("prebuiltDescription")}
@@ -42,17 +45,16 @@ const HowItWorks = () => {
- Scalable Foundation
+ {t("scalableTitle")}
- Our boilerplate is designed to grow with your product. Easily
- add new features and functionalities as needed.
+ {t("scalableDescription")}
@@ -63,17 +65,16 @@ const HowItWorks = () => {
- Easy Customization
+ {t("easyTitle")}
- Tailor the experience to your specific needs and preferences
- for maximum results.
+ {t("easyDescription")}
diff --git a/src/components/layouts/homepage/PerfectFit.tsx b/src/components/layouts/homepage/PerfectFit.tsx
index c3ff4e2d5..d0e2ce196 100644
--- a/src/components/layouts/homepage/PerfectFit.tsx
+++ b/src/components/layouts/homepage/PerfectFit.tsx
@@ -1,27 +1,27 @@
"use client";
+import { useTranslations } from "next-intl";
import Link from "next/link";
//
const PerfectFit = () => {
+ const t = useTranslations("perfectFit");
//
return (
diff --git a/src/components/layouts/homepage/TestimonialCard.tsx b/src/components/layouts/homepage/TestimonialCard.tsx
index b8020b224..c85d75621 100644
--- a/src/components/layouts/homepage/TestimonialCard.tsx
+++ b/src/components/layouts/homepage/TestimonialCard.tsx
@@ -1,3 +1,4 @@
+import { useTranslations } from "next-intl";
import Image from "next/image";
import { Rating } from "./svgs";
@@ -10,10 +11,11 @@ interface Properties {
}
const TestimonialCard = (properties: Properties) => {
+ const t = useTranslations("testimonials");
return (