diff --git a/package-lock.json b/package-lock.json index 56278c8..9434862 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,10 +19,13 @@ "moment-hijri": "^3.0.0", "next": "15.1.0", "next-i18next": "^15.4.1", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "next-seo": "^6.6.0", + "react": "18.2.0", + "react-content-loader": "^7.0.2", + "react-dom": "18.2.0", + "react-ga4": "^2.1.0", "react-i18next": "^15.4.0", - "react-icons": "^5.4.0" + "react-icons": "4.11.0" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -856,7 +859,7 @@ "react-dom": "^16.8.0 || 17.x" } }, - "node_modules/@reach/utils": { + "node_modules/@reach/portal/node_modules/@reach/utils": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.18.0.tgz", "integrity": "sha512-KdVMdpTgDyK8FzdKO9SCpiibuy/kbv3pwgfXshTI6tEcQT1OOwj7BAksnzGC0rPz0UholwC+AgkqEl3EJX3M1A==", @@ -3651,8 +3654,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -3794,7 +3796,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -4041,6 +4042,16 @@ "react-i18next": ">= 13.5.0" } }, + "node_modules/next-seo": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/next-seo/-/next-seo-6.6.0.tgz", + "integrity": "sha512-0VSted/W6XNtgAtH3D+BZrMLLudqfm0D5DYNJRXHcDgan/1ZF1tDFIsWrmvQlYngALyphPfZ3ZdOqlKpKdvG6w==", + "peerDependencies": { + "next": "^8.1.1-canary.54 || >=9.0.0", + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -4544,24 +4555,44 @@ ] }, "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, "engines": { "node": ">=0.10.0" } }, + "node_modules/react-content-loader": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/react-content-loader/-/react-content-loader-7.0.2.tgz", + "integrity": "sha512-773S98JTyC8VB2nu7LXUhpHx8tZMieGxMcx3qTe7IkohT6Br7d9AXnIXs/wQ6IhlUdKQcw6JLKk1QKigYCWDRA==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dependencies": { - "scheduler": "^0.25.0" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" }, "peerDependencies": { - "react": "^19.0.0" + "react": "^18.2.0" } }, + "node_modules/react-ga4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", + "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" + }, "node_modules/react-i18next": { "version": "15.4.0", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.0.tgz", @@ -4584,9 +4615,9 @@ } }, "node_modules/react-icons": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz", - "integrity": "sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz", + "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==", "peerDependencies": { "react": "*" } @@ -4767,9 +4798,12 @@ } }, "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==" + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } }, "node_modules/semver": { "version": "7.6.3", diff --git a/package.json b/package.json index f6827dc..5d348dd 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,13 @@ "moment-hijri": "^3.0.0", "next": "15.1.0", "next-i18next": "^15.4.1", + "next-seo": "^6.6.0", "react": "18.2.0", + "react-content-loader": "^7.0.2", "react-dom": "18.2.0", + "react-ga4": "^2.1.0", "react-i18next": "^15.4.0", - "react-icons": "4.11.0" + "react-icons": "4.11.0" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/public/assets/images/noduas.svg b/public/assets/images/noduas.svg new file mode 100644 index 0000000..568100d --- /dev/null +++ b/public/assets/images/noduas.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/common/default-seo.tsx b/src/components/common/default-seo.tsx new file mode 100644 index 0000000..0e19397 --- /dev/null +++ b/src/components/common/default-seo.tsx @@ -0,0 +1,59 @@ +import { DefaultSeo as NextDefaultSeo } from "next-seo"; + +interface DefaultSeoProps { + title?: string; + description?: string; + keywords?: string; +} + +const DefaultSeo: React.FC = ({ + title = "Dua Site", + description = "A comprehensive collection of Duas", + keywords = "dua, islam, prayer, supplication", +}) => { + return ( + + ); +}; + +export default DefaultSeo; diff --git a/src/components/sidebar/list.tsx b/src/components/sidebar/categories.tsx similarity index 54% rename from src/components/sidebar/list.tsx rename to src/components/sidebar/categories.tsx index e1a6c5b..27013d9 100644 --- a/src/components/sidebar/list.tsx +++ b/src/components/sidebar/categories.tsx @@ -4,59 +4,30 @@ import http from "@/api/http"; import Image from "next/image"; import categoryImage from "../../../public/assets/images/Group 27009.svg"; -import DayContainer from "../../../public/assets/images/Vector.svg"; import Folder from "../../../public/assets/images/Inner Plugin Iframe.svg"; import NoData from "../../../public/assets/images/Frame 1000005074.webp"; import Audio from "../../../public/assets/images/Icon ionic-md-musical-notes.svg"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; -import colorizeVowels from "../utils/colorize-vowels"; -import { formatHijriDate } from "../utils/date-formaters"; -import { Category, Dua, ListProps } from "../utils/types"; +import { Category, Dua, CategoriesProps } from "../utils/types"; import { useTranslation } from "next-i18next"; // Importing the translation hook - -const List: React.FC = ({ tab, path, setPath, data, setData }) => { +import ShimmerLoader from "../ui/list-loading"; + +const Categories: React.FC = ({ + path, + setPath, + data, + setData, +}) => { const { t } = useTranslation("common"); // Initialize translation hook for "common" namespace const [loading, setLoading] = useState(false); - const [currentDhikr, setCurrentDhikr] = useState(""); - const [currentTranslation, setCurrentTranslation] = useState(""); const [error, setError] = useState(null); // State to hold error messages const router = useRouter(); - const today = new Date(); - const dayOfWeek = new Intl.DateTimeFormat("en-US", { - weekday: "long", - }).format(today); - - let locale: string; - - useEffect(() => { - locale = localStorage.getItem("locale") || "en"; - if (tab === "Today") { - setLoading(true); - setError(null); // Reset error state before starting the request - http - .get("web/mafatih-duas/dhikrs/?today=true") - .then((res) => { - const dhikrForToday = res.data.find( - (item: any) => item.day === dayOfWeek - ); - if (dhikrForToday) { - setCurrentDhikr(dhikrForToday.text); - setCurrentTranslation( - dhikrForToday.translation[locale] || dhikrForToday.translation.en - ); - } - setLoading(false); - }) - .catch((error) => { - setLoading(false); // Ensure loading state is false if request fails - setError(t("error.fetchDhikrData")); // Display a translated error message - console.error("Error fetching Dhikr data:", error); - }); - } - }, [tab, dayOfWeek, locale, t]); const openCategory = (category: Category) => { + if (category.children.length === 0) { + return; + } setData({ type: "children", data: category.children }); setPath((prev) => [ ...prev, @@ -65,44 +36,32 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => { }; const openDua = (dua: Dua) => { - const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); - const updatedDuas = [...lastReadDuas, dua]; - localStorage.setItem("last-read", JSON.stringify(updatedDuas)); - const slug = dua.title.toLowerCase().replaceAll(" ", "-"); - router.push(`/duas/${slug}-${dua.id}`); + const lastReadDuas = JSON.parse( + localStorage.getItem("last-read") || "[]" + ); + const updatedDuas = [...lastReadDuas, dua]; + localStorage.setItem("last-read", JSON.stringify(updatedDuas)); + const slug = dua.title.toLowerCase().replaceAll(" ", "-"); + router.push(`/duas/${slug}-${dua.id}`); }; useEffect(() => { setData({ type: null, data: [] }); setError(null); // Reset error state before starting the request - if (tab === "Categories") { - setLoading(true); - http - .get("web/mafatih-categories/") - .then((res) => { - setData({ type: "Categories", data: res.data }); - setPath([{ name: "", type: "Categories", data: res.data }]); - setLoading(false); - }) - .catch((error) => { - setLoading(false); - setError(t("error.fetchCategories")); // Display a translated error message - console.error("Error fetching categories:", error); - }); - } - - if (tab === "Today") { - http - .get("web/mafatih-duas/?today=true") - .then((res) => { - setData({ type: null, data: res.data.results }); - }) - .catch((error) => { - setError(t("error.fetchTodayDuas")); - console.error("Error fetching today's duas:", error); - }); - } - }, [setData, setPath, tab, t]); + setLoading(true); + http + .get("web/mafatih-categories/") + .then((res) => { + setData({ type: "Categories", data: res.data }); + setPath([{ name: "", type: "Categories", data: res.data }]); + setLoading(false); + }) + .catch((error) => { + setLoading(false); + setError(t("error.fetchCategories")); // Display a translated error message + console.error("Error fetching categories:", error); + }); + }, [setData, setPath, t]); useEffect(() => { if (data.data.length) { @@ -111,6 +70,7 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => { filteredData.forEach((category: Category) => { const hasDua = category.children.filter((item: any) => !item.title); + http .get(`web/mafatih-duas/?category=${category.id}`) .then((res) => { @@ -142,10 +102,12 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => { }); }); } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [path, t]); if (loading) { - return

{t("loading")}

; // Translating the loading message + return ; // Translating the loading message } if (error) { @@ -154,36 +116,6 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => { return (
- {tab === "Today" && ( -
-

{formatHijriDate(today)}

-
-
-
-
-
- Day Container -

- {t("todayDhikr")} -

{" "} - {/* Translating the title */} -
-
- -
- {colorizeVowels(currentDhikr)} -
- -

{currentTranslation}

-
-
-
- )} - {data?.data?.length ? ( data.data.map((item: Category | Dua) => { if (data.type === "Categories") { @@ -253,4 +185,4 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => { ); }; -export default List; +export default Categories; diff --git a/src/components/sidebar/famous.tsx b/src/components/sidebar/famous.tsx new file mode 100644 index 0000000..ddbeeb5 --- /dev/null +++ b/src/components/sidebar/famous.tsx @@ -0,0 +1,83 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use client"; + +import http from "@/api/http"; +import Image from "next/image"; +import NoData from "../../../public/assets/images/Frame 1000005074.webp"; +import Audio from "../../../public/assets/images/Icon ionic-md-musical-notes.svg"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import {Dua } from "../utils/types"; +import { useTranslation } from "next-i18next"; // Importing the translation hook +import ShimmerLoader from "../ui/list-loading"; + +const Famous: React.FC = () => { + const { t } = useTranslation("common"); // Initialize translation hook for "common" namespace + const [data, setData] = useState<{ type: string; data: Dua[] } | null>(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); // State to hold error messages + const router = useRouter(); + + const openDua = (dua: Dua) => { + const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); + const updatedDuas = [...lastReadDuas, dua]; + localStorage.setItem("last-read", JSON.stringify(updatedDuas)); + const slug = dua.title.toLowerCase().replaceAll(" ", "-"); + router.push(`/duas/${slug}-${dua.id}`); + }; + + useEffect(() => { + setLoading(true); + setError(null); // Reset error state before starting the request + + http + .get("web/mafatih-duas/?famous=true") + .then((res) => { + setLoading(false); + setData({ type: "Famous", data: res.data.results }); + }) + .catch((error) => { + setError(t("error.fetchTodayDuas")); + console.error("Error fetching today's duas:", error); + }); + }, [setData, t]); + + if (loading) { + return ; // Translating the loading message + } + + if (error) { + return

{error}

; // Displaying the error message + } + + return ( +
+ {data?.data?.length ? ( + data.data.map((dua: Dua) => { + return ( +
openDua(dua)} + > +
+

{dua.title}

+
+ {dua.not_synced && ( +
+ audio available +
+ )} +
+ ); + }) + ) : ( +
+ no data +
+ )} +
+ ); +}; + +export default Famous; diff --git a/src/components/sidebar/nearby.tsx b/src/components/sidebar/nearby.tsx new file mode 100644 index 0000000..a3166ba --- /dev/null +++ b/src/components/sidebar/nearby.tsx @@ -0,0 +1,137 @@ +import http from "@/api/http"; +import { FaLocationCrosshairs } from "react-icons/fa6"; +import { useState } from "react"; +import { Dua } from "../utils/types"; +import { useRouter } from "next/router"; +import Image from "next/image"; +import NoData from "../../../public/assets/images/noduas.svg"; +import Audio from "../../../public/assets/images/Icon ionic-md-musical-notes.svg"; +import ShimmerLoader from "../ui/list-loading"; + +const NearBy = () => { + const [data, setData] = useState<{ type: string; data: Dua[] } | null>(null); + const [loading, setLoading] = useState(false); // For managing loading state + const [error, setError] = useState(null); // For error handling + const [fetched, setFetched] = useState(false); + const router = useRouter(); + + const openDua = (dua: Dua) => { + const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); + const updatedDuas = [...lastReadDuas, dua]; + localStorage.setItem("last-read", JSON.stringify(updatedDuas)); + const slug = dua.title.toLowerCase().replaceAll(" ", "-"); + router.push(`/duas/${slug}-${dua.id}`); + }; + const onClick = () => { + // Check if geolocation is available + if (navigator.geolocation) { + setLoading(true); + setError(null); // Reset error state before attempting to get location + + navigator.geolocation.getCurrentPosition( + (position) => { + const { latitude, longitude } = position.coords; + + // Make the API request with the user's latitude and longitude + http + .get(`web/mafatih-duas/?lat=${latitude}&lon=${longitude}`) + .then((res) => { + console.log("Nearby Duas:", res.data); // Handle the response here + setData({ type: "Famous", data: res.data.results }); + setFetched(true); + }) + .catch((err) => { + setError("Failed to fetch Duas. Please try again later."); + console.error("Error fetching Duas:", err); + }) + .finally(() => { + setLoading(false); + }); + }, + (error) => { + // Handle errors related to geolocation + setLoading(false); + setError( + "Failed to get your location. Please allow location access." + ); + console.error("Geolocation Error:", error); + } + ); + } else { + setError("Geolocation is not supported by your browser."); + } + }; + + return ( +
+ {data?.data?.length ? ( + <> +
+

+ Discover Location-Based Du'as +

+ +
+ {data.data.map((dua: Dua) => { + return ( +
openDua(dua)} + > +
+

{dua.title}

+
+ {dua.not_synced && ( +
+ audio available +
+ )} +
+ ); + })} + + ) : loading ? : fetched ? ( + <> +
+

+ Discover Location-Based Du'as +

+ +
+
+ no data +

No Du'as available for this location

+
+ + ) : ( +
+

+ Discover Location-Based Du'as +

+ +
+ )} +
+ ); +}; + +export default NearBy; diff --git a/src/components/sidebar/tabs.tsx b/src/components/sidebar/tabs.tsx index ffd09ed..2079d4b 100644 --- a/src/components/sidebar/tabs.tsx +++ b/src/components/sidebar/tabs.tsx @@ -3,6 +3,11 @@ import List from "./list"; import { FaArrowLeft } from "react-icons/fa6"; import { DataState, Path } from "@/components/utils/types"; // Import types from types.ts import { useTranslation } from "next-i18next"; // Importing useTranslation +import { tap } from "node:test/reporters"; +import Categories from "./categories"; +import Famous from "./famous"; +import Today from "./today"; +import NearBy from "./nearby"; const tabs = [ { name: "Categories" }, @@ -100,13 +105,25 @@ const Tabs = () => {
)} - + } + { + selected.name === "Famous" && + } + { + selected.name === "near_by" && + } + { + selected.name === "Today" && + } + {/* + /> */} ); }; diff --git a/src/components/sidebar/today.tsx b/src/components/sidebar/today.tsx new file mode 100644 index 0000000..75ec2e2 --- /dev/null +++ b/src/components/sidebar/today.tsx @@ -0,0 +1,146 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use client"; + +import http from "@/api/http"; +import Image from "next/image"; +import DayContainer from "../../../public/assets/images/Vector.svg"; +import NoData from "../../../public/assets/images/Frame 1000005074.webp"; +import Audio from "../../../public/assets/images/Icon ionic-md-musical-notes.svg"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import colorizeVowels from "../utils/colorize-vowels"; +import { formatHijriDate } from "../utils/date-formaters"; +import { Dua } from "../utils/types"; +import { useTranslation } from "next-i18next"; // Importing the translation hook +import ShimmerLoader from "../ui/list-loading"; + +const Today: React.FC = () => { + const { t } = useTranslation("common"); // Initialize translation hook for "common" namespace + const [data, setData] = useState<{ type: string; data: Dua[] } | null>(null); + const [loading, setLoading] = useState(false); + const [currentDhikr, setCurrentDhikr] = useState(""); + const [currentTranslation, setCurrentTranslation] = useState(""); + const [error, setError] = useState(null); // State to hold error messages + const router = useRouter(); + const today = new Date(); + const dayOfWeek = new Intl.DateTimeFormat("en-US", { + weekday: "long", + }).format(today); + + let locale: string; + + useEffect(() => { + locale = localStorage.getItem("locale") || "en"; + setLoading(true); + setError(null); // Reset error state before starting the request + http + .get("web/mafatih-duas/dhikrs/?today=true") + .then((res) => { + const dhikrForToday = res.data.find( + (item: any) => item.day === dayOfWeek + ); + if (dhikrForToday) { + setCurrentDhikr(dhikrForToday.text); + setCurrentTranslation( + dhikrForToday.translation[locale] || dhikrForToday.translation.en + ); + } + setLoading(false); + }) + .catch((error) => { + setLoading(false); // Ensure loading state is false if request fails + setError(t("error.fetchDhikrData")); // Display a translated error message + console.error("Error fetching Dhikr data:", error); + }); + }, [dayOfWeek, locale, t]); + + const openDua = (dua: Dua) => { + const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); + const updatedDuas = [...lastReadDuas, dua]; + localStorage.setItem("last-read", JSON.stringify(updatedDuas)); + const slug = dua.title.toLowerCase().replaceAll(" ", "-"); + router.push(`/duas/${slug}-${dua.id}`); + }; + + useEffect(() => { + setData({ type: null, data: [] }); + setError(null); // Reset error state before starting the request + + http + .get("web/mafatih-duas/?today=true") + .then((res) => { + setData({ type: "Today", data: res.data.results }); + }) + .catch((error) => { + setError(t("error.fetchTodayDuas")); + console.error("Error fetching today's duas:", error); + }); + }, []); + + if (loading) { + return ; // Translating the loading message + } + + if (error) { + return

{error}

; // Displaying the error message + } + + return ( +
+
+

{formatHijriDate(today)}

+
+
+
+
+
+ Day Container +

+ {t("todayDhikr")} +

{" "} + {/* Translating the title */} +
+
+ +
+ {colorizeVowels(currentDhikr)} +
+ +

{currentTranslation}

+
+
+
+ + {data?.data?.length ? ( + data.data.map((dua: Dua) => { + return ( +
openDua(dua)} + > +
+

{dua.title}

+
+ {dua.not_synced && ( +
+ audio available +
+ )} +
+ ); + }) + ) : ( +
+ no data +
+ )} +
+ ); +}; + +export default Today; diff --git a/src/components/ui/list-loading.tsx b/src/components/ui/list-loading.tsx new file mode 100644 index 0000000..f9fdb7e --- /dev/null +++ b/src/components/ui/list-loading.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import ContentLoader from 'react-content-loader'; + +const ShimmerLoader = () => { + return ( + + {Array.from({ length: 10 }).map((_, index) => ( + + ))} + + ); +}; + +export default ShimmerLoader; diff --git a/src/components/utils/ga.js b/src/components/utils/ga.js new file mode 100644 index 0000000..7739f10 --- /dev/null +++ b/src/components/utils/ga.js @@ -0,0 +1,13 @@ +// utils/ga.js + +import ReactGA from "react-ga4"; + +// Initialize Google Analytics with the provided Measurement ID +export const initGA = (measurementId) => { + ReactGA.initialize(measurementId); +}; + +// Log page view in Google Analytics +export const logPageView = () => { + ReactGA.send("pageview"); +}; diff --git a/src/components/utils/types.ts b/src/components/utils/types.ts index 7bf8b2e..c2ff3cb 100644 --- a/src/components/utils/types.ts +++ b/src/components/utils/types.ts @@ -75,3 +75,13 @@ export interface ListProps { data: any; // Adjust this type as needed setData: React.Dispatch>; // Adjust this type as needed } +export interface CategoriesProps { + path: Path[]; // Expecting an array of Path objects + setPath: React.Dispatch>; // A setter for the Path[] array + data: any; // Adjust this type as needed + setData: React.Dispatch>; // Adjust this type as needed + } +export interface FamousProps { + data: any; // Adjust this type as needed + setData: React.Dispatch>; // Adjust this type as needed + } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index d598c9f..2f8233d 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,3 +1,4 @@ +import DefaultSeo from "@/components/common/default-seo"; import { AudioProvider } from "@/components/context/audio-conext"; import { FontSettingsProvider } from "@/components/context/font-setting-context"; import { UIProvider } from "@/components/context/ui.context"; @@ -15,21 +16,23 @@ import type { AppProps } from "next/app"; const rtlLanguages = ["ar", "ur", "fa", "ks", "tg", "bn"]; function App({ Component, pageProps }: AppProps) { - - const isRtl = rtlLanguages.includes(pageProps?._nextI18Next?.initialLocale) || false; + const isRtl = + rtlLanguages.includes(pageProps?._nextI18Next?.initialLocale) || false; return ( -
{/* Apply rtl class if the language is RTL */} + +
+ {" "} + {/* Apply rtl class if the language is RTL */}
-
+
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 3093cc2..a84146e 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -2,7 +2,7 @@ import { Html, Head, Main, NextScript } from "next/document"; export default function Document() { return ( - +
diff --git a/src/pages/duas/[slug].tsx b/src/pages/duas/[slug].tsx index 503a2bd..b27d615 100644 --- a/src/pages/duas/[slug].tsx +++ b/src/pages/duas/[slug].tsx @@ -14,6 +14,7 @@ import { useUI } from "@/components/context/ui.context"; import { useFontSettingsContext } from "@/components/context/font-setting-context"; import { useAudio } from "@/components/context/audio-conext"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; +import { DefaultSeo } from "next-seo"; // Define the Dua interface interface Dua { @@ -51,53 +52,53 @@ const DuaComponent: React.FC = ({ // Use the shared context for font settings const { fontSettings } = useFontSettingsContext(); - + // State hooks const [duaParts, setDuaParts] = useState([]); const [recitingPart, setRecitingPart] = useState(null); const [loading, setLoading] = useState(false); // Fetch Dua parts and audio -// Use useCallback to memoize fetchData so it doesn’t change between renders -const fetchData = useCallback(async (nextPage) => { - if (!slug || fetching) return; // Prevent fetching if data is already being fetched - - const id = slug.split("-").pop(); - if (!id) return; - - setFetching(true); // Set fetching to true when data starts fetching - - // Reset the duaParts state when the slug changes - const offset = nextPage ? duaParts.length : 0; - - try { - setLoading(true); - - // Fetch the data - const duaResponse = await http.get( - `web/mafatih-duas/${id}/parts/?offset=${offset}` - ); - - // Append the new results to the existing duaParts - setDuaParts((prev) => [...prev, ...duaResponse.data.results]); - } catch (error) { - console.error("Error fetching Dua parts:", error); - } finally { - setLoading(false); - setFetching(false); // Reset fetching state after fetching is done - } - - getAudio(id); -}, [slug, fetching, duaParts, getAudio]); // Dependencies for fetchData - - + // Use useCallback to memoize fetchData so it doesn’t change between renders + const fetchData = useCallback( + async (nextPage) => { + if (!slug || fetching) return; // Prevent fetching if data is already being fetched + + const id = slug.split("-").pop(); + if (!id) return; + + setFetching(true); // Set fetching to true when data starts fetching + + // Reset the duaParts state when the slug changes + const offset = nextPage ? duaParts.length : 0; + + try { + setLoading(true); + + // Fetch the data + const duaResponse = await http.get( + `web/mafatih-duas/${id}/parts/?offset=${offset}` + ); + + // Append the new results to the existing duaParts + setDuaParts((prev) => [...prev, ...duaResponse.data.results]); + } catch (error) { + console.error("Error fetching Dua parts:", error); + } finally { + setLoading(false); + setFetching(false); // Reset fetching state after fetching is done + } -// Use the memoized fetchData in the effect hook -useEffect(() => { - setDuaParts([]); // Reset Dua parts when slug changes - fetchData(false); -}, [slug]); // Only slug changes should trigger this effect + getAudio(id); + }, + [slug, fetching, duaParts, getAudio] + ); // Dependencies for fetchData + // Use the memoized fetchData in the effect hook + useEffect(() => { + setDuaParts([]); // Reset Dua parts when slug changes + fetchData(false); + }, [slug]); // Only slug changes should trigger this effect // Play audio from a specific part const playAudio = useCallback( @@ -218,123 +219,144 @@ useEffect(() => { } }, [recitingPart, duaParts]); - const handleScroll = useCallback((e: React.UIEvent) => { - const target = e.currentTarget; - const isBottom = target.scrollHeight - target.scrollTop === target.clientHeight; + const handleScroll = useCallback( + (e: React.UIEvent) => { + const target = e.currentTarget; + const isBottom = + target.scrollHeight - target.scrollTop === target.clientHeight; - if (isBottom && !fetching) { // Only fetch when not already fetching - fetchData(true); - } + if (isBottom && !fetching) { + // Only fetch when not already fetching + fetchData(true); + } - setScrollPosition(target.scrollTop); // Update scroll position - }, [fetching]); + setScrollPosition(target.scrollTop); // Update scroll position + }, + [fetching] + ); if (!slug) { 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 keywords = "dua, islam, prayer, supplication, dua parts"; // Keywords for SEO return ( -
-
- -

{processSlug(slug)}

-
+ <> + +
+
- +

{processSlug(slug)}

+
+ + +
-
-
- {duaParts.map((item, index) => ( -
{ - partRefs.current[index] = el; - }} - className="p-1 rounded-3xl my-4" - style={{ - background: - "linear-gradient(to right, rgba(254, 127, 120, 0.11), rgba(234, 109, 86, 0.3))", - borderRadius: "1.5rem", - padding: "1px", - }} - > -
- {item.text && ( -
- {colorizeVowels(item.text)} -
- )} - {item.local_alpha && ( -

- {item.local_alpha} -

- )} - {item.translation && ( -

- {item.translation} -

- )} - {item.description && ( -

- {item.description} -

- )} - - {audio && !!Object.keys(audio)?.length && ( - - )} +
+ {duaParts.map((item, index) => ( +
{ + partRefs.current[index] = el; + }} + className="p-1 rounded-3xl my-4" + style={{ + background: + "linear-gradient(to right, rgba(254, 127, 120, 0.11), rgba(234, 109, 86, 0.3))", + borderRadius: "1.5rem", + padding: "1px", + }} + > +
+ {item.text && ( +
+ {colorizeVowels(item.text)} +
+ )} + {item.local_alpha && ( +

+ {item.local_alpha} +

+ )} + {item.translation && ( +

+ {item.translation} +

+ )} + {item.description && ( +

+ {item.description} +

+ )} + + {audio && !!Object.keys(audio)?.length && ( + + )} +
-
- ))} -
+ ))} +
- {audio && Object.keys(audio).length > 0 && ( -
+ {audio && Object.keys(audio).length > 0 && ( +
+ ); }; diff --git a/tailwind.config.ts b/tailwind.config.ts index cb08338..964c272 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -8,6 +8,15 @@ export default { ], theme: { extend: { + keyframes: { + shimmer: { + '0%': { backgroundPosition: '-200% 0' }, + '100%': { backgroundPosition: '200% 0' }, + }, + }, + animation: { + shimmer: 'shimmer 1.5s infinite linear', // Add shimmer animation here + }, boxShadow: { 'header': '2px -10px 129px 35px #F4846F', 'inner-header': '2px -20px 15px 30px rgba(244,132,111,1);', @@ -26,7 +35,7 @@ export default { addUtilities( { '.text-rendering-optimize-speed': { - 'text-rendering': 'geometricPrecision', + 'text-rendering': 'optimizeSpeed', }, '.no-ligatures': { 'font-variant-ligatures': 'none', // Add this utility