Browse Source

feat: add robots.txt and sitemap.xml for improved SEO, implement custom 404 page with localization support

master
sina_sajjadi 3 days ago
parent
commit
6f9ac13d0c
  1. 37
      next-sitemap.config.js
  2. BIN
      public/assets/images/Icon_Illustration.webp
  3. 6
      public/locales/en/common.json
  4. 10
      public/robots.txt
  5. 27
      public/sitemap-0.xml
  6. 5
      public/sitemap.xml
  7. 46
      src/components/common/default-seo.tsx
  8. 2
      src/components/context/audio-conext.tsx
  9. 2
      src/components/layout/header.tsx
  10. 5
      src/components/layout/sidebar.tsx
  11. 102
      src/components/modals/donate-modal.tsx
  12. 4
      src/components/modals/search-modal.tsx
  13. 5
      src/components/sidebar/categories.tsx
  14. 4
      src/components/sidebar/famous.tsx
  15. 4
      src/components/sidebar/nearby.tsx
  16. 4
      src/components/sidebar/search.tsx
  17. 4
      src/components/sidebar/tabs.tsx
  18. 4
      src/components/sidebar/today.tsx
  19. 34
      src/pages/404.tsx
  20. 15
      src/pages/duas/[slug].tsx

37
next-sitemap.config.js

@ -1,12 +1,31 @@
const config = {
siteUrl: 'https://www.example.com',
generateRobotsTxt: true,
// next-sitemap.config.js
module.exports = {
siteUrl: 'https://duasapp.com', // Replace with your site's URL
generateRobotsTxt: true, // Generates robots.txt alongside the sitemap
changefreq: 'monthly', // Set the frequency of page changes
priority: 0.8, // Default priority for pages
sitemapSize: 5000, // Maximum entries per sitemap file
exclude: ['/admin/*', '/dashboard/*', '/404*'], // Exclude specific paths
additionalPaths: async (config) => {
// Custom static paths
const staticPaths = [
await config.transform(config, '/about'),
];
// Dynamic Dua paths (replace this with API call or database query in production)
const duaSlugs = ["etiquette-of-reciting-the-request-for-entry", "etiquettes-of-carrying-the-janazah", "dua-abu-hamza-thumali"]; // Example dynamic IDs
const dynamicPaths = duaSlugs.map((slug) => ({
loc: `/duas/${slug}`,
lastmod: new Date().toISOString(),
changefreq: 'monthly',
priority: 0.9,
}));
return [...staticPaths, ...dynamicPaths];
},
robotsTxtOptions: { robotsTxtOptions: {
policies: [
{ userAgent: '*', allow: '/', disallow: ['/api/', '/admin/', '/private/'] },
additionalSitemaps: [
'https://duasapp.com/sitemap-0.xml', // Add other sitemaps if necessary
], ],
}, },
};
module.exports = config;
};

BIN
public/assets/images/Icon_Illustration.webp

6
public/locales/en/common.json

@ -40,5 +40,9 @@
"monthly": "Monthly", "monthly": "Monthly",
"enter_desired_amount": "Enter the desired amount...", "enter_desired_amount": "Enter the desired amount...",
"donate_now": "Donate Now", "donate_now": "Donate Now",
"credits": "Credits"
"credits": "Credits",
"notFoundImageAlt": "Not Found Illustration",
"ohNo": "Oh No!",
"somethingWentWrong": "Something went wrong!",
"backToHomePage": "Back to Home Page"
} }

10
public/robots.txt

@ -0,0 +1,10 @@
# *
User-agent: *
Allow: /
# Host
Host: https://duasapp.com
# Sitemaps
Sitemap: https://duasapp.com/sitemap.xml
Sitemap: https://duasapp.com/sitemap-0.xml

27
public/sitemap-0.xml

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
<url><loc>https://duasapp.com</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/about</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/es/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/de/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/uz/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/pt/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/bn/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/zh/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/az/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/ur/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/fr/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/tr/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/id/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/sw/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/ru/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/ar/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/tg/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/fa/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/gu/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/ks/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/ha/404</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>monthly</changefreq><priority>0.8</priority></url>
<url><loc>https://duasapp.com/dua/etiquette-of-reciting-the-request-for-entry</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>daily</changefreq><priority>0.9</priority></url>
<url><loc>https://duasapp.com/dua/etiquettes-of-carrying-the-janazah</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>daily</changefreq><priority>0.9</priority></url>
<url><loc>https://duasapp.com/dua/dua-abu-hamza-thumali</loc><lastmod>2025-01-15T13:11:26.470Z</lastmod><changefreq>daily</changefreq><priority>0.9</priority></url>
</urlset>

5
public/sitemap.xml

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>https://duasapp.com/sitemap-0.xml</loc></sitemap>
<sitemap><loc>https://duasapp.com/sitemap-0.xml</loc></sitemap>
</sitemapindex>

46
src/components/common/default-seo.tsx

@ -1,3 +1,5 @@
// components/DefaultSeo.js
import React from "react";
import { DefaultSeo as NextDefaultSeo } from "next-seo"; import { DefaultSeo as NextDefaultSeo } from "next-seo";
interface DefaultSeoProps { interface DefaultSeoProps {
@ -12,7 +14,7 @@ interface DefaultSeoProps {
const DefaultSeo: React.FC<DefaultSeoProps> = ({ const DefaultSeo: React.FC<DefaultSeoProps> = ({
type = "site", type = "site",
title = "Dua Site",
title = "Dua's Site",
description = "A comprehensive collection of Duas", description = "A comprehensive collection of Duas",
keywords = "dua, islam, prayer, supplication", keywords = "dua, islam, prayer, supplication",
url = "https://duasapp.com", url = "https://duasapp.com",
@ -25,38 +27,38 @@ const DefaultSeo: React.FC<DefaultSeoProps> = ({
? { ? {
"@context": "https://schema.org/", "@context": "https://schema.org/",
"@type": "WebSite", "@type": "WebSite",
"name": title,
"url": url,
"description": description,
"publisher": {
name: title,
url: url,
description: description,
publisher: {
"@type": "Organization", "@type": "Organization",
"name": "DuasApp",
"url": url,
"logo": {
name: "DuasApp",
url: url,
logo: {
"@type": "ImageObject", "@type": "ImageObject",
"url": "/assets/images/Hosseiniye.svg",
url: `${url}/assets/images/Hosseiniye.svg`,
}, },
}, },
"potentialAction": {
potentialAction: {
"@type": "SearchAction", "@type": "SearchAction",
"target": `${url}/?search={search_term_string}`,
target: `${url}/?search={search_term_string}`,
"query-input": "required name=search_term_string", "query-input": "required name=search_term_string",
}, },
"inLanguage": "en",
inLanguage: "en",
} }
: { : {
"@context": "https://schema.org/", "@context": "https://schema.org/",
"@type": "CreativeWork", "@type": "CreativeWork",
"name": title,
"url": url,
"description": description,
"text": text,
"translation": {
name: title,
url: url,
description: description,
text: text,
translation: {
"@type": "CreativeWork", "@type": "CreativeWork",
"name": "Translation",
"text": translation,
name: "Translation",
text: translation,
}, },
"keywords": keywords,
keywords: keywords,
}; };
return ( return (
@ -72,7 +74,7 @@ const DefaultSeo: React.FC<DefaultSeoProps> = ({
description: description, description: description,
images: [ images: [
{ {
url: "/assets/images/Hosseiniye.svg",
url: `${url}/assets/images/Hosseiniye.svg`,
width: 1200, width: 1200,
height: 630, height: 630,
alt: type === "site" ? "Dua Site" : title, alt: type === "site" ? "Dua Site" : title,
@ -92,7 +94,7 @@ const DefaultSeo: React.FC<DefaultSeoProps> = ({
charset: "utf-8", charset: "utf-8",
}, },
{ {
name: "apple-mobile-web-app-capable",
name: "mobile-web-app-capable",
content: "yes", content: "yes",
}, },
{ {

2
src/components/context/audio-conext.tsx

@ -79,7 +79,7 @@ export const AudioProvider: React.FC<{ children: ReactNode }> = ({
setAudio(selectedAudio || audioResponse.data.results[0] || null); setAudio(selectedAudio || audioResponse.data.results[0] || null);
} catch (error) { } catch (error) {
console.error("Error fetching audios:", error);
console.log("Error fetching audios:", error);
} }
}; };

2
src/components/layout/header.tsx

@ -16,7 +16,7 @@ const Header = () => {
}`} }`}
aria-label="Main Navigation" aria-label="Main Navigation"
> >
<div className="max-w-[1440px] h-20 m-auto flex justify-between w-full items-center lg:p-6">
<div className="max-w-[1440px] h-20 m-auto flex justify-between w-full items-center lg:px-6">
{/* Logo Section */} {/* Logo Section */}
<div className="flex gap-11 h-full items-center"> <div className="flex gap-11 h-full items-center">
<Logo aria-label="Site Logo" /> <Logo aria-label="Site Logo" />

5
src/components/layout/sidebar.tsx

@ -6,14 +6,15 @@ function SideBar() {
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const slug = params?.slug as string; const slug = params?.slug as string;
console.log(router.pathname , router.pathname.includes("/404"));
if (router.pathname.includes("/about") || router.pathname.includes("/last-read")) {
if (router.pathname.includes("/about")) {
return null; return null;
} }
return ( return (
<aside <aside
className={`w-full lg:h-[calc(100vh-130px)] self-start overflow-auto rounded-3xl p-6 lg:max-w-[430px] lg:bg-[#F5F5F5] ${ className={`w-full lg:h-[calc(100vh-130px)] self-start overflow-auto rounded-3xl p-6 lg:max-w-[430px] lg:bg-[#F5F5F5] ${
slug && "hidden lg:block"
(slug || router.pathname.includes("/404")) && "hidden lg:block"
}`} }`}
> >
<Tabs /> <Tabs />

102
src/components/modals/donate-modal.tsx

@ -1,31 +1,62 @@
import React, { useState, MouseEvent } from "react";
import React, { useState, useEffect, MouseEvent } from "react";
import { IoClose } from "react-icons/io5"; import { IoClose } from "react-icons/io5";
import credits from "../.././../public/assets/images/Frame 1116606661.svg";
import Image from "next/image"; import Image from "next/image";
import { useUI } from "../context/ui.context"; import { useUI } from "../context/ui.context";
import { useTranslation } from "next-i18next"; // Importing translation hook
import { useTranslation } from "next-i18next";
import http from "@/api/http";
const DonateModal: React.FC = () => { const DonateModal: React.FC = () => {
const { t } = useTranslation("common"); // Initialize translation hook
const { t } = useTranslation("common");
const { closeModal } = useUI(); const { closeModal } = useUI();
const [selected, setSelected] = useState("one-time"); // 'one-time' or 'monthly'
const [selectedPrice, setSelectedPrice] = useState<number | null>(null);
const [selected, setSelected] = useState("once");
const [selectedPrice, setSelectedPrice] = useState<string | null>(null);
const [prices, setPrices] = useState<any[]>([]);
const [iconUrl, setIconUrl] = useState<string>("");
const [availableTypes, setAvailableTypes] = useState({ once: false, monthly: false });
// Define the prices for each type
const oneTimePrices = [5, 15, 30, 50];
const monthlyPrices = [5, 10, 20, 30];
// Determine which price list to display based on the selected type
const prices = selected === "monthly" ? monthlyPrices : oneTimePrices;
useEffect(() => {
// Fetch prices and icons
http.get("/web/donate/v2/").then((response) => {
const data = response.data;
setAvailableTypes({
once: !!data.once,
monthly: !!data.monthly,
});
if (selected === "monthly" && data.monthly) {
setPrices(data.monthly.prices);
setIconUrl(data.monthly.icon);
} else if (selected === "once" && data.once) {
setPrices(data.once.prices);
setIconUrl(data.once.icon);
}
});
}, [selected]);
// Closes modal if backdrop is clicked
const handleBackdropClick = (event: MouseEvent<HTMLDivElement>) => { const handleBackdropClick = (event: MouseEvent<HTMLDivElement>) => {
// Ensure we're clicking directly on the backdrop (not a child element)
if (event.target === event.currentTarget) { if (event.target === event.currentTarget) {
closeModal(); closeModal();
} }
}; };
const handleDonateNow = () => {
if (!selectedPrice) return; // Ensure a price is selected
const payload = {
donate_type: selected,
price: selectedPrice,
};
http
.post("/web/donate/v2/create/", payload)
.then((response) => {
const { donate_link } = response.data;
window.location.href = donate_link;
})
.catch((error) => {
console.error("Error creating donation:", error);
});
};
return ( return (
<div <div
className="fixed inset-0 flex items-center justify-center bg-black/30 z-50" className="fixed inset-0 flex items-center justify-center bg-black/30 z-50"
@ -48,29 +79,32 @@ const DonateModal: React.FC = () => {
{t("support_us_to_grow")} {t("support_us_to_grow")}
</h5> </h5>
</div> </div>
<div className="w-full bg-[#F5F5F5] h-[152px] overflow-y-auto mt-10"> <div className="w-full bg-[#F5F5F5] h-[152px] overflow-y-auto mt-10">
<p className="text-sm text-gray-500">
{t("donation_message")}
</p>
<p className="text-sm text-gray-500">{t("donation_message")}</p>
</div> </div>
<div> <div>
<div className="bg-[#EBEBEB] mt-8 rounded-2xl px-4 py-3 sm:px-6 flex"> <div className="bg-[#EBEBEB] mt-8 rounded-2xl px-4 py-3 sm:px-6 flex">
{availableTypes.once && (
<button <button
type="button" type="button"
className={`w-full inline-flex justify-center rounded-2xl px-4 py-2 bg-[#EBEBEB] text-[#8B8B8B] text-base font-medium ${ className={`w-full inline-flex justify-center rounded-2xl px-4 py-2 bg-[#EBEBEB] text-[#8B8B8B] text-base font-medium ${
selected === "one-time" && "bg-white text-black shadow-sm"
selected === "once" && "bg-white text-black shadow-sm"
}`} }`}
onClick={() => { onClick={() => {
setSelected("one-time");
setSelectedPrice(null); // reset selected price
setSelected("once");
setSelectedPrice(null);
}} }}
> >
{t("one_time")} {t("one_time")}
</button> </button>
)}
{availableTypes.monthly && (
<button <button
onClick={() => { onClick={() => {
setSelected("monthly"); setSelected("monthly");
setSelectedPrice(null); // reset selected price
setSelectedPrice(null);
}} }}
type="button" type="button"
className={`w-full inline-flex justify-center rounded-2xl px-4 py-2 bg-[#EBEBEB] text-[#8B8B8B] text-base font-medium ${ className={`w-full inline-flex justify-center rounded-2xl px-4 py-2 bg-[#EBEBEB] text-[#8B8B8B] text-base font-medium ${
@ -79,24 +113,23 @@ const DonateModal: React.FC = () => {
> >
{t("monthly")} {t("monthly")}
</button> </button>
)}
</div> </div>
</div> </div>
<div className="flex items-center mt-6 flex-col gap-4"> <div className="flex items-center mt-6 flex-col gap-4">
<div className="flex gap-4 w-full"> <div className="flex gap-4 w-full">
{prices.map((price) => (
{prices.map((price: any) => (
<button <button
key={price}
onClick={() => setSelectedPrice(price)}
className={`w-full py-5 px-6 rounded-2xl
${
selectedPrice === price
key={price.price}
onClick={() => setSelectedPrice(price.price)}
className={`w-full py-5 px-6 rounded-2xl ${
selectedPrice === price.price
? "bg-[#2D4ECD]/20 text-[#2D4ECD]" ? "bg-[#2D4ECD]/20 text-[#2D4ECD]"
: "bg-[#D7DBE2] text-black" : "bg-[#D7DBE2] text-black"
}
`}
}`}
> >
{price}$
{price.value}
</button> </button>
))} ))}
</div> </div>
@ -108,12 +141,17 @@ const DonateModal: React.FC = () => {
[&::-webkit-inner-spin-button]:appearance-none" [&::-webkit-inner-spin-button]:appearance-none"
placeholder={t("enter_desired_amount")} placeholder={t("enter_desired_amount")}
type="number" type="number"
value={selectedPrice || ""}
onChange={(e) => setSelectedPrice(e.target.value)}
/> />
</div> </div>
<button className="flex justify-between w-full h-[70px] px-7 items-center bg-[#2D4ECD] rounded-xl text-white text-lg font-bold mt-10">
<button
onClick={handleDonateNow}
className="flex justify-between w-full h-[70px] px-7 items-center bg-[#2D4ECD] rounded-xl text-white text-lg font-bold mt-10"
>
<p className="whitespace-nowrap">{t("donate_now")}</p> <p className="whitespace-nowrap">{t("donate_now")}</p>
<Image src={credits} alt={t("credits")} />
<Image src={iconUrl} alt={t("credits")} width={175} height={27} />
</button> </button>
</div> </div>
</div> </div>

4
src/components/modals/search-modal.tsx

@ -25,8 +25,8 @@ const SearchModal: React.FC = () => {
const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]");
const updatedDuas = [...lastReadDuas, dua]; const updatedDuas = [...lastReadDuas, dua];
localStorage.setItem("last-read", JSON.stringify(updatedDuas)); localStorage.setItem("last-read", JSON.stringify(updatedDuas));
const slug = dua.title.toLowerCase().replaceAll(" ", "-");
router.push(`/duas/${slug}-${dua.id}`);
const slug = dua.slug
router.push(`/duas/${slug}`);
closeModal(); closeModal();
}; };

5
src/components/sidebar/categories.tsx

@ -34,6 +34,7 @@ const Categories: React.FC<CategoriesProps> = ({
{ name: category.name, type: "children", data: category.children }, { name: category.name, type: "children", data: category.children },
]); ]);
}; };
console.log(data);
const openDua = (dua: Dua) => { const openDua = (dua: Dua) => {
const lastReadDuas = JSON.parse( const lastReadDuas = JSON.parse(
@ -41,8 +42,8 @@ const Categories: React.FC<CategoriesProps> = ({
); );
const updatedDuas = [...lastReadDuas, dua]; const updatedDuas = [...lastReadDuas, dua];
localStorage.setItem("last-read", JSON.stringify(updatedDuas)); localStorage.setItem("last-read", JSON.stringify(updatedDuas));
const slug = dua.title.toLowerCase().replaceAll(" ", "-");
router.push(`/duas/${slug}-${dua.id}`);
const slug = dua.slug
router.push(`/duas/${slug}`);
}; };
useEffect(() => { useEffect(() => {

4
src/components/sidebar/famous.tsx

@ -22,8 +22,8 @@ const Famous: React.FC = () => {
const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]");
const updatedDuas = [...lastReadDuas, dua]; const updatedDuas = [...lastReadDuas, dua];
localStorage.setItem("last-read", JSON.stringify(updatedDuas)); localStorage.setItem("last-read", JSON.stringify(updatedDuas));
const slug = dua.title.toLowerCase().replaceAll(" ", "-");
router.push(`/duas/${slug}-${dua.id}`);
const slug = dua.slug
router.push(`/duas/${slug}`);
}; };
useEffect(() => { useEffect(() => {

4
src/components/sidebar/nearby.tsx

@ -19,8 +19,8 @@ const NearBy = () => {
const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]");
const updatedDuas = [...lastReadDuas, dua]; const updatedDuas = [...lastReadDuas, dua];
localStorage.setItem("last-read", JSON.stringify(updatedDuas)); localStorage.setItem("last-read", JSON.stringify(updatedDuas));
const slug = dua.title.toLowerCase().replaceAll(" ", "-");
router.push(`/duas/${slug}-${dua.id}`);
const slug = dua.slug
router.push(`/duas/${slug}`);
}; };
const onClick = () => { const onClick = () => {
// Check if geolocation is available // Check if geolocation is available

4
src/components/sidebar/search.tsx

@ -23,8 +23,8 @@ const Search: React.FC = () => {
const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]");
const updatedDuas = [...lastReadDuas, dua]; const updatedDuas = [...lastReadDuas, dua];
localStorage.setItem("last-read", JSON.stringify(updatedDuas)); localStorage.setItem("last-read", JSON.stringify(updatedDuas));
const slug = dua.title.toLowerCase().replaceAll(" ", "-");
router.push(`/duas/${slug}-${dua.id}`);
const slug = dua.slug
router.push(`/duas/${slug}`);
}; };
useEffect(() => { useEffect(() => {

4
src/components/sidebar/tabs.tsx

@ -66,13 +66,13 @@ const Tabs = () => {
path.map((item, index) => { path.map((item, index) => {
if (item.name && index + 1 < path.length && index > 0) { if (item.name && index + 1 < path.length && index > 0) {
return ( return (
<span key={index} className="flex items-center">
<span key={index} className="">
<Link href="#" onClick={() => handlePathClick(index)}> <Link href="#" onClick={() => handlePathClick(index)}>
<span className="text-xs text-[#8B8B8B] font-semibold cursor-pointer"> <span className="text-xs text-[#8B8B8B] font-semibold cursor-pointer">
{item.name} {item.name}
</span> </span>
</Link> </Link>
<span className="mx-2"></span>
<span className="mx-2 "></span>
</span> </span>
); );
} else { } else {

4
src/components/sidebar/today.tsx

@ -58,8 +58,8 @@ const Today: React.FC = () => {
const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]");
const updatedDuas = [...lastReadDuas, dua]; const updatedDuas = [...lastReadDuas, dua];
localStorage.setItem("last-read", JSON.stringify(updatedDuas)); localStorage.setItem("last-read", JSON.stringify(updatedDuas));
const slug = dua.title.toLowerCase().replaceAll(" ", "-");
router.push(`/duas/${slug}-${dua.id}`);
const slug = dua.slug
router.push(`/duas/${slug}`);
}; };
useEffect(() => { useEffect(() => {

34
src/pages/404.tsx

@ -0,0 +1,34 @@
import React from "react";
import Link from "next/link";
import notFound from "../../public/assets/images/Icon_Illustration.webp";
import Image from "next/image";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useTranslation } from "next-i18next";
const NotFound: React.FC = () => {
const { t } = useTranslation("common");
return (
<div className="w-full h-full flex items-center justify-center mt-[48%]">
<div className="text-center">
<Image src={notFound} alt={t("notFoundImageAlt")} />
<h1 className="text-xl">{t("ohNo")}</h1>
<p className="text-[#8B8B8B]">{t("somethingWentWrong")}</p>
<div className="bg-white p-2 rounded-2xl mt-4">
<Link href="/">{t("backToHomePage")}</Link>
</div>
</div>
</div>
);
};
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, ["common", "footer"])),
// Will be passed to the page component as props
},
};
}
export default NotFound;

15
src/pages/duas/[slug].tsx

@ -15,6 +15,7 @@ import { useFontSettingsContext } from "@/components/context/font-setting-contex
import { useAudio } from "@/components/context/audio-conext"; import { useAudio } from "@/components/context/audio-conext";
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { DefaultSeo } from "next-seo"; import { DefaultSeo } from "next-seo";
import NotFound from "../404";
// Define the Dua interface // Define the Dua interface
interface Dua { interface Dua {
@ -64,7 +65,7 @@ const DuaComponent: React.FC<DuaComponentProps> = ({
async (nextPage) => { async (nextPage) => {
if (!slug || fetching) return; // Prevent fetching if data is already being fetched if (!slug || fetching) return; // Prevent fetching if data is already being fetched
const id = slug.split("-").pop();
const id = slug;
if (!id) return; if (!id) return;
setFetching(true); // Set fetching to true when data starts fetching setFetching(true); // Set fetching to true when data starts fetching
@ -82,14 +83,17 @@ const DuaComponent: React.FC<DuaComponentProps> = ({
// Append the new results to the existing duaParts // Append the new results to the existing duaParts
setDuaParts((prev) => [...prev, ...duaResponse.data.results]); setDuaParts((prev) => [...prev, ...duaResponse.data.results]);
console.log(duaResponse);
getAudio(id);
} catch (error) { } catch (error) {
console.error("Error fetching Dua parts:", error);
router.replace("/404")
console.log("Error fetching Dua parts:", error);
} finally { } finally {
setLoading(false); setLoading(false);
setFetching(false); // Reset fetching state after fetching is done setFetching(false); // Reset fetching state after fetching is done
} }
getAudio(id);
}, },
[slug, fetching, duaParts, getAudio] [slug, fetching, duaParts, getAudio]
); // Dependencies for fetchData ); // Dependencies for fetchData
@ -237,12 +241,13 @@ const DuaComponent: React.FC<DuaComponentProps> = ({
}, },
[fetching] [fetching]
); );
console.log(duaParts[0]?.seo_field);
if (!slug) { if (!slug) {
return null; // Handling the case where slug is not available return null; // Handling the case where slug is not available
} }
const title = processSlug(slug); // Title derived from slug
const description = "This is a detailed description for the specific Dua page."; // You can customize this further
const title = duaParts[0]?.seo_field?.title // Title derived from slug
const description = duaParts[0]?.seo_field?.description; // You can customize this further
const keywords = "dua, islam, prayer, supplication, dua parts"; // Keywords for SEO const keywords = "dua, islam, prayer, supplication, dua parts"; // Keywords for SEO
return ( return (

Loading…
Cancel
Save