Browse Source

feat: integrate next-sitemap,added search for desktop, and update header for donate modal

master
sina_sajjadi 2 weeks ago
parent
commit
30db6203ee
  1. 12
      next-sitemap.config.js
  2. 2
      next.config.ts
  3. 56
      package-lock.json
  4. 1
      package.json
  5. 67
      public/assets/images/Frame 1116606661.svg
  6. 12
      public/locales/en/common.json
  7. 64
      src/components/common/default-seo.tsx
  8. 26
      src/components/context/search-context.tsx
  9. 47
      src/components/layout/header.tsx
  10. 123
      src/components/modals/donate-modal.tsx
  11. 8
      src/components/modals/modal-manager.tsx
  12. 114
      src/components/sidebar/search.tsx
  13. 120
      src/components/sidebar/tabs.tsx
  14. 155
      src/components/ui/search-duas.tsx
  15. 3
      src/pages/_app.tsx
  16. 9
      src/pages/duas/[slug].tsx

12
next-sitemap.config.js

@ -0,0 +1,12 @@
const config = {
siteUrl: 'https://www.example.com',
generateRobotsTxt: true,
robotsTxtOptions: {
policies: [
{ userAgent: '*', allow: '/', disallow: ['/api/', '/admin/', '/private/'] },
],
},
};
module.exports = config;

2
next.config.ts

@ -1,7 +1,7 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
reactStrictMode: true,
reactStrictMode: false,
images: { images: {
domains: ["habibapp.com"], // Add the domain for image hosting domains: ["habibapp.com"], // Add the domain for image hosting
}, },

56
package-lock.json

@ -20,6 +20,7 @@
"next": "15.1.0", "next": "15.1.0",
"next-i18next": "^15.4.1", "next-i18next": "^15.4.1",
"next-seo": "^6.6.0", "next-seo": "^6.6.0",
"next-sitemap": "^4.2.3",
"react": "18.2.0", "react": "18.2.0",
"react-content-loader": "^7.0.2", "react-content-loader": "^7.0.2",
"react-dom": "18.2.0", "react-dom": "18.2.0",
@ -64,6 +65,11 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@corex/deepmerge": {
"version": "4.0.43",
"resolved": "https://registry.npmjs.org/@corex/deepmerge/-/deepmerge-4.0.43.tgz",
"integrity": "sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ=="
},
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
@ -797,7 +803,6 @@
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"dependencies": { "dependencies": {
"@nodelib/fs.stat": "2.0.5", "@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9" "run-parallel": "^1.1.9"
@ -810,7 +815,6 @@
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
} }
@ -819,7 +823,6 @@
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"dependencies": { "dependencies": {
"@nodelib/fs.scandir": "2.1.5", "@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0" "fastq": "^1.6.0"
@ -1543,7 +1546,6 @@
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.1.1" "fill-range": "^7.1.1"
}, },
@ -2629,7 +2631,6 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
"integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
"dev": true,
"dependencies": { "dependencies": {
"@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3", "@nodelib/fs.walk": "^1.2.3",
@ -2645,7 +2646,6 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
}, },
@ -2669,7 +2669,6 @@
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"dev": true,
"dependencies": { "dependencies": {
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
@ -2690,7 +2689,6 @@
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -3360,7 +3358,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -3408,7 +3405,6 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": { "dependencies": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
}, },
@ -3444,7 +3440,6 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
} }
@ -3822,7 +3817,6 @@
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
} }
@ -3831,7 +3825,6 @@
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": { "dependencies": {
"braces": "^3.0.3", "braces": "^3.0.3",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
@ -3875,7 +3868,6 @@
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
@ -4052,6 +4044,37 @@
"react-dom": ">=16.0.0" "react-dom": ">=16.0.0"
} }
}, },
"node_modules/next-sitemap": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.2.3.tgz",
"integrity": "sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==",
"funding": [
{
"url": "https://github.com/iamvishnusankar/next-sitemap.git"
}
],
"dependencies": {
"@corex/deepmerge": "^4.0.43",
"@next/env": "^13.4.3",
"fast-glob": "^3.2.12",
"minimist": "^1.2.8"
},
"bin": {
"next-sitemap": "bin/next-sitemap.mjs",
"next-sitemap-cjs": "bin/next-sitemap.cjs"
},
"engines": {
"node": ">=14.18"
},
"peerDependencies": {
"next": "*"
}
},
"node_modules/next-sitemap/node_modules/@next/env": {
"version": "13.5.8",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.8.tgz",
"integrity": "sha512-YmiG58BqyZ2FjrF2+5uZExL2BrLr8RTQzLXNDJ8pJr0O+rPlOeDPXp1p1/4OrR3avDidzZo3D8QO2cuDv1KCkw=="
},
"node_modules/next/node_modules/postcss": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@ -4322,7 +4345,6 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
}, },
@ -4538,7 +4560,6 @@
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -4732,7 +4753,6 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true,
"engines": { "engines": {
"iojs": ">=1.0.0", "iojs": ">=1.0.0",
"node": ">=0.10.0" "node": ">=0.10.0"
@ -4742,7 +4762,6 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -5414,7 +5433,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },

1
package.json

@ -21,6 +21,7 @@
"next": "15.1.0", "next": "15.1.0",
"next-i18next": "^15.4.1", "next-i18next": "^15.4.1",
"next-seo": "^6.6.0", "next-seo": "^6.6.0",
"next-sitemap": "^4.2.3",
"react": "18.2.0", "react": "18.2.0",
"react-content-loader": "^7.0.2", "react-content-loader": "^7.0.2",
"react-dom": "18.2.0", "react-dom": "18.2.0",

67
public/assets/images/Frame 1116606661.svg

@ -0,0 +1,67 @@
<svg width="201" height="57" viewBox="0 0 201 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_10454_8052)">
<rect x="16.1797" y="15.375" width="38.25" height="26.25" rx="5.625" fill="white" stroke="#D9D9D9" stroke-width="0.75"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.7604 24.1596L34.7793 24.5889V22.9672L36.7604 22.5459V24.1596ZM28.7092 23.468L26.7833 23.8814L26.7754 30.2487C26.7754 31.4252 27.6515 32.2917 28.8197 32.2917C29.4669 32.2917 29.9404 32.1725 30.2009 32.0294V30.4157C29.9483 30.519 28.7013 30.8847 28.7013 29.7082V26.8862H30.2009V25.193H28.7013L28.7092 23.468ZM39.341 25.6779C39.6172 25.4235 40.1145 25.0579 40.888 25.0579C42.2693 25.0579 43.5716 26.3139 43.5795 28.6191C43.5795 31.1391 42.2929 32.2838 40.8801 32.2838C40.1855 32.2838 39.7672 31.9897 39.4831 31.783L39.4752 34.0326L37.502 34.4539V25.1851H39.2384L39.341 25.6779ZM39.4845 30.1613C39.6739 30.368 39.9501 30.5349 40.4158 30.5349C41.142 30.5349 41.6313 29.74 41.6313 28.6748C41.6313 27.6334 41.1341 26.8226 40.4158 26.8226C39.9659 26.8226 39.6818 26.9816 39.4766 27.2121L39.4845 30.1613ZM49.796 28.6986C49.796 26.6636 48.8173 25.0579 46.9467 25.0579C45.0682 25.0579 43.9316 26.6636 43.9316 28.6827C43.9316 31.0755 45.2734 32.2838 47.1993 32.2838C48.1385 32.2838 48.8489 32.0691 49.3856 31.7671V30.1772C48.8489 30.4475 48.2333 30.6144 47.4519 30.6144C46.6863 30.6144 46.0075 30.3441 45.9206 29.4061H49.7803C49.7803 29.3623 49.7831 29.2629 49.7864 29.1482L49.7864 29.1481L49.7864 29.148V29.1479L49.7864 29.1478C49.7908 28.9921 49.796 28.8085 49.796 28.6986ZM45.9004 27.9435C45.9004 27.0452 46.445 26.6716 46.9423 26.6716C47.4237 26.6716 47.9368 27.0452 47.9368 27.9435H45.9004ZM24.0467 26.7829C23.6284 26.7829 23.3758 26.9021 23.3758 27.2121C23.3758 27.5506 23.8105 27.6995 24.3498 27.8843C25.2289 28.1854 26.386 28.5818 26.3909 30.05C26.3909 31.473 25.2622 32.2917 23.6205 32.2917C22.9417 32.2917 22.1998 32.1566 21.4657 31.8386V29.9467C22.1287 30.3124 22.9654 30.5826 23.6205 30.5826C24.0625 30.5826 24.3782 30.4634 24.3782 30.0977C24.3782 29.7228 23.907 29.5514 23.3381 29.3445C22.4718 29.0294 21.3789 28.6319 21.3789 27.3075C21.3789 25.9005 22.4444 25.0579 24.0467 25.0579C24.7018 25.0579 25.349 25.1612 26.0041 25.4236V27.2916C25.4043 26.9657 24.6466 26.7829 24.0467 26.7829ZM32.6612 25.7813L32.5349 25.193H30.8301V32.1486H32.8033V27.4347C33.269 26.8226 34.0583 26.9339 34.3029 27.0213V25.193C34.0504 25.0976 33.1269 24.9227 32.6612 25.7813ZM34.7793 25.193H36.7604V32.1486H34.7793V25.193Z" fill="#6461FC"/>
</g>
<g filter="url(#filter1_d_10454_8052)">
<rect x="62.5565" y="16.5365" width="35.1992" height="23.9271" rx="3.71822" fill="white" stroke="#D9D9D9" stroke-width="0.495763"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M73.1531 32.8334H70.9907L69.3692 26.6607C69.2922 26.3767 69.1288 26.1257 68.8884 26.0074C68.2885 25.7101 67.6274 25.4734 66.9062 25.3541V25.1165H70.3897C70.8705 25.1165 71.2311 25.4734 71.2912 25.888L72.1325 30.3407L74.2939 25.1165H76.3962L73.1531 32.8334ZM77.6047 32.8334H75.5625L77.2441 25.1165H79.2864L77.6047 32.8334ZM81.9168 27.2543C81.9769 26.8387 82.3375 26.601 82.7581 26.601C83.4192 26.5413 84.1393 26.6607 84.7403 26.957L85.1008 25.2955C84.4999 25.0578 83.8388 24.9385 83.2389 24.9385C81.2568 24.9385 79.8145 26.0074 79.8145 27.4909C79.8145 28.6195 80.8361 29.2121 81.5573 29.5691C82.3375 29.925 82.6379 30.1627 82.5778 30.5186C82.5778 31.0526 81.9769 31.2902 81.377 31.2902C80.6558 31.2902 79.9346 31.1122 79.2746 30.8149L78.9141 32.4775C79.6352 32.7737 80.4154 32.8931 81.1366 32.8931C83.3591 32.9517 84.7403 31.8838 84.7403 30.281C84.7403 28.2625 81.9168 28.1442 81.9168 27.2543ZM91.8949 32.8334L90.2733 25.1165H88.5316C88.171 25.1165 87.8104 25.3541 87.6902 25.7101L84.6875 32.8334H86.7898L87.2094 31.7059H89.7925L90.0329 32.8334H91.8949ZM88.8317 27.1946L89.4316 30.103H87.75L88.8317 27.1946Z" fill="#172B85"/>
</g>
<g filter="url(#filter2_d_10454_8052)">
<rect x="105.875" y="15.375" width="38.25" height="26.25" rx="5.625" fill="white" stroke="#D9D9D9" stroke-width="0.75"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M125.276 34.3028C123.949 35.4589 122.227 36.1567 120.346 36.1567C116.146 36.1567 112.742 32.6798 112.742 28.3909C112.742 24.1019 116.146 20.625 120.346 20.625C122.227 20.625 123.949 21.3228 125.276 22.4789C126.604 21.3228 128.326 20.625 130.207 20.625C134.407 20.625 137.811 24.1019 137.811 28.3909C137.811 32.6798 134.407 36.1567 130.207 36.1567C128.326 36.1567 126.604 35.4589 125.276 34.3028Z" fill="#ED0006"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M125.277 34.3028C126.913 32.8784 127.95 30.7583 127.95 28.3909C127.95 26.0234 126.913 23.9033 125.277 22.4789C126.605 21.3228 128.327 20.625 130.208 20.625C134.407 20.625 137.812 24.1019 137.812 28.3909C137.812 32.6798 134.407 36.1567 130.208 36.1567C128.327 36.1567 126.605 35.4589 125.277 34.3028Z" fill="#F9A000"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M125.278 34.3029C126.914 32.8785 127.951 30.7584 127.951 28.3909C127.951 26.0235 126.914 23.9034 125.278 22.479C123.643 23.9034 122.605 26.0235 122.605 28.3909C122.605 30.7584 123.643 32.8785 125.278 34.3029Z" fill="#FF5E00"/>
</g>
<g filter="url(#filter3_d_10454_8052)">
<rect x="152.375" y="15.375" width="38.25" height="26.25" rx="5.625" fill="white" stroke="#D9D9D9" stroke-width="0.75"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M163.026 32.146H161.292C161.174 32.146 161.073 32.233 161.054 32.3512L160.353 36.8389C160.339 36.9275 160.407 37.0073 160.496 37.0073H161.324C161.442 37.0073 161.543 36.9203 161.562 36.8018L161.751 35.5915C161.769 35.4729 161.87 35.386 161.989 35.386H162.537C163.679 35.386 164.338 34.8281 164.51 33.7227C164.588 33.239 164.514 32.859 164.289 32.5928C164.043 32.3007 163.606 32.146 163.026 32.146ZM163.226 33.7851C163.131 34.4131 162.656 34.4131 162.196 34.4131H161.935L162.118 33.2402C162.129 33.1694 162.19 33.1172 162.261 33.1172H162.381C162.694 33.1172 162.989 33.1172 163.142 33.2973C163.233 33.4048 163.261 33.5644 163.226 33.7851ZM168.207 33.7649H167.377C167.307 33.7649 167.245 33.8172 167.235 33.8881L167.198 34.1224L167.14 34.0375C166.96 33.7742 166.559 33.6862 166.159 33.6862C165.242 33.6862 164.459 34.3876 164.306 35.3716C164.227 35.8623 164.34 36.3316 164.615 36.6589C164.868 36.9598 165.23 37.0852 165.661 37.0852C166.4 37.0852 166.81 36.6054 166.81 36.6054L166.773 36.8383C166.759 36.9274 166.827 37.0072 166.915 37.0072H167.663C167.782 37.0072 167.882 36.9202 167.901 36.8017L168.35 33.9333C168.364 33.8451 168.296 33.7649 168.207 33.7649ZM167.05 35.3961C166.97 35.8748 166.594 36.1962 166.114 36.1962C165.873 36.1962 165.68 36.1182 165.556 35.9703C165.434 35.8235 165.387 35.6143 165.426 35.3815C165.501 34.9068 165.884 34.5749 166.356 34.5749C166.592 34.5749 166.784 34.6539 166.91 34.8031C167.036 34.9539 167.087 35.1642 167.05 35.3961ZM171.794 33.7648H172.628C172.745 33.7648 172.813 33.8971 172.747 33.9938L169.973 38.037C169.928 38.1025 169.854 38.1414 169.775 38.1414H168.941C168.824 38.1414 168.756 38.0081 168.824 37.9111L169.687 36.68L168.769 33.9577C168.737 33.8631 168.806 33.7648 168.906 33.7648H169.725C169.832 33.7648 169.926 33.8354 169.957 33.9383L170.444 35.5825L171.595 33.8715C171.64 33.8047 171.715 33.7648 171.794 33.7648Z" fill="#253B80"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M180.837 36.8392L181.549 32.2694C181.56 32.1985 181.621 32.1462 181.691 32.146H182.492C182.581 32.146 182.648 32.226 182.635 32.3146L181.933 36.8021C181.915 36.9205 181.814 37.0075 181.695 37.0075H180.98C180.891 37.0075 180.823 36.9277 180.837 36.8392ZM175.389 32.1462H173.655C173.537 32.1462 173.436 32.2333 173.418 32.3515L172.717 36.8392C172.703 36.9277 172.771 37.0075 172.859 37.0075H173.749C173.831 37.0075 173.902 36.9467 173.915 36.8638L174.114 35.5917C174.132 35.4732 174.233 35.3862 174.352 35.3862H174.9C176.042 35.3862 176.701 34.8284 176.873 33.7229C176.951 33.2393 176.876 32.8592 176.652 32.593C176.406 32.3009 175.969 32.1462 175.389 32.1462ZM175.589 33.7853C175.495 34.4133 175.019 34.4133 174.56 34.4133H174.298L174.482 33.2404C174.493 33.1696 174.553 33.1174 174.624 33.1174H174.744C175.057 33.1174 175.353 33.1174 175.505 33.2975C175.596 33.4051 175.624 33.5647 175.589 33.7853ZM180.57 33.7652H179.741C179.669 33.7652 179.609 33.8174 179.598 33.8883L179.562 34.1227L179.503 34.0378C179.324 33.7744 178.923 33.6864 178.523 33.6864C177.606 33.6864 176.823 34.3878 176.67 35.3718C176.591 35.8625 176.703 36.3318 176.979 36.6592C177.232 36.9601 177.594 37.0855 178.025 37.0855C178.764 37.0855 179.173 36.6056 179.173 36.6056L179.136 36.8386C179.123 36.9276 179.19 37.0074 179.279 37.0074H180.027C180.145 37.0074 180.246 36.9204 180.265 36.802L180.714 33.9336C180.727 33.8453 180.659 33.7652 180.57 33.7652ZM179.413 35.3963C179.334 35.8751 178.957 36.1965 178.477 36.1965C178.236 36.1965 178.043 36.1184 177.919 35.9705C177.797 35.8237 177.751 35.6146 177.789 35.3817C177.864 34.907 178.247 34.5752 178.719 34.5752C178.955 34.5752 179.147 34.6542 179.273 34.8034C179.4 34.9541 179.45 35.1644 179.413 35.3963Z" fill="#179BD7"/>
<path d="M169.435 30.5405L169.648 29.174L169.173 29.1628H166.906L168.482 19.0785C168.486 19.0481 168.502 19.0197 168.525 18.9995C168.549 18.9794 168.579 18.9683 168.609 18.9683H172.431C173.7 18.9683 174.576 19.2348 175.033 19.761C175.247 20.0078 175.384 20.2658 175.45 20.5496C175.519 20.8475 175.521 21.2033 175.453 21.6373L175.448 21.669V21.9471L175.662 22.0697C175.843 22.1664 175.986 22.277 176.096 22.4037C176.28 22.6148 176.398 22.883 176.448 23.201C176.5 23.5281 176.483 23.9172 176.398 24.3578C176.3 24.8647 176.142 25.3061 175.929 25.6673C175.732 26.0001 175.482 26.2761 175.185 26.4901C174.902 26.6933 174.565 26.8476 174.184 26.9463C173.814 27.0433 173.394 27.0923 172.932 27.0923H172.634C172.422 27.0923 172.215 27.1697 172.053 27.3083C171.89 27.4499 171.783 27.6431 171.75 27.8546L171.727 27.9777L171.351 30.3862L171.334 30.4747C171.329 30.5028 171.322 30.5167 171.31 30.5262C171.3 30.5347 171.285 30.5405 171.271 30.5405H169.435Z" fill="#253B80"/>
<path d="M175.87 21.7012C175.858 21.7748 175.845 21.8501 175.831 21.9274C175.327 24.5401 173.602 25.4427 171.4 25.4427H170.279C170.009 25.4427 169.782 25.6402 169.74 25.9084L169.166 29.5846L169.004 30.6266C168.976 30.8027 169.111 30.9615 169.287 30.9615H171.276C171.511 30.9615 171.711 30.7887 171.748 30.5542L171.768 30.4522L172.142 28.0529L172.166 27.9214C172.203 27.6861 172.403 27.5133 172.639 27.5133H172.936C174.863 27.5133 176.372 26.7234 176.813 24.4377C176.997 23.4829 176.901 22.6856 176.414 22.1249C176.267 21.9558 176.084 21.8155 175.87 21.7012Z" fill="#179BD7"/>
<path d="M175.341 21.4893C175.264 21.4667 175.184 21.4461 175.103 21.4276C175.021 21.4095 174.937 21.3934 174.85 21.3795C174.548 21.3301 174.217 21.3066 173.862 21.3066H170.866C170.792 21.3066 170.722 21.3235 170.66 21.3539C170.521 21.421 170.419 21.5531 170.394 21.7147L169.757 25.7899L169.738 25.9088C169.78 25.6406 170.007 25.4431 170.277 25.4431H171.398C173.6 25.4431 175.325 24.5401 175.829 21.9278C175.844 21.8505 175.856 21.7752 175.868 21.7016C175.74 21.6333 175.602 21.5749 175.453 21.5251C175.417 21.5127 175.379 21.5008 175.341 21.4893Z" fill="#222D65"/>
<path d="M170.398 21.7146C170.423 21.5529 170.525 21.4209 170.663 21.3542C170.727 21.3238 170.796 21.3069 170.87 21.3069H173.866C174.221 21.3069 174.552 21.3304 174.854 21.3798C174.941 21.3937 175.025 21.4098 175.107 21.4279C175.188 21.4464 175.267 21.467 175.345 21.4896C175.383 21.5011 175.421 21.513 175.458 21.525C175.606 21.5747 175.745 21.6336 175.872 21.7015C176.022 20.7359 175.871 20.0785 175.354 19.4833C174.784 18.8279 173.755 18.5474 172.438 18.5474H168.616C168.347 18.5474 168.118 18.7448 168.076 19.0135L166.485 29.2014C166.453 29.403 166.607 29.5849 166.808 29.5849H169.168L169.761 25.7898L170.398 21.7146Z" fill="#253B80"/>
</g>
<defs>
<filter id="filter0_d_10454_8052" x="0.804688" y="0" width="69" height="57" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="7.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_10454_8052"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_10454_8052" result="shape"/>
</filter>
<filter id="filter1_d_10454_8052" x="52.3894" y="6.37332" width="55.5297" height="44.2534" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="4.95763"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_10454_8052"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_10454_8052" result="shape"/>
</filter>
<filter id="filter2_d_10454_8052" x="90.5" y="0" width="69" height="57" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="7.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_10454_8052"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_10454_8052" result="shape"/>
</filter>
<filter id="filter3_d_10454_8052" x="137" y="0" width="69" height="57" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="7.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.07 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_10454_8052"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_10454_8052" result="shape"/>
</filter>
</defs>
</svg>

12
public/locales/en/common.json

@ -33,6 +33,12 @@
"no_data_found": "No Data Found", "no_data_found": "No Data Found",
"arabic": "Arabic", "arabic": "Arabic",
"translation": "Translation", "translation": "Translation",
"transliteration": "Transliteration"
}
"transliteration": "Transliteration",
"support_us_to_grow": "Support us to Grow!",
"donation_message": "With God’s grace and your support, we’ve launched this app to promote faith. To grow further and reach more people, we need your financial help. Every contribution, no matter how small, will make a difference.",
"one_time": "One time",
"monthly": "Monthly",
"enter_desired_amount": "Enter the desired amount...",
"donate_now": "Donate Now",
"credits": "Credits"
}

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

@ -1,33 +1,81 @@
import { DefaultSeo as NextDefaultSeo } from "next-seo"; import { DefaultSeo as NextDefaultSeo } from "next-seo";
interface DefaultSeoProps { interface DefaultSeoProps {
type?: "site" | "dua"; // Determines the type of SEO
title?: string; title?: string;
description?: string; description?: string;
keywords?: string; keywords?: string;
url?: string; // For specific pages like a Dua page
text?: string; // For the Arabic text of the Dua
translation?: string; // For the English translation of the Dua
} }
const DefaultSeo: React.FC<DefaultSeoProps> = ({ const DefaultSeo: React.FC<DefaultSeoProps> = ({
type = "site",
title = "Dua Site", title = "Dua 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",
text,
translation,
}) => { }) => {
// Define structured data dynamically based on the type
const structuredData =
type === "site"
? {
"@context": "https://schema.org/",
"@type": "WebSite",
"name": title,
"url": url,
"description": description,
"publisher": {
"@type": "Organization",
"name": "DuasApp",
"url": url,
"logo": {
"@type": "ImageObject",
"url": "/assets/images/Hosseiniye.svg",
},
},
"potentialAction": {
"@type": "SearchAction",
"target": `${url}/?search={search_term_string}`,
"query-input": "required name=search_term_string",
},
"inLanguage": "en",
}
: {
"@context": "https://schema.org/",
"@type": "CreativeWork",
"name": title,
"url": url,
"description": description,
"text": text,
"translation": {
"@type": "CreativeWork",
"name": "Translation",
"text": translation,
},
"keywords": keywords,
};
return ( return (
<NextDefaultSeo <NextDefaultSeo
title={title} title={title}
titleTemplate={`%s | ${title}`} titleTemplate={`%s | ${title}`}
description={description} description={description}
canonical="https://duasapp.com"
canonical={url}
openGraph={{ openGraph={{
type: "website",
type: type === "site" ? "website" : "article",
locale: "en_US", locale: "en_US",
site_name: title,
site_name: type === "site" ? title : "Dua Site",
description: description, description: description,
images: [ images: [
{ {
url: "/assets/images/Hosseiniye.svg", url: "/assets/images/Hosseiniye.svg",
width: 1200, width: 1200,
height: 630, height: 630,
alt: "Dua Site",
alt: type === "site" ? "Dua Site" : title,
}, },
], ],
}} }}
@ -41,7 +89,7 @@ const DefaultSeo: React.FC<DefaultSeoProps> = ({
content: "width=device-width, initial-scale=1", content: "width=device-width, initial-scale=1",
}, },
{ {
charSet: "utf-8",
charset: "utf-8",
}, },
{ {
name: "apple-mobile-web-app-capable", name: "apple-mobile-web-app-capable",
@ -52,6 +100,12 @@ const DefaultSeo: React.FC<DefaultSeoProps> = ({
content: "#ffffff", content: "#ffffff",
}, },
]} ]}
additionalScripts={[
{
type: "application/ld+json",
innerHTML: JSON.stringify(structuredData),
},
]}
/> />
); );
}; };

26
src/components/context/search-context.tsx

@ -0,0 +1,26 @@
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface SearchContextProps {
searchTerm: string;
setSearchTerm: (term: string) => void;
}
const SearchContext = createContext<SearchContextProps | undefined>(undefined);
export const SearchProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [searchTerm, setSearchTerm] = useState<string>('');
return (
<SearchContext.Provider value={{ searchTerm, setSearchTerm }}>
{children}
</SearchContext.Provider>
);
};
export const useSearch = (): SearchContextProps => {
const context = useContext(SearchContext);
if (!context) {
throw new Error('useSearch must be used within a SearchProvider');
}
return context;
};

47
src/components/layout/header.tsx

@ -6,42 +6,55 @@ import Logo from "../ui/logo";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
const Header = () => { const Header = () => {
const { displayDownload } = useUI();
const {t} = useTranslation("common")
const { displayDownload, openModal } = useUI();
const { t } = useTranslation("common");
return ( return (
<header <header
className={`w-full shadow-sm sticky top-0 bg-white hidden lg:flex z-10 ${ className={`w-full shadow-sm sticky top-0 bg-white hidden lg:flex z-10 ${
displayDownload && "mt-[58px]" displayDownload && "mt-[58px]"
}`} }`}
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:p-6">
{/* Logo Section */}
<div className="flex gap-11 h-full items-center"> <div className="flex gap-11 h-full items-center">
<Logo />
<div className="h-full">
<Logo aria-label="Site Logo" />
<nav className="h-full" aria-label="Primary Navigation">
<ul className="flex gap-11 font-semibold text-[#4D4D4D] h-full items-center"> <ul className="flex gap-11 font-semibold text-[#4D4D4D] h-full items-center">
<li className="h-full flex items-center border-[#c79389] hover:border-b hover:text-[#EB6E57] ">
<Link href={"/"}>{t("home")}</Link>
<li className="h-full flex items-center border-[#c79389] hover:border-b hover:text-[#EB6E57]">
<Link href="/" className="hover:text-[#EB6E57]">
{t("home")}
</Link>
</li> </li>
<li className="h-full flex items-center border-[#F4846F] hover:border-b hover:text-[#EB6E57] ">
<Link href={"/about"}>{t("about")}</Link>
<li className="h-full flex items-center border-[#F4846F] hover:border-b hover:text-[#EB6E57]">
<Link href="/about" className="hover:text-[#EB6E57]">
{t("about")}
</Link>
</li> </li>
<li className="">
<button className="bg-gradient-to-r from-[#F79B59] to-[#EB6E57] rounded-[28px] py-1 px-4">
<p className="text-white text-base font-semibold">{t("donate")}</p>
<li>
<button
onClick={() => openModal("DONATE_VIEW")}
className="bg-gradient-to-r from-[#F79B59] to-[#EB6E57] rounded-[28px] py-1 px-4"
aria-label={t("donate")}
>
<p className="text-white text-base font-semibold">
{t("donate")}
</p>
</button> </button>
</li> </li>
</ul> </ul>
</nav>
</div> </div>
</div>
<div className="flex gap-6 h-11">
<SearchDuas />
<div>
<LanguageSwitcher />
</div>
{/* Right Side: Search and Language Switcher */}
<div className="flex gap-6 h-11">
<SearchDuas aria-label="Search Duas" />
<LanguageSwitcher aria-label="Language Switcher" />
</div> </div>
</div> </div>
</header> </header>
); );
}; };
export default Header; export default Header;

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

@ -0,0 +1,123 @@
import React, { useState, MouseEvent } from "react";
import { IoClose } from "react-icons/io5";
import credits from "../.././../public/assets/images/Frame 1116606661.svg";
import Image from "next/image";
import { useUI } from "../context/ui.context";
import { useTranslation } from "next-i18next"; // Importing translation hook
const DonateModal: React.FC = () => {
const { t } = useTranslation("common"); // Initialize translation hook
const { closeModal } = useUI();
const [selected, setSelected] = useState("one-time"); // 'one-time' or 'monthly'
const [selectedPrice, setSelectedPrice] = useState<number | null>(null);
// 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;
// Closes modal if backdrop is clicked
const handleBackdropClick = (event: MouseEvent<HTMLDivElement>) => {
// Ensure we're clicking directly on the backdrop (not a child element)
if (event.target === event.currentTarget) {
closeModal();
}
};
return (
<div
className="fixed inset-0 flex items-center justify-center bg-black/30 z-50"
onClick={handleBackdropClick}
>
<div
className="w-[499px] bg-[#F5F5F5] px-4 py-5 sm:p-6 rounded-3xl overflow-hidden shadow-xl transform transition-all"
onClick={(e) => e.stopPropagation()}
>
<div className="flex bg-[#F5F5F5] flex-row-reverse">
<button
onClick={() => closeModal()}
type="button"
className="ml-auto bg-transparent border-0 text-gray-400 hover:text-gray-500 focus:outline-none"
aria-label={t("close")}
>
<IoClose />
</button>
<h5 className="text-lg leading-6 font-medium text-gray-900">
{t("support_us_to_grow")}
</h5>
</div>
<div className="w-full bg-[#F5F5F5] h-[152px] overflow-y-auto mt-10">
<p className="text-sm text-gray-500">
{t("donation_message")}
</p>
</div>
<div>
<div className="bg-[#EBEBEB] mt-8 rounded-2xl px-4 py-3 sm:px-6 flex">
<button
type="button"
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"
}`}
onClick={() => {
setSelected("one-time");
setSelectedPrice(null); // reset selected price
}}
>
{t("one_time")}
</button>
<button
onClick={() => {
setSelected("monthly");
setSelectedPrice(null); // reset selected price
}}
type="button"
className={`w-full inline-flex justify-center rounded-2xl px-4 py-2 bg-[#EBEBEB] text-[#8B8B8B] text-base font-medium ${
selected === "monthly" && "bg-white text-black shadow-sm"
}`}
>
{t("monthly")}
</button>
</div>
</div>
<div className="flex items-center mt-6 flex-col gap-4">
<div className="flex gap-4 w-full">
{prices.map((price) => (
<button
key={price}
onClick={() => setSelectedPrice(price)}
className={`w-full py-5 px-6 rounded-2xl
${
selectedPrice === price
? "bg-[#2D4ECD]/20 text-[#2D4ECD]"
: "bg-[#D7DBE2] text-black"
}
`}
>
{price}$
</button>
))}
</div>
<input
className="bg-[#D7DBE2] w-full h-16 rounded-2xl text-center text-[#36363C]
[appearance:textfield]
[&::-webkit-outer-spin-button]:appearance-none
[&::-webkit-inner-spin-button]:appearance-none"
placeholder={t("enter_desired_amount")}
type="number"
/>
</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">
<p className="whitespace-nowrap">{t("donate_now")}</p>
<Image src={credits} alt={t("credits")} />
</button>
</div>
</div>
);
};
export default DonateModal;

8
src/components/modals/modal-manager.tsx

@ -4,6 +4,7 @@ import dynamic from "next/dynamic";
import RecitersModal from "./reciters"; import RecitersModal from "./reciters";
import AudioSetting from "./audio-setting"; import AudioSetting from "./audio-setting";
import LanguageModal from "./languages-modal"; import LanguageModal from "./languages-modal";
import DonateModal from "./donate-modal";
// import Newsletter from "../newsletter"; // import Newsletter from "../newsletter";
const SettingModal = dynamic(() => import("@/components/modals/setting")); const SettingModal = dynamic(() => import("@/components/modals/setting"));
const SearchModal = dynamic(() => import("@/components/modals/search-modal")); const SearchModal = dynamic(() => import("@/components/modals/search-modal"));
@ -24,11 +25,8 @@ const ManagedModal: React.FC = () => {
{modalView === "SEARCH_VIEW" && <SearchModal />} {modalView === "SEARCH_VIEW" && <SearchModal />}
{modalView === "AUDIO_SETTING_VIEW" && <AudioSetting/>} {modalView === "AUDIO_SETTING_VIEW" && <AudioSetting/>}
{modalView === "LANGUAGES_VIEW" && <LanguageModal/>} {modalView === "LANGUAGES_VIEW" && <LanguageModal/>}
{/* {modalView === "SIGN_UP_VIEW" && <SignUpForm />}
{modalView === "FORGET_PASSWORD" && <ForgetPasswordForm />}
{modalView === "PRODUCT_VIEW" && <ProductPopup />}
{modalView === "NEWSLETTER_VIEW" && <Newsletter />}
{modalView === "CHAT_VIEW" && <ChatPopup />} */}
{modalView === "DONATE_VIEW" && <DonateModal/>}
</Modal> </Modal>
); );
}; };

114
src/components/sidebar/search.tsx

@ -0,0 +1,114 @@
"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 { useSearchParams, useRouter } from "next/navigation";
import { Dua } from "../utils/types";
import { useTranslation } from "next-i18next"; // Importing the translation hook
import ShimmerLoader from "../ui/list-loading";
import DOMPurify from "dompurify"; // Sanitize input to avoid XSS attacks
const Search: React.FC = () => {
const { t } = useTranslation("common"); // Initialize translation hook for "common" namespace
const [data, setData] = useState<Dua[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null); // State to hold error messages
const router = useRouter();
const searchParams = useSearchParams(); // Use to retrieve query parameters
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(() => {
const searchParam = searchParams?.get("search") || ""; // Extract search query from URL
if (!searchParam) return;
setLoading(true);
setError(null); // Reset error state before starting the request
const debounceTimer = setTimeout(() => {
// Perform the search API call with the actual user input
http
.get(`web/mafatih-duas/?search=${encodeURIComponent(searchParam)}`)
.then((res) => {
setData(res.data.results);
})
.catch((err) => {
console.error("Error fetching search results:", err);
setError(t("error_message")); // Use translation for error message
})
.finally(() => {
setLoading(false); // Always stop loading here
});
}, 500);
return () => clearTimeout(debounceTimer);
}, [searchParams?.get("search") , t]);
// Function to highlight matching words
const highlightText = (text: string, query: string) => {
if (!query.trim()) return text;
const sanitizedQuery = DOMPurify.sanitize(query);
const regex = new RegExp(`(${sanitizedQuery})`, "gi");
return text.replace(
regex,
`<span style="color: #F4846F;">$1</span>`
);
};
if (loading) {
return <ShimmerLoader />; // Translating the loading message
}
if (error) {
return <p>{error}</p>; // Displaying the error message
}
return (
<div>
<p className="text-xs">
{t("dua_s_found", { count: data.length })}
</p>
{data.length ? (
data.map((dua: Dua) => {
const searchQuery = searchParams?.get("search") || ""; // Get current search query
return (
<div
className="flex justify-between p-3 bg-white my-4 rounded-2xl cursor-pointer"
key={dua.id}
onClick={() => openDua(dua)}
>
<div className="flex items-center gap-2">
<p
dangerouslySetInnerHTML={{
__html: highlightText(dua.title, searchQuery),
}}
/>
</div>
{dua.not_synced && (
<div className="flex items-center p-3 bg-[#EBEBEB] rounded-lg">
<Image src={Audio} alt={t("audio_available")} />
</div>
)}
</div>
);
})
) : (
<div className="flex my-[40%] items-center justify-center">
<Image src={NoData} alt={t("no_data_found")} />
</div>
)}
</div>
);
};
export default Search;

120
src/components/sidebar/tabs.tsx

@ -1,13 +1,14 @@
import { useState } from "react"; import { useState } from "react";
import List from "./list";
import { FaArrowLeft } from "react-icons/fa6"; import { FaArrowLeft } from "react-icons/fa6";
import { DataState, Path } from "@/components/utils/types"; // Import types from types.ts import { DataState, Path } from "@/components/utils/types"; // Import types from types.ts
import { useTranslation } from "next-i18next"; // Importing useTranslation import { useTranslation } from "next-i18next"; // Importing useTranslation
import { tap } from "node:test/reporters";
import Categories from "./categories"; import Categories from "./categories";
import Famous from "./famous"; import Famous from "./famous";
import Today from "./today"; import Today from "./today";
import NearBy from "./nearby"; import NearBy from "./nearby";
import Search from "./search";
import { useSearchParams } from "next/navigation";
import Link from "next/link";
const tabs = [ const tabs = [
{ name: "Categories" }, { name: "Categories" },
@ -21,109 +22,116 @@ const Tabs = () => {
const [selected, setSelected] = useState({ name: "Today" }); const [selected, setSelected] = useState({ name: "Today" });
const [data, setData] = useState<DataState>({ type: null, data: [] }); const [data, setData] = useState<DataState>({ type: null, data: [] });
const [path, setPath] = useState<Path[] | []>([]); const [path, setPath] = useState<Path[] | []>([]);
const searchParams = useSearchParams(); // Use to retrieve query parameters
// const router = useRouter();
const searchQuery = searchParams?.get("search") || ""; // Extract `search` query param
const back = () => { const back = () => {
if (path.length <= 1) return; // Avoid out-of-bounds access
setPath((prevPath) => { setPath((prevPath) => {
const newPath = [...prevPath]; // Create a shallow copy of the array
newPath.pop(); // Remove the last item from the copy
return newPath; // Return the updated array to setPath
const newPath = [...prevPath];
newPath.pop();
return newPath;
}); });
setData(path[path.length - 2]);
setData(path[path.length - 2] || { type: null, data: [] }); // Safe access
}; };
const handlePathClick = (index: number) => { const handlePathClick = (index: number) => {
setPath((prev) => { setPath((prev) => {
const newPath = [...prev]; // Create a shallow copy of the array
const newPath = [...prev];
newPath.splice(index + 1); // Remove elements starting from index + 1 newPath.splice(index + 1); // Remove elements starting from index + 1
return newPath; // Return the updated array
return newPath;
}); });
// Optionally, you can update the data here as well:
setData({ type: path[index].type, data: path[index].data }); setData({ type: path[index].type, data: path[index].data });
}; };
return ( return (
<> <>
{path.length > 1 && (
{path.length > 1 && !searchQuery.trim() && (
<div> <div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div onClick={back} className="p-2 bg-white rounded-xl cursor-pointer">
<button
onClick={back}
className="p-2 bg-white rounded-xl cursor-pointer"
aria-label="Go Back"
>
<FaArrowLeft size="23" /> <FaArrowLeft size="23" />
</div>
<p className="text-base font-bold">{path[1]?.name}</p>
</button>
<h2 className="text-base font-bold">{path[1]?.name}</h2> {/* Heading for current tab */}
<div className="w-10" /> <div className="w-10" />
</div> </div>
{path.length > 2 && {path.length > 2 &&
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
className="text-xs text-[#8B8B8B] font-semibold cursor-pointer"
key={index}
onClick={() => handlePathClick(index)}
>
<span key={index} className="flex items-center">
<Link href="#" onClick={() => handlePathClick(index)}>
<span className="text-xs text-[#8B8B8B] font-semibold cursor-pointer">
{item.name} {item.name}
</span> </span>
</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
</>
</span>
); );
} else { } else {
return ( return (
<span
className="text-xs font-semibold"
key={index}
onClick={() => handlePathClick(index)}
>
{item.name}
</span>
<Link href="#" key={index} onClick={() => handlePathClick(index)}>
<span className="text-xs font-semibold">{item.name}</span>
</Link>
); );
} }
})} })}
</div> </div>
)} )}
{path.length < 2 && (
{path.length < 2 && !searchQuery.trim() && (
<div className="rounded-2xl bg-[#EBEBEB]"> <div className="rounded-2xl bg-[#EBEBEB]">
{/* Render the path names */}
<h2 className="sr-only">Navigation Tabs</h2> {/* Hidden heading for screen readers */}
<ul className="p-1 flex justify-between font-semibold text-xs whitespace-nowrap text-[#8B8B8B]"> <ul className="p-1 flex justify-between font-semibold text-xs whitespace-nowrap text-[#8B8B8B]">
{tabs.map((tab) => ( {tabs.map((tab) => (
<li <li
key={tab.name} key={tab.name}
className={`hover:bg-white hover:shadow-lg w-20 h-9 rounded-xl flex items-center justify-center cursor-pointer ${ className={`hover:bg-white hover:shadow-lg w-20 h-9 rounded-xl flex items-center justify-center cursor-pointer ${
JSON.stringify(selected) === JSON.stringify(tab) && "bg-white shadow-lg"
selected.name === tab.name ? "bg-white shadow-lg" : ""
}`} }`}
onClick={() => {
setSelected(tab);
}}
onClick={() => setSelected(tab)}
> >
<p>{t(tab.name.toLowerCase())}</p> {/* Translate tab name */}
<p>{t(tab.name.toLowerCase())}</p>
</li> </li>
))} ))}
</ul> </ul>
</div> </div>
)} )}
{
selected.name === "Categories" && <Categories path={path} data={data} setData={setData} setPath={setPath} />
}
{
selected.name === "Famous" && <Famous/>
}
{
selected.name === "near_by" && <NearBy/>
}
{
selected.name === "Today" && <Today/>
}
{/* <List
tab={selected.name}
data={data}
setData={setData}
path={path}
setPath={setPath}
/> */}
{selected.name === "Categories" && !searchQuery.trim() && (
<>
<h2 className="sr-only">{t("categories")}</h2>
<Categories path={path} data={data} setData={setData} setPath={setPath} />
</>
)}
{selected.name === "Famous" && !searchQuery.trim() && (
<>
<h2 className="sr-only">{t("famous")}</h2>
<Famous />
</>
)}
{selected.name === "near_by" && !searchQuery.trim() && (
<>
<h2 className="sr-only">{t("near_by")}</h2>
<NearBy />
</>
)}
{selected.name === "Today" && !searchQuery.trim() && (
<>
<h2 className="sr-only">{t("today")}</h2>
<Today />
</>
)}
{searchQuery.trim() && (
<>
<Search />
</>
)}
</> </>
); );
}; };

155
src/components/ui/search-duas.tsx

@ -1,141 +1,76 @@
import React, { useEffect, useState, ChangeEvent, useRef } from "react";
import http from "@/api/http";
import { HiMiniMagnifyingGlass } from "react-icons/hi2";
import React, { useState, ChangeEvent, useEffect, KeyboardEvent } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useTranslation } from "next-i18next"; // Import useTranslation hook
interface Dua {
id: number;
title: string;
// Add other relevant fields based on your API response
}
import { HiMiniMagnifyingGlass } from "react-icons/hi2";
import { useTranslation } from "next-i18next";
const SearchDuas: React.FC = () => { const SearchDuas: React.FC = () => {
const { t } = useTranslation('common'); // Initialize translation hook with the 'common' namespace
const router = useRouter();
const router = useRouter(); // Initialize the Next.js router
const { t } = useTranslation("common"); // Translation hook
const [value, setValue] = useState<string>(""); const [value, setValue] = useState<string>("");
const [results, setResults] = useState<Dua[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [show, setShow] = useState<boolean>(false);
// Ref for the main container
const containerRef = useRef<HTMLDivElement>(null);
const openDua = (dua: Dua) => {
setShow(false);
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}`);
};
// On mount (or whenever router.query.search changes), set the input value if there's a 'search' query param
useEffect(() => { useEffect(() => {
// If the input is empty, reset results and do not send a request
if (value.trim() === "") {
setResults([]);
setError(null);
setShow(false);
return;
const param = router.query.search;
if (typeof param === "string") {
setValue(param);
} }
// Set loading state
setIsLoading(true);
setError(null);
setShow(true);
// Implement debounce: wait for 500ms after the user stops typing
const debounceTimer = setTimeout(() => {
// Perform the search API call with the actual user input
http
.get(`web/mafatih-duas/?search=${encodeURIComponent(value)}`)
.then((res) => {
setResults(res.data.results);
setIsLoading(false);
// Optionally, you can handle the response here
})
.catch((err) => {
console.error("Error fetching search results:", err);
setError(t("error_message")); // Use translation for error message
setIsLoading(false);
}, [router.query.search]);
useEffect(()=>{
// If value is empty, remove any existing search query param.
// Otherwise, set the search query param to the current value.
if (!value.trim()) {
// Remove search param
router.push(router.pathname);
} else {
// Append/update search param
router.push({
pathname: router.pathname,
query: { ...router.query, search: value.trim() },
}); });
}, 500); // 500ms debounce duration
// Cleanup function to cancel the timeout if the value changes before 500ms
return () => clearTimeout(debounceTimer);
}, [value, t]); // Add t to the dependency array
// Handle input changes
}
} , [router, value])
// Update the input value when the user types
const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value); setValue(e.target.value);
}; };
useEffect(() => {
// Function to handle clicks outside the component
const handleClickOutside = (event: MouseEvent) => {
if (
containerRef.current &&
!containerRef.current.contains(event.target as Node)
) {
setShow(false);
// Handle Enter key press
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
// If value is empty, remove any existing search query param.
// Otherwise, set the search query param to the current value.
if (!value.trim()) {
// Remove search param
router.push(router.pathname);
} else {
// Append/update search param
router.push({
pathname: router.pathname,
query: { ...router.query, search: value.trim() },
});
}
} }
}; };
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on cleanup
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
return ( return (
<div className="relative flex flex-col items-center" ref={containerRef}>
<div className="relative flex flex-col items-center">
<div className="flex items-center w-64 h-11 px-4 rounded-2xl gap-3 bg-[#EBEBEB]"> <div className="flex items-center w-64 h-11 px-4 rounded-2xl gap-3 bg-[#EBEBEB]">
<label htmlFor="search-input" className="cursor-pointer"> <label htmlFor="search-input" className="cursor-pointer">
<HiMiniMagnifyingGlass size={24} /> <HiMiniMagnifyingGlass size={24} />
</label> </label>
<input <input
onChange={handleChange} onChange={handleChange}
onKeyDown={handleKeyDown}
id="search-input" id="search-input"
type="text" type="text"
value={value} value={value}
placeholder={t("search_placeholder")} // Use translation for placeholder
placeholder={t("search_placeholder")}
className="text-xs bg-[#EBEBEB] w-full focus:outline-none" className="text-xs bg-[#EBEBEB] w-full focus:outline-none"
aria-label="Search Duas"
onClick={() => {
setShow(true);
}}
aria-label="Search Term"
/> />
</div> </div>
{/* Loading Indicator */}
{isLoading && show && (
<div className="absolute top-14 mt-2 w-64 z-20 bg-white shadow-lg rounded-lg text-sm text-gray-600 px-4 py-2">
{t("loading")} {/* Use translation for loading text */}
</div>
)}
{/* Error Message */}
{error && show && (
<div className="mt-2 text-sm text-red-500">{error}</div>
)}
{/* Search Results */}
{results.length > 0 && show && (
<ul className="absolute top-14 mt-2 w-64 z-20 bg-white shadow-lg rounded-lg h-60 overflow-y-auto">
{results.map((dua) => (
<li
onClick={() => openDua(dua)}
key={dua.id}
className="px-4 py-2 hover:bg-gray-100 cursor-pointer"
>
{dua.title}
</li>
))}
</ul>
)}
</div> </div>
); );
}; };

3
src/pages/_app.tsx

@ -1,6 +1,7 @@
import DefaultSeo from "@/components/common/default-seo"; import DefaultSeo from "@/components/common/default-seo";
import { AudioProvider } from "@/components/context/audio-conext"; import { AudioProvider } from "@/components/context/audio-conext";
import { FontSettingsProvider } from "@/components/context/font-setting-context"; import { FontSettingsProvider } from "@/components/context/font-setting-context";
import { SearchProvider } from "@/components/context/search-context";
import { UIProvider } from "@/components/context/ui.context"; import { UIProvider } from "@/components/context/ui.context";
import Header from "@/components/layout/header"; import Header from "@/components/layout/header";
import MobileHeader from "@/components/layout/mobile-header"; import MobileHeader from "@/components/layout/mobile-header";
@ -23,6 +24,7 @@ function App({ Component, pageProps }: AppProps) {
<FontSettingsProvider> <FontSettingsProvider>
<UIProvider> <UIProvider>
<AudioProvider> <AudioProvider>
<SearchProvider>
<DefaultSeo /> <DefaultSeo />
<div dir={isRtl ? "rtl" : "ltr"}> <div dir={isRtl ? "rtl" : "ltr"}>
{" "} {" "}
@ -41,6 +43,7 @@ function App({ Component, pageProps }: AppProps) {
</div> </div>
<ManagedModal /> <ManagedModal />
</div> </div>
</SearchProvider>
</AudioProvider> </AudioProvider>
</UIProvider> </UIProvider>
</FontSettingsProvider> </FontSettingsProvider>

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

@ -96,8 +96,9 @@ const DuaComponent: React.FC<DuaComponentProps> = ({
// Use the memoized fetchData in the effect hook // Use the memoized fetchData in the effect hook
useEffect(() => { useEffect(() => {
setDuaParts([]); // Reset Dua parts when slug changes
fetchData(false);
setDuaParts([]);
fetchData(false); // Fetch data for the first page
}, [slug]); // Only slug changes should trigger this effect }, [slug]); // Only slug changes should trigger this effect
// Play audio from a specific part // Play audio from a specific part
@ -219,6 +220,8 @@ const DuaComponent: React.FC<DuaComponentProps> = ({
} }
}, [recitingPart, duaParts]); }, [recitingPart, duaParts]);
const handleScroll = useCallback( const handleScroll = useCallback(
(e: React.UIEvent<HTMLDivElement>) => { (e: React.UIEvent<HTMLDivElement>) => {
const target = e.currentTarget; const target = e.currentTarget;
@ -286,7 +289,7 @@ const DuaComponent: React.FC<DuaComponentProps> = ({
padding: "1px", padding: "1px",
}} }}
> >
<div className="p-3 bg-white rounded-3xl">
<div className={`p-3 rounded-3xl ${recitingPart === item ? "bg-[#F9F2F1]" :"bg-white" }`}>
{item.text && ( {item.text && (
<div <div
className={`mb-4 text-right ${ className={`mb-4 text-right ${

Loading…
Cancel
Save