diff --git a/.gitignore b/.gitignore index 1d6860d..80b1041 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,10 @@ yarn-error.log* # local env files .env*.local +.env.local +.env.development +.env.dev +.env.prod .env # vercel @@ -34,3 +38,10 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# other +txt +text +txt.txt +text.txt +other diff --git a/src/config/const.ts b/config.ts similarity index 92% rename from src/config/const.ts rename to config.ts index 1e20cc9..6fd828a 100644 --- a/src/config/const.ts +++ b/config.ts @@ -99,9 +99,9 @@ export const quranLanguages = [ "Yau", "Yoruba", "Zulu", - ]; - - export const quranLanguageVersions: Record = { +]; + +export const quranLanguageVersions: Record = { Achinese: [], Afar: [], Afrikaans: [], @@ -199,20 +199,20 @@ export const quranLanguages = [ Yau: ["la"], Yoruba: ["la"], Zulu: [], - }; - - // Hadith -- - export const hadithLanguages = ["english", "arabic"]; - - export const hadithBooks = { +}; + +// Hadith -- +export const hadithLanguages = ["english", "arabic"]; + +export const hadithBooks = { ABM_BOOKS: new Set(["abudawud", "bukhari", "muslim"]), ITN_BOOKS: new Set(["ibnmajah", "tirmidhi", "nasai"]), ALL_BOOKS: new Set([ - "abudawud", - "bukhari", - "muslim", - "ibnmajah", - "tirmidhi", - "nasai", + "abudawud", + "bukhari", + "muslim", + "ibnmajah", + "tirmidhi", + "nasai", ]), - }; \ No newline at end of file +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e907685..85d1a5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,9 +20,11 @@ "next": "13.5.11", "react": "^18", "react-dom": "^18", + "recharts": "^2.15.0", "tailwind-merge": "^3.3.1", "tailwind-variants": "^3.1.1", "tailwindcss-animate": "^1.0.7", + "vaul": "^1.1.2", "zod": "^4.1.12", "zustand": "^5.0.8" }, @@ -48,6 +50,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@floating-ui/core": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", @@ -464,6 +475,42 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", @@ -1007,6 +1054,69 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz", @@ -1376,7 +1486,133 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, "node_modules/detect-node-es": { @@ -1397,6 +1633,16 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1426,6 +1672,21 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1620,6 +1881,27 @@ "node": ">= 0.4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1740,6 +2022,12 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -2156,6 +2444,23 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2201,6 +2506,12 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/react-remove-scroll": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", @@ -2248,6 +2559,21 @@ } } }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -2270,6 +2596,22 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -2291,6 +2633,38 @@ "node": ">=8.10.0" } }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -2687,6 +3061,12 @@ "node": ">=0.8" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2821,6 +3201,41 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/vaul": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 10fcfeb..4d1876c 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,11 @@ "next": "13.5.11", "react": "^18", "react-dom": "^18", + "recharts": "^2.15.0", "tailwind-merge": "^3.3.1", "tailwind-variants": "^3.1.1", "tailwindcss-animate": "^1.0.7", + "vaul": "^1.1.2", "zod": "^4.1.12", "zustand": "^5.0.8" }, diff --git a/public/images/designs/original-951106d2a573176f8a302e4e000314ce.webp b/public/images/designs/original-951106d2a573176f8a302e4e000314ce.webp new file mode 100644 index 0000000..5d1465f Binary files /dev/null and b/public/images/designs/original-951106d2a573176f8a302e4e000314ce.webp differ diff --git a/public/images/designs/original-bf9bb17199c8e197482b1edd7e5654f7.webp b/public/images/designs/original-bf9bb17199c8e197482b1edd7e5654f7.webp new file mode 100644 index 0000000..f2c116f Binary files /dev/null and b/public/images/designs/original-bf9bb17199c8e197482b1edd7e5654f7.webp differ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2a75f6f..c79c760 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; import { cn } from "@/lib/utils/clsx"; import { ReactNode } from "react"; -import { notoSans, notoSansArabic, redHatText } from "@/config/fonts"; -import { defaultMeta } from "@/config/meta"; +import { notoSans, notoSansArabic, redHatText } from "@/assets/fonts"; +import { defaultMeta } from "@/assets/meta"; import LayoutWrapper from "@/components/layout/LayoutWrapper"; import "@/assets/globals.css"; diff --git a/src/app/page.tsx b/src/app/page.tsx index 7495b1c..4f2f286 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,23 +1,44 @@ -"use client" -import { sendAdhanNotification } from "@/lib/notification/notify"; -import { requestNotificationPermission } from "@/lib/notification/permissions"; +"use client"; + +import { + FeaturesSectionFeatures, HeroCommunityStats, + YourProgressTrackingPointStats, + YourProgressWeeklyActivity, + YourProgressActivityDistribution, + YourProgressTodayGoals, + LearnGrowPages, + TESTIMONIALS, + WhyChooseUsStats, + WhyChooseUsFeatures +} from "@/components/pages/home/content" +import FeatureSection from "@/components/pages/home/FeatureSection" +import HeroSection from "@/components/pages/home/HeroSection" +import YourProgressSection from "@/components/pages/home/YourProgressSection" +import LearnGrowSection from "@/components/pages/home/LearnGrowSection" +import TestimonialSection from "@/components/pages/home/TestimonialSection" +import WhyChooseUsSection from "@/components/pages/home/WhyChooseUsSection" +import CallToActionSection from "@/components/pages/home/CallToActionSection" const HomePage = () => { return ( -
-

fonts testing

- -
-

Deenify Dashboard

-

- Welcome to your daily Islamic companion. -

-

- بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ -

-
+
+ + + + + + +
- ); -}; + ) +} -export default HomePage; \ No newline at end of file +export default HomePage \ No newline at end of file diff --git a/src/config/fonts.ts b/src/assets/fonts.ts similarity index 94% rename from src/config/fonts.ts rename to src/assets/fonts.ts index e34a3ea..49ff56c 100644 --- a/src/config/fonts.ts +++ b/src/assets/fonts.ts @@ -1,13 +1,15 @@ import { Noto_Sans, Noto_Sans_Arabic, Red_Hat_Text } from "next/font/google"; + // heading - professional and stylish export const redHatText = Red_Hat_Text({ subsets: ["latin"], - weight: ["400", "500", "600", "700"], + weight: ["300", "400", "500", "600", "700"], display: "swap", variable: "--font-heading", }); + // body - supports 95+ languages including Latin, Cyrillic, Greek, Devanagari, Vietnamese, and more // Excellent for Quran translations in multiple languages with diacritical marks export const notoSans = Noto_Sans({ @@ -17,6 +19,7 @@ export const notoSans = Noto_Sans({ variable: "--font-noto", }); + // arabic - for Arabic text and Quran verses export const notoSansArabic = Noto_Sans_Arabic({ subsets: ["arabic"], diff --git a/src/assets/globals.css b/src/assets/globals.css index 0029f24..948ab93 100644 --- a/src/assets/globals.css +++ b/src/assets/globals.css @@ -17,7 +17,6 @@ --color-emerald-700: oklch(.508 .118 165.612); --color-emerald-800: oklch(.432 .095 166.913); --color-emerald-900: oklch(.378 .077 168.94); - /* Red */ --color-red-50: oklch(.971 .013 17.38); --color-red-100: oklch(.936 .032 17.717); @@ -29,7 +28,6 @@ --color-red-700: oklch(.505 .213 27.518); --color-red-800: oklch(.444 .177 26.899); --color-red-900: oklch(.396 .141 25.723); - /* Amber */ --color-amber-50: oklch(.987 .022 95.277); --color-amber-100: oklch(.962 .059 95.617); @@ -52,7 +50,6 @@ --color-purple-700: oklch(.496 .265 301.924); --color-purple-800: oklch(.438 .218 303.724); --color-purple-900: oklch(.381 .176 304.987); - /* Blue */ --color-blue-50: oklch(.97 .014 254.604); --color-blue-100: oklch(.932 .032 255.585); @@ -85,7 +82,6 @@ --input-background: 0 0% 95.3%; --ring: 0 0% 3.9%; --radius: 0.625rem; - /* Chart colors for data visualization */ --chart-1: 12 76% 61%; --chart-2: 173 58% 39%; @@ -115,7 +111,6 @@ width: 100%; height: 100%; scroll-behavior: smooth; - font-size: 16px; } body { @@ -125,9 +120,8 @@ font-family: var(--font-noto, system-ui, ui-sans-serif, sans-serif); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - + scroll-behavior: smooth; } - } @@ -145,7 +139,6 @@ font-weight: 500; } - input::placeholder, textarea::placeholder, label, @@ -161,13 +154,7 @@ list-style-type: none; } - .container { - width: 100%; - max-width: 1200px; - margin: 0 auto; - padding: 0 16px; - } - + /* Scrollbar hide */ .scrollbar-hide { scrollbar-width: none; @@ -222,8 +209,21 @@ &::-webkit-scrollbar-track { background-color: transparent; - } } +} + + +/* Components */ +@layer components { + + /* Generic container */ + .container { + @apply w-full max-w-[1202px] mx-auto px-4; + } + /* Footer container */ + .container-footer { + @apply w-full max-w-[1360px] mx-auto md:px-6 px-4; + } } \ No newline at end of file diff --git a/src/config/meta.ts b/src/assets/meta.ts similarity index 100% rename from src/config/meta.ts rename to src/assets/meta.ts diff --git a/src/assets/svg/MenuIcon.tsx b/src/assets/svg/MenuIcon.tsx new file mode 100644 index 0000000..8d085d4 --- /dev/null +++ b/src/assets/svg/MenuIcon.tsx @@ -0,0 +1,36 @@ +import React from 'react' + +const MenuIcon = () => { + return ( + + ) +} + +export default MenuIcon \ No newline at end of file diff --git a/src/components/layout/LayoutWrapper.tsx b/src/components/layout/LayoutWrapper.tsx index 7e60568..914e801 100644 --- a/src/components/layout/LayoutWrapper.tsx +++ b/src/components/layout/LayoutWrapper.tsx @@ -1,22 +1,30 @@ "use client" + import React, { ReactNode, useState } from 'react' -import { Sidebar } from './sidebar/Sidebar' -import Footer from './footer/Footer' import Header from './header/Header' -import { cn } from '@/lib/utils' +import Footer from './footer/Footer' +import Sidebar from './side-bar/Sidebar' +import BottomBar from './bottom-bar/BottomBar' import { useBreakpoint } from '@/hooks/useBreakpoint' +import { cn } from '@/lib/utils/clsx' + +type DrawerTabsType = "menu" | "language" | "settings" interface LayoutWrapperProptype { readonly children: ReactNode } -const LayoutWrapper = ({ children }: LayoutWrapperProptype) => { + +const LayoutWrapper = ({ children }: LayoutWrapperProptype) => { const [sidebarExpanded, setSidebarExpanded] = useState(false) const [isLocked, setIsLocked] = useState(false) + const [activeDrawerTab, setActiveDrawerTab] = useState(null) const isMobile = useBreakpoint('lg', 'down') return ( + // Layout Wrapper
- { setSidebarExpanded={setSidebarExpanded} /> + {/* Content-Container right-side */}
+ {/* Header top-bar */}
setSidebarExpanded(!sidebarExpanded)} + onToggleSidebar={() => { + if (isMobile) setActiveDrawerTab("menu") + else setSidebarExpanded(!sidebarExpanded) + }} /> + + {/* Content-scroller inner-content */}
-
- {children} +
+ <>{children} +
-
+ + {/* Mobile-Actions bottom-Bar */} +
) diff --git a/src/components/layout/bottom-bar/BottomBar.tsx b/src/components/layout/bottom-bar/BottomBar.tsx new file mode 100644 index 0000000..9174805 --- /dev/null +++ b/src/components/layout/bottom-bar/BottomBar.tsx @@ -0,0 +1,160 @@ +"use client" + +import React, { useState } from "react" +import Link from "next/link" +import { usePathname } from "next/navigation" +import { Home, Search, Settings, Globe, } from "lucide-react" +import { Drawer, DrawerContent, DrawerThumb } from "@/components/ui/drawer" +import { cn } from "@/lib/utils/clsx" +import { DrawerTabs } from "./content" +import Tabs from "@/components/shared/Tabs" +import MenuList from "./MenuList" +import LanguageList from "./LanguageList" +import SettingsList from "./SettingsList" + + +type DrawerTabsType = "menu" | "language" | "settings" + +interface BottomBarProps { + isVisible: boolean + activeDrawerTab?: DrawerTabsType | null + onDrawerTabChange?: (tab: DrawerTabsType | null) => void +} + + +const BottomBar = ({ + isVisible, + activeDrawerTab: externalActiveTab, + onDrawerTabChange +}: BottomBarProps) => { + const [selectedLanguage, setSelectedLanguage] = useState("en") + const [searchOpen, setSearchOpen] = useState(false) + const pathname = usePathname() + + // Use external state as single source of truth - no internal state conflicts + const isDrawerOpen = externalActiveTab !== null && externalActiveTab !== undefined + const activeDrawerTab = externalActiveTab || "menu" + + // Handle drawer close - reset parental state + const handleDrawerClose = (open: boolean) => { + if (!open) { + onDrawerTabChange?.(null) + } + } + + if (!isVisible) return null + + return ( + <> + {/* Actions Bottom Bar */} + + + + {/* Drawer Bottom-Bar */} + + + + { + onDrawerTabChange?.(tabId) + }} + showIndicator + className="flex-1 flex flex-col overflow-hidden" + contentContainerClassName="overflow-y-auto scrollbar-content" + > + { + activeDrawerTab === "menu" + ? + : activeDrawerTab === "language" + ? + : activeDrawerTab === "settings" + ? + :

No content

+ } +
+
+
+ + ) +} + +export default BottomBar diff --git a/src/components/layout/bottom-bar/LanguageList.tsx b/src/components/layout/bottom-bar/LanguageList.tsx new file mode 100644 index 0000000..cebf4eb --- /dev/null +++ b/src/components/layout/bottom-bar/LanguageList.tsx @@ -0,0 +1,51 @@ +import { Check } from 'lucide-react' +import React from 'react' +import { LANGUAGES } from './content' +import { cn } from '@/lib/utils/clsx' + +interface LanguageListProps { + selectedLanguage: string + setSelectedLanguage: (language: string) => void +} + +const LanguageList = ({ selectedLanguage, setSelectedLanguage }: LanguageListProps) => { + return ( +
+
+

Select Language

+

Choose your preferred language

+
+
+ {LANGUAGES.map((lang) => { + const isSelected = selectedLanguage === lang.code + return ( + + ) + })} +
+
+ ) +} + +export default LanguageList \ No newline at end of file diff --git a/src/components/layout/bottom-bar/MenuList.tsx b/src/components/layout/bottom-bar/MenuList.tsx new file mode 100644 index 0000000..8cbc5c4 --- /dev/null +++ b/src/components/layout/bottom-bar/MenuList.tsx @@ -0,0 +1,74 @@ +"use client" + +import Link from 'next/link' +import React from 'react' +import { sidebarSections } from '../side-bar/content' +import { cn } from '@/lib/utils/clsx' +import { Moon } from 'lucide-react' +import { usePathname } from 'next/navigation' + +type DrawerTabsType = "menu" | "language" | "settings" + +interface MenuListProps { + onDrawerTabChange?: (tab: DrawerTabsType | null) => void +} + +const MenuList = ({ onDrawerTabChange }: MenuListProps) => { + const pathname = usePathname() + + return ( +
+ {/* Logo Section */} +
+
+ +
+
+

Deenify

+

Islamic Companion

+
+
+ + {/* Navigation Sections */} + {sidebarSections.map((section: typeof sidebarSections[0], idx: number) => ( +
+

+ {section.title} +

+
+ {section.items.map((item: typeof section.items[0]) => { + const Icon = item.icon + const isActive = pathname === item.href + + return ( + onDrawerTabChange?.(null)} + className={cn( + "flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors", + isActive + ? "bg-emerald-50 text-emerald-900" + : "text-gray-700 hover:bg-gray-50" + )} + > + + {item.label} + {isActive && ( +
+ )} + + ) + })} +
+
+ ))} +
+ ) +} + +export default MenuList \ No newline at end of file diff --git a/src/components/layout/bottom-bar/SettingsList.tsx b/src/components/layout/bottom-bar/SettingsList.tsx new file mode 100644 index 0000000..391ca32 --- /dev/null +++ b/src/components/layout/bottom-bar/SettingsList.tsx @@ -0,0 +1,258 @@ +import { + Bell, Palette, Sun, Moon, Volume2, + VolumeX, Shield, User, Mail, Smartphone, + ChevronRight, LucideIcon +} from 'lucide-react' +import React from 'react' +import { useRouter } from 'next/navigation' +import { cn } from '@/lib/utils/clsx' +import { Switch } from '@/components/ui/switch' +import { Button } from '@/components/ui/button' +import { useBreakpoint } from '@/hooks/useBreakpoint' + +interface SettingsListItemProps { + icon: LucideIcon + label: string + description: string + handleWithSwitch?: boolean + checked?: boolean + onSwitchChange?: (checked: boolean) => void + switchSize?: "sm" | "md" | "lg" + switchVariant?: "default" | "faded" + switchDisabled?: boolean + href?: string + onClick?: () => void + disabled?: boolean +} + +const SettingsList = () => { + const router = useRouter() + + // State management for all toggles + const [lightMode, setLightMode] = React.useState(true) + const [darkMode, setDarkMode] = React.useState(false) + const [prayerAlerts, setPrayerAlerts] = React.useState(true) + const [emailNotifications, setEmailNotifications] = React.useState(false) + const [pushNotifications, setPushNotifications] = React.useState(true) + const [adhanSound, setAdhanSound] = React.useState(true) + const [muteAllSounds, setMuteAllSounds] = React.useState(false) + + const isSmDown = useBreakpoint("sm", "down") + + return ( +
+ {/* Quick Settings */} +
+

Quick Settings

+

Customize your app experience

+
+ + {/* Appearance */} +
+

+ + Appearance +

+
+ + +
+
+ +
+ + {/* Notifications */} +
+

+ + Notifications +

+
+ + + +
+
+ +
+ + {/* Sound & Media */} +
+

+ + Sound & Media +

+
+ + +
+
+ +
+ + {/* Privacy & Security */} +
+

+ + Privacy & Security +

+
+ + +
+
+
+ ) +} + +const SettingsListItem: React.FC = ({ + icon: Icon, + label, + description, + handleWithSwitch = false, + checked, + onSwitchChange, + switchSize = "md", + switchVariant = "default", + switchDisabled = false, + href, + onClick, + disabled = false, +}) => { + const router = useRouter() + + const handleClick = () => { + if (disabled) return + if (href) { + router.push(href) + } else if (onClick) { + onClick() + } + } + + if (handleWithSwitch) { + return ( +
+ {/* Left: Icon */} +
+ +
+ + {/* Center: Title and Description */} +
+
+

{label}

+ +
+

+ {description} +

+
+
+ ) + } + + return ( + + ) +} + +export default SettingsList diff --git a/src/components/layout/bottom-bar/content.ts b/src/components/layout/bottom-bar/content.ts new file mode 100644 index 0000000..2ee8ccc --- /dev/null +++ b/src/components/layout/bottom-bar/content.ts @@ -0,0 +1,48 @@ +import type { TabItem } from "@/components/shared/Tabs" +import { type SearchItem } from "@/components/ui/input" +import { + BookOpen, FileText, Clock, Compass, Users, + MenuIcon, + Globe, + Settings, +} from "lucide-react" + + +export const SEARCH_ITEMS: SearchItem[] = [ + { type: "Surah", name: "Al-Fatihah", icon: BookOpen, page: "/quran" }, + { type: "Surah", name: "Al-Baqarah", icon: BookOpen, page: "/quran" }, + { type: "Surah", name: "Al-Imran", icon: BookOpen, page: "/quran" }, + { type: "Surah", name: "An-Nisa", icon: BookOpen, page: "/quran" }, + { type: "Surah", name: "Al-Maidah", icon: BookOpen, page: "/quran" }, + { type: "Surah", name: "Yasin", icon: BookOpen, page: "/quran" }, + { type: "Surah", name: "Al-Mulk", icon: BookOpen, page: "/quran" }, + { type: "Surah", name: "Al-Kahf", icon: BookOpen, page: "/quran" }, + { type: "Hadith", name: "Sahih Bukhari", icon: FileText, page: "/hadith" }, + { type: "Hadith", name: "Sahih Muslim", icon: FileText, page: "/hadith" }, + { type: "Hadith", name: "Sunan Abu Dawood", icon: FileText, page: "/hadith" }, + { type: "Hadith", name: "Jami at-Tirmidhi", icon: FileText, page: "/hadith" }, + { type: "Feature", name: "Prayer Times", icon: Clock, page: "/prayer" }, + { type: "Feature", name: "Qibla Finder", icon: Compass, page: "/qibla" }, + { type: "Feature", name: "Dhikr Counter", icon: Users, page: "/dhikr" }, + { type: "Feature", name: "Islamic Calendar", icon: Clock, page: "/calendar" }, + { type: "Feature", name: "Supplications", icon: BookOpen, page: "/supplications" }, + { type: "Feature", name: "Guides & Learning", icon: BookOpen, page: "/guides" }, +] + +export const LANGUAGES = [ + { code: "en", name: "English", flag: "🇺🇸" }, + { code: "ar", name: "العربية", flag: "🇸🇦" }, + { code: "ur", name: "اردو", flag: "🇵🇰" }, + { code: "tr", name: "Türkçe", flag: "🇹🇷" }, + { code: "fr", name: "Français", flag: "🇫🇷" }, + { code: "id", name: "Bahasa Indonesia", flag: "🇮🇩" }, + { code: "ms", name: "Bahasa Melayu", flag: "🇲🇾" }, + { code: "bn", name: "বাংলা", flag: "🇧🇩" }, +] + + +export const DrawerTabs: TabItem[] = [ + { id: "menu", label: "Menu", icon: MenuIcon }, + { id: "language", label: "Language", icon: Globe }, + { id: "settings", label: "Settings", icon: Settings }, +] \ No newline at end of file diff --git a/src/components/layout/bottom-bar/index.ts b/src/components/layout/bottom-bar/index.ts new file mode 100644 index 0000000..10d1b67 --- /dev/null +++ b/src/components/layout/bottom-bar/index.ts @@ -0,0 +1,2 @@ +export { default as BottomBar } from "./BottomBar" +export * from "./content" \ No newline at end of file diff --git a/src/components/layout/footer/Footer.tsx b/src/components/layout/footer/Footer.tsx index cf7813c..1643e08 100644 --- a/src/components/layout/footer/Footer.tsx +++ b/src/components/layout/footer/Footer.tsx @@ -1,8 +1,167 @@ +"use client" + import React from 'react' +import Link from 'next/link' +import { Moon, Mail, Phone, MapPin } from 'lucide-react' const Footer = () => { + const currentYear = new Date().getFullYear() + + const quickLinks = [ + { label: 'Prayer Times', page: '/prayer' }, + { label: 'Quran', page: '/quran' }, + { label: 'Hadith', page: '/hadith' }, + { label: 'Calendar', page: '/calendar' }, + { label: 'Qibla Finder', page: '/qibla' }, + { label: 'Dhikr Counter', page: '/dhikr' }, + ] + + const resources = [ + { label: 'Supplications', page: '/supplications' }, + { label: 'Guides & Learning', page: '/guides' }, + { label: 'Islamic History', page: '/history' }, + { label: 'Prophetic Chain', page: '/prophets' }, + { label: 'Islamic Miracles', page: '/miracles' }, + { label: 'Five Pillars', page: '/pillars' }, + ] + + const community = [ + { label: 'About Us', page: '/about' }, + { label: 'Contact Us', page: '/contact' }, + { label: 'Community Forum', page: '/forum' }, + { label: 'Blog & Articles', page: '/blog' }, + { label: 'FAQs', page: '/faqs' }, + { label: 'Support Center', page: '/support' }, + ] + + const legal = [ + { label: 'Privacy Policy', page: '/privacy' }, + { label: 'Terms of Service', page: '/terms' }, + { label: 'Cookie Policy', page: '/cookies' }, + { label: 'Disclaimer', page: '/disclaimer' }, + { label: 'Accessibility', page: '/accessibility' }, + { label: 'Licenses', page: '/licenses' }, + ] + return ( -
Footer
+
+
+
+ {/* Main Footer Content */} +
+ {/* About Section */} +
+
+
+
+ +
+
+
+

Deenify

+

Islamic Companion

+
+
+

+ Your comprehensive Islamic lifestyle companion. Strengthen your faith with prayer times, Quran reading, authentic Hadith, and more. +

+
+ + {/* Quick Links */} +
+

Quick Links

+
    + {quickLinks.map((link) => ( +
  • + + {link.label} + +
  • + ))} +
+
+ + {/* Resources */} +
+

Resources

+
    + {resources.map((link) => ( +
  • + + {link.label} + +
  • + ))} +
+
+ + {/* Contact */} +
+

Contact Us

+
    +
  • + + info@islamiccompanion.com +
  • +
  • + + +1 (555) 123-4567 +
  • +
  • + + 123 Islamic Center Dr
    City, State 12345
    +
  • +
+
+
+ + {/* Community & Legal */} +
+
+

Community

+
+ {community.map((link) => ( + + {link.label} + + ))} +
+
+
+

Legal

+
+ {legal.map((link) => ( + + {link.label} + + ))} +
+
+
+ + {/* Bottom Bar */} +
+

+ © {currentYear} Islamic Companion. All rights reserved. +

+
+
+
+
) } diff --git a/src/components/layout/header/Header.tsx b/src/components/layout/header/Header.tsx index 88ae54e..85c4400 100644 --- a/src/components/layout/header/Header.tsx +++ b/src/components/layout/header/Header.tsx @@ -1,7 +1,7 @@ "use client" import React from "react" -import { Menu, BookOpen, FileText, Clock, Compass, Users } from "lucide-react" +import { Menu, BookOpen, FileText, Clock, Compass, Users, Moon } from "lucide-react" import { Button } from "@/components/ui/button" import { Input, type SearchItem } from "@/components/ui/input" import { LanguageSelector } from "@/components/shared/LanguageSelector" @@ -10,12 +10,13 @@ import { UserProfileDropdown, type UserPreferences, } from "@/components/shared/UserProfileDropdown" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/clsx" import { useRouter } from "next/navigation" +import MenuIcon from "@/assets/svg/MenuIcon" export interface HeaderProps { userPreferences?: UserPreferences - onToggleSidebar?: () => void + onToggleSidebar?: () => void searchItems?: SearchItem[] className?: string } @@ -91,45 +92,46 @@ const Header: React.FC = ({ } return ( -
-
- {/* Left Section - Mobile Menu & Greeting */} -
- +
+
+ {/* Left Section - Logo (Mobile) & Greeting */} +
+ {/* Logo - Mobile Only */} +
+
+ +
+
-

+

{getGreeting(userPreferences?.name)}

-

+

Assalamu Alaikum wa Rahmatullahi wa Barakatuh

- {/* Right Section - Search, Language, Notifications, Profile */} -
+ {/* Right Section - Search, Language, Notifications, Profile, Menu (Mobile) */} +
- { - // Handle language change if needed - console.log("Language changed to:", code) - }} - /> +
+ { + // Handle language change if needed + console.log("Language changed to:", code) + }} + /> +
{ @@ -144,6 +146,27 @@ const Header: React.FC = ({ console.log("Sign out clicked") }} /> + + {/* Menu Button - Mobile Only (far right) */} +
diff --git a/src/components/layout/header/content.ts b/src/components/layout/header/content.ts index 0da73f0..9219eb7 100644 --- a/src/components/layout/header/content.ts +++ b/src/components/layout/header/content.ts @@ -1,4 +1,13 @@ -import { Settings, LogOut, HelpCircle, User, Heart, BookOpen, TrendingUp, Clock, LucideIcon } from "lucide-react"; +import { + Settings, + HelpCircle, + User, + BookOpen, + TrendingUp, + Clock, + LucideIcon, + Gift +} from "lucide-react"; export interface MenuItem { label: string; @@ -25,7 +34,7 @@ export const profileMenuItems: MenuSection[] = [ label: "Help & Support", sectionItems: [ { label: "Support Center", icon: HelpCircle, href: "/support-center" }, - { label: "Donate", icon: Heart, href: "/donate" }, + { label: "Donate", icon: Gift, href: "/donate" }, ] } ] diff --git a/src/components/layout/sidebar/Sidebar.tsx b/src/components/layout/side-bar/Sidebar.tsx similarity index 84% rename from src/components/layout/sidebar/Sidebar.tsx rename to src/components/layout/side-bar/Sidebar.tsx index dde9851..418743d 100644 --- a/src/components/layout/sidebar/Sidebar.tsx +++ b/src/components/layout/side-bar/Sidebar.tsx @@ -3,10 +3,9 @@ import React, { useEffect, useState } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; -import { ChevronLeft, BookOpen } from 'lucide-react'; -import { cn } from '@/lib/utils'; +import { ChevronLeft, BookOpen, Moon } from 'lucide-react'; +import { cn } from '@/lib/utils/clsx'; import { sidebarSections } from './content'; -import Logo from '@/components/shared/Logo'; interface SidebarPropTypes { isLocked: boolean; @@ -16,7 +15,7 @@ interface SidebarPropTypes { isMobile: boolean; } -export function Sidebar( +const Sidebar = ( { sidebarExpanded, setSidebarExpanded, @@ -24,7 +23,7 @@ export function Sidebar( setIsLocked, isMobile, }: SidebarPropTypes -) { +) => { const pathname = usePathname(); const [showScrollbar, setShowScrollbar] = useState(false); @@ -57,8 +56,10 @@ export function Sidebar( {isMobile && (
setSidebarExpanded(false)} /> @@ -72,16 +73,26 @@ export function Sidebar( if (!isLocked && !isMobile) setSidebarExpanded(false); }} className={cn( - "bg-white border-r border-gray-200 flex flex-col fixed left-0 top-0 h-full min-h-dvh z-[51]", - "transition-[width] duration-300 ease-in-out will-change-[width]", - sidebarExpanded ? 'w-[280px]' : isMobile ? "w-[76px]" : "w-[76px]", + "bg-white border-r border-layout-separator flex flex-col fixed left-0 top-0 h-full min-h-dvh z-[51]", + isMobile + ? "w-[280px] transition-transform duration-300 ease-in-out will-change-transform" + : "transition-[width] duration-300 ease-in-out will-change-[width]", + isMobile + ? sidebarExpanded + ? "translate-x-0" + : "-translate-x-[calc(100%+20px)]" + : sidebarExpanded + ? "2xl:w-[280px] w-[215px]" + : "w-[76px]", )}> {/* Header - Logo */} -
+
{/* Logo and Title */}
- +
+ +

- Your Islamic Companion + Islamic Companion

@@ -108,7 +119,7 @@ export function Sidebar( setSidebarExpanded(false); } }} - className="absolute right-0 bottom-0 w-6 h-6 bg-white border border-gray-200 rounded-full flex items-center justify-center hover:bg-emerald-50 hover:border-emerald-300 transition-all duration-200 z-[60] shadow-md cursor-pointer translate-x-1/2 translate-y-1/2" + className="absolute right-0 bottom-0 w-6 h-6 bg-white border border-layout-separator rounded-full flex items-center justify-center hover:bg-emerald-50 hover:border-emerald-300 transition-all duration-200 z-[60] shadow-md cursor-pointer translate-x-1/2 translate-y-1/2" aria-label={sidebarExpanded ? "Collapse sidebar" : "Expand sidebar"} > {sidebarExpanded ? ( @@ -206,9 +217,9 @@ export function Sidebar(
-
-
-
+
+
+
)} diff --git a/src/components/layout/sidebar/content.ts b/src/components/layout/side-bar/content.ts similarity index 88% rename from src/components/layout/sidebar/content.ts rename to src/components/layout/side-bar/content.ts index 8ebcb0d..0c52d86 100644 --- a/src/components/layout/sidebar/content.ts +++ b/src/components/layout/side-bar/content.ts @@ -1,5 +1,12 @@ -import { Home, Clock, BookOpen, FileText, Calendar, Compass, Hand, User, BookHeart, GraduationCap, Scroll, Users, Star, Target, Heart, Flame, HelpCircle, Gift } from 'lucide-react'; +import { + Home, Clock, + BookOpen, FileText, + Calendar, Compass, Hand, User, + BookHeart, GraduationCap, + Scroll, Users, Star, Target, + Heart, Flame, HelpCircle, Gift +} from 'lucide-react'; export const sidebarSections = [ { diff --git a/src/components/pages/home/CallToActionSection.tsx b/src/components/pages/home/CallToActionSection.tsx new file mode 100644 index 0000000..57d5947 --- /dev/null +++ b/src/components/pages/home/CallToActionSection.tsx @@ -0,0 +1,49 @@ +import React from 'react' +import { motion } from 'framer-motion' +import { Moon } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { useRouter } from 'next/navigation' + +const CallToActionSection = () => { + const router = useRouter() + return ( +
+
+
+ + + +

+ Begin Your Spiritual Journey Today +

+

+ Start strengthening your connection with Allah through daily prayers, Quran reading, and Islamic knowledge. + Everything you need is at your fingertips. +

+
+ + +
+
+
+
+ ) +} + +export default CallToActionSection \ No newline at end of file diff --git a/src/components/pages/home/FeatureSection.tsx b/src/components/pages/home/FeatureSection.tsx new file mode 100644 index 0000000..535ac24 --- /dev/null +++ b/src/components/pages/home/FeatureSection.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { FeaturesSectionFeatureType } from './content' + +const FeatureSection = ({ FEATURES }: { FEATURES: FeaturesSectionFeatureType[] }) => { + return ( +
+
+
+ + Features + +

+ Everything You Need for Your Islamic Journey +

+

+ Comprehensive tools and resources designed to help you practice and strengthen your faith daily. +

+
+ +
+ {FEATURES.map((feature) => { + const Icon = feature.icon + return ( + + +
+ +
+ {feature.title} +
+ +

{feature.description}

+
+
+ ) + })} +
+
+
+ ) +} + +export default FeatureSection \ No newline at end of file diff --git a/src/components/pages/home/HeroSection.tsx b/src/components/pages/home/HeroSection.tsx new file mode 100644 index 0000000..5656c63 --- /dev/null +++ b/src/components/pages/home/HeroSection.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { motion } from "framer-motion" +import { BookOpen, Clock } from "lucide-react"; +import { HeroCommunityStatType } from "./content"; + +const HeroSection = ({ COMMUNITY_STATS }: { COMMUNITY_STATS: HeroCommunityStatType[] }) => { + + return ( +
+
+ + {/* Prominent animated background patterns */} + + + + {/* Content */} +
+
+
+

+ بِسْمِ ٱللَّٰهِ ٱلرَّحْمَٰنِ ٱلرَّحِيمِ +

+

+ In the name of Allah, the Most Gracious, the Most Merciful +

+
+
+ +
+

+ Your Complete Islamic Companion +

+

+ Strengthen your faith with accurate prayer times, complete Quran access, authentic Hadith + collections, and essential Islamic tools - all in one beautifully designed application. +

+
+ + +
+
+ +
+ {COMMUNITY_STATS.map((stat) => ( +
+
{stat.value}
+
{stat.label}
+
+ ))} +
+
+
+ ) +} + +export default HeroSection \ No newline at end of file diff --git a/src/components/pages/home/LearnGrowSection.tsx b/src/components/pages/home/LearnGrowSection.tsx new file mode 100644 index 0000000..84a00f7 --- /dev/null +++ b/src/components/pages/home/LearnGrowSection.tsx @@ -0,0 +1,72 @@ +'use client'; + +import React from 'react' +import { cn } from '@/lib/utils/clsx' +import { Badge } from '@/components/ui/badge' +import { Card, CardContent } from '@/components/ui/card' +import { useRouter } from 'next/navigation' +import { LearnGrowPageType } from './content' + +const LearnGrowSection = ({ LearnGrowPages }: { LearnGrowPages: LearnGrowPageType[] }) => { + const router = useRouter() + + return ( +
+
+
+ + Learn & Grow + +

Deepen Your Islamic Knowledge

+

+ Explore comprehensive guides and resources to strengthen your understanding of Islam +

+
+ +
+ {LearnGrowPages.slice(0, 3).map((page) => { + const Icon = page.icon + return ( + router.push(`/${page.slug}`)} + > + +
+ +
+

{page.title}

+

{page.desc}

+
+
+ ) + })} +
+ +
+ {LearnGrowPages.slice(3, 5).map((page) => { + const Icon = page.icon + return ( + router.push(`/${page.slug}`)} + > + +
+ +
+

{page.title}

+

{page.desc}

+
+
+ ) + })} +
+
+
+ ) +} + +export default LearnGrowSection \ No newline at end of file diff --git a/src/components/pages/home/TestimonialSection.tsx b/src/components/pages/home/TestimonialSection.tsx new file mode 100644 index 0000000..a92283c --- /dev/null +++ b/src/components/pages/home/TestimonialSection.tsx @@ -0,0 +1,98 @@ +import React from 'react' +import { Badge } from '@/components/ui/badge' +import { Card, CardContent } from '@/components/ui/card' +import { Quote } from 'lucide-react' +import { Avatar, AvatarFallback } from '@/components/ui/avatar' +import { Star } from 'lucide-react' +import { TestimonialType } from './content' + +const TestimonialSection = ({ TESTIMONIALS }: { TESTIMONIALS: TestimonialType[] }) => { + return ( +
+
+
+ + Testimonials + +

Loved by Muslims Worldwide

+

+ Join thousands of believers who have transformed their spiritual journey with our app. +

+
+ +
+
+
+ +
+ {TESTIMONIALS.slice(0, 3).map((testimonial) => ( + +
+ +
+ +

"{testimonial.text}"

+
+
+ + + {testimonial.avatar} + + +
+

{testimonial.name}

+

{testimonial.location}

+
+
+ {[...Array(testimonial.rating)].map((_, i) => ( + + ))} +
+
+
+ + ))} +
+ +
+ {TESTIMONIALS.slice(3, 6).map((testimonial) => ( + +
+ +
+ +

"{testimonial.text}"

+
+
+ + + {testimonial.avatar} + + +
+

{testimonial.name}

+

{testimonial.location}

+
+
+ {[...Array(testimonial.rating)].map((_, i) => ( + + ))} +
+
+
+ + ))} +
+
+
+
+ ) +} + +export default TestimonialSection \ No newline at end of file diff --git a/src/components/pages/home/WhyChooseUsSection.tsx b/src/components/pages/home/WhyChooseUsSection.tsx new file mode 100644 index 0000000..2ff3a04 --- /dev/null +++ b/src/components/pages/home/WhyChooseUsSection.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import { Badge } from '@/components/ui/badge' +import { Card, CardContent } from '@/components/ui/card' +import { Check } from 'lucide-react' +import { WhyChooseUsFeatureType, WhyChooseUsStatType } from './content' + +interface WhyChooseUsSectionType { + FEATURES: WhyChooseUsFeatureType[] + STATS: WhyChooseUsStatType[] +} + +const WhyChooseUsSection = ({ FEATURES, STATS }: WhyChooseUsSectionType) => { + return ( +
+
+
+
+ + Why Choose Us + +

The Most Complete Islamic App

+

+ Join millions of Muslims worldwide who trust our application for their daily Islamic needs. Built with care + and attention to Islamic principles. +

+
+ {FEATURES.map((feature) => ( +
+
+ +
+ {feature} +
+ ))} +
+
+
+ {STATS.map((stat) => { + const Icon = stat.icon + return ( + + + +

{stat.title}

+

{stat.desc}

+
+
+ ) + })} +
+
+
+
+ ) +} + +export default WhyChooseUsSection \ No newline at end of file diff --git a/src/components/pages/home/YourProgressSection.tsx b/src/components/pages/home/YourProgressSection.tsx new file mode 100644 index 0000000..d051886 --- /dev/null +++ b/src/components/pages/home/YourProgressSection.tsx @@ -0,0 +1,154 @@ +"use client" + +import React from 'react' +import { Badge } from '@/components/ui/badge' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { cn } from '@/lib/utils/clsx' +import { Target } from 'lucide-react' +import { BarChart3 } from 'lucide-react' +import { Bar, Cell, Pie, PieChart, ResponsiveContainer } from 'recharts' +import { BarChart } from 'recharts' +import { CartesianGrid } from 'recharts' +import { XAxis } from 'recharts' +import { YAxis } from 'recharts' +import { Tooltip } from 'recharts' +import { Progress } from '@/components/ui/progress' +import { + YourProgressActivityDistributionType, YourProgressTrackingPointStatType, + YourProgressWeeklyActivityType, YourProgressTodayGoalType +} from './content' + + +interface YourProgressSectionProps { + TrackingPointStats: YourProgressTrackingPointStatType[] + WeeklyActivity: YourProgressWeeklyActivityType[] + ActivityDistribution: YourProgressActivityDistributionType[] + TodayGoals: YourProgressTodayGoalType[] +} + +const YourProgressSection = ( + { + TrackingPointStats, + WeeklyActivity, + ActivityDistribution, + TodayGoals + }: YourProgressSectionProps +) => { + return ( +
+
+
+ + Your Progress + +

Track Your Spiritual Journey

+

+ Monitor your daily Islamic activities and see your progress over time +

+
+ +
+ {TrackingPointStats.map((stat) => { + const Icon = stat.icon + return ( + + +
+ +
+
{stat.value}
+
{stat.label}
+
+
+ ) + })} +
+ +
+ + + + + Weekly Activity + + Your activities over the past week + + + + + + + + + + + + + + + + + + + + Activity Distribution + + Breakdown of your Islamic practices + + + + + + `${name} ${(percent * 100).toFixed(0)}%` + } + outerRadius={80} + fill="#8884d8" + dataKey="value" + > + {ActivityDistribution.map((entry, index) => ( + + ))} + + + + + + +
+ + + + Today's Goals + Track your daily Islamic practices + + + {TodayGoals.map((goal) => ( +
+
+ {goal.label} + + {goal.current}/{goal.total} + +
+ +
+ ))} +
+
+
+
+ ) +} + +export default YourProgressSection \ No newline at end of file diff --git a/src/components/pages/home/content.ts b/src/components/pages/home/content.ts new file mode 100644 index 0000000..3396210 --- /dev/null +++ b/src/components/pages/home/content.ts @@ -0,0 +1,226 @@ +import { + Compass, FileText, BookOpen, Calendar, Star, Users, Target, Heart, Shield, Smartphone, + Globe, Award, TrendingUp, Flame, LucideIcon, + Clock +} from "lucide-react" + +// Hero section +export type HeroCommunityStatType = { + value: string + label: string +} +export const HeroCommunityStats: HeroCommunityStatType[] = [ + { value: "10M+", label: "Active Users Worldwide" }, + { value: "50+", label: "Countries Supported" }, + { value: "4.9/5", label: "Average User Rating" }, + { value: "1M+", label: "Daily Prayer Notifications" }, +] + + +// Features section +export type FeaturesSectionFeatureType = { + icon: LucideIcon + title: string + description: string +} +export const FeaturesSectionFeatures: FeaturesSectionFeatureType[] = [ + { + icon: Clock, + title: "Accurate Prayer Times", + description: "Get precise prayer times based on your location with multiple calculation methods.", + }, + { + icon: BookOpen, + title: "Complete Quran", + description: "Read, search, and bookmark verses with translations in multiple languages.", + }, + { + icon: Compass, + title: "Qibla Direction", + description: "Find the direction to Mecca from anywhere in the world with our compass.", + }, + { + icon: FileText, + title: "Authentic Hadith", + description: "Access verified collections of Hadith from Sahih al-Bukhari and Muslim.", + }, + { + icon: Calendar, + title: "Islamic Calendar", + description: "Stay updated with Hijri dates and important Islamic occasions.", + }, + { + icon: Star, + title: "Dhikr Counter", + description: "Track your daily remembrance of Allah with customizable goals.", + }, +] + + +// Your-progress section + +// tracking-activity points +export type YourProgressTrackingPointStatType = { + icon: LucideIcon + label: string + value: number + bgColor: string + iconColor: string +} +export const YourProgressTrackingPointStats: YourProgressTrackingPointStatType[] = [ + { icon: Flame, label: "Day Streak", value: 7, bgColor: "bg-orange-100", iconColor: "text-orange-600" }, + { icon: Award, label: "Total Points", value: 842, bgColor: "bg-purple-100", iconColor: "text-purple-600" }, + { icon: TrendingUp, label: "Global Rank", value: 142, bgColor: "bg-blue-100", iconColor: "text-blue-600" }, + { icon: Target, label: "Achievements", value: 12, bgColor: "bg-emerald-100", iconColor: "text-emerald-600" }, +] + + +// weekly-activity charts +export type YourProgressWeeklyActivityType = { + day: string + prayers: number + quran: number + dhikr: number +} +export const YourProgressWeeklyActivity: YourProgressWeeklyActivityType[] = [ + { day: "Mon", prayers: 5, quran: 3, dhikr: 100 }, +] + +// activity-distribution charts +export type YourProgressActivityDistributionType = { + name: string + value: number + color: string +} +export const YourProgressActivityDistribution: YourProgressActivityDistributionType[] = [ + { name: "Prayers", value: 35, color: "#10b981" }, + { name: "Quran", value: 28, color: "#3b82f6" }, + { name: "Dhikr", value: 25, color: "#8b5cf6" }, + { name: "Duas", value: 12, color: "#f59e0b" }, +] + +// today-goals charts +export type YourProgressTodayGoalType = { + label: string + current: number + total: number + value: number +} +export const YourProgressTodayGoals: YourProgressTodayGoalType[] = [ + { label: "Prayers Completed", current: 3, total: 5, value: 60 }, +] + + +// learn-grow section +export type LearnGrowPageType = { + icon: LucideIcon + title: string + desc: string + bgColor: string + iconColor: string + slug: string +} +export const LearnGrowPages: LearnGrowPageType[] = [ + { icon: BookOpen, title: "Total Books", desc: "Total Books", bgColor: "bg-purple-100", iconColor: "text-purple-600", slug: "total-books" }, + { icon: Users, title: "Total Users", desc: "Total Users", bgColor: "bg-blue-100", iconColor: "text-blue-600", slug: "total-users" }, + { icon: TrendingUp, title: "Total Views", desc: "Total Views", bgColor: "bg-emerald-100", iconColor: "text-emerald-600", slug: "total-views" }, +] + + + +// Testimonial section +export type TestimonialType = { + name: string + location: string + avatar: string + rating: number + text: string +} +export const TESTIMONIALS: TestimonialType[] = [ + { + name: "Ahmed Hassan", + location: "Dubai, UAE", + avatar: "AH", + rating: 5, + text: "This app has transformed my daily routine. The prayer time notifications are accurate, and the Quran reader is beautifully designed. May Allah reward the developers!", + }, + { + name: "Fatima Zahra", + location: "London, UK", + avatar: "FZ", + rating: 5, + text: "As a busy professional, this app helps me stay connected to my faith. The Dhikr counter and Hadith collections are my favorite features.", + }, + { + name: "Muhammad Ibrahim", + location: "Jakarta, Indonesia", + avatar: "MI", + rating: 5, + text: "The most comprehensive Islamic app I have ever used. The interface is clean, and everything works seamlessly. Highly recommended!", + }, + { + name: "Aisha Rahman", + location: "Toronto, Canada", + avatar: "AR", + rating: 5, + text: "The Quran reading feature with bookmarks has helped me stay consistent with my daily recitation. Truly a blessing for Muslims everywhere.", + }, + { + name: "Omar Abdullah", + location: "Riyadh, Saudi Arabia", + avatar: "OA", + rating: 5, + text: "Excellent resource for learning about Islam. The Hadith collections are authentic and the guides are very helpful for new Muslims.", + }, + { + name: "Zainab Malik", + location: "Sydney, Australia", + avatar: "ZM", + rating: 5, + text: "A beautiful and user-friendly app. The Islamic calendar and Qibla finder are incredibly accurate. May Allah bless this project!", + }, +] + + + +// Why-Choose-Us section + +// Why-Choose-Us features +export type WhyChooseUsFeatureType = string +export const WhyChooseUsFeatures: WhyChooseUsFeatureType[] = [ + "Customizable prayer notifications", + "Offline access to Quran", + "Dark mode support", + "Multiple language support", + "Sync across devices", + "Ad-free experience", +] + +// Why-Choose-Us stats +export type WhyChooseUsStatType = { + icon: LucideIcon + title: string + desc: string +} +export const WhyChooseUsStats: WhyChooseUsStatType[] = [ + { + icon: Shield, + title: "Verified Content", + desc: "All Hadith and Islamic content verified by scholars", + }, + { + icon: Smartphone, + title: "Mobile First", + desc: "Optimized for all devices and screen sizes", + }, + { + icon: Globe, + title: "Global Access", + desc: "Works anywhere in the world with offline support", + }, + { + icon: Users, + title: "Community", + desc: "Join a thriving community of believers", + }, +] \ No newline at end of file diff --git a/src/components/shared/LanguageSelector.tsx b/src/components/shared/LanguageSelector.tsx index 250e6ff..b1b0595 100644 --- a/src/components/shared/LanguageSelector.tsx +++ b/src/components/shared/LanguageSelector.tsx @@ -9,7 +9,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/clsx" export type Language = { code: string @@ -22,6 +22,7 @@ interface LanguageSelectorProps { defaultLanguage?: string onLanguageChange?: (code: string) => void className?: string + compact?: boolean } @@ -42,6 +43,7 @@ export const LanguageSelector: React.FC = ({ defaultLanguage = "en", onLanguageChange, className, + compact = false, }) => { const [selectedLang, setSelectedLang] = useState(defaultLanguage) const [open, setOpen] = useState(false) @@ -59,31 +61,38 @@ export const LanguageSelector: React.FC = ({ "!focus-visible:ring-0 !focus-visible:ring-offset-0 focus-visible:outline-none", "focus:outline-none !ring-0 !ring-offset-0", "active:ring-0 active:ring-offset-0", - "w-[100px]", + compact ? "w-10" : "w-[100px]" )} > = ({ +
) : ( localNotifications?.map((notification, idx) => { - const Icon = notification.icon const colorClass = colorClasses[notification.color || "blue"] const isRead = notification.read return ( -
handleNotificationClick(notification)} className={cn( - "group relative p-[10px] rounded-sm last:mb-0 transition-all duration-150 mb-[6px] select-none", - + "group relative p-2.5 rounded-md last:mb-0 transition-all duration-150 mb-1.5 select-none", // Background & Gradients isRead ? "bg-gray-50/50 hover:bg-gray-50" @@ -210,14 +226,13 @@ export const NotificationsPopover: React.FC = ({
- +
-

= ({ {isRead && ( <> - + )}

@@ -259,32 +274,29 @@ export const NotificationsPopover: React.FC = ({
- +
) }) )}
- {/* Actions Section */} -
- {localNotifications.length > 0 && - ( -
- -
- )} -
+ {/* Actions Section - Fixed at bottom */} + {localNotifications.length > 0 && ( +
+ +
+ )} ) diff --git a/src/components/shared/Tabs.tsx b/src/components/shared/Tabs.tsx new file mode 100644 index 0000000..e28e229 --- /dev/null +++ b/src/components/shared/Tabs.tsx @@ -0,0 +1,209 @@ +"use client"; + +import React, { useEffect, useRef, useState, useCallback } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import { cn } from '@/lib/utils/clsx' + +export type TabType = "menu" | "language" | "settings" + +export interface TabItem { + id: TabType + label: string + icon?: React.ElementType + content?: React.ReactNode +} + +interface TabsPropType { + allTabs: Array + activeTab: TabType + onTabChange: (tabId: TabType) => void + children?: React.ReactNode + className?: string + tabsContainerClassName?: string + contentContainerClassName?: string + showIndicator?: boolean + variant?: "default" | "underline" | "pills" +} + +const Tabs = ({ + allTabs, + activeTab, + children, + onTabChange, + className, + tabsContainerClassName, + contentContainerClassName, + showIndicator = true, + variant = "underline" +}: TabsPropType) => { + + const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 }) + const tabRefs = useRef>({}) + const tabsContainerRef = useRef(null) + + const updateIndicator = useCallback(() => { + const activeTabElement = tabRefs.current[activeTab] + const container = tabsContainerRef.current + if (activeTabElement && showIndicator && container) { + const containerRect = container.getBoundingClientRect() + const tabRect = activeTabElement.getBoundingClientRect() + const scrollLeft = container.scrollLeft + + setIndicatorStyle({ + left: tabRect.left - containerRect.left + scrollLeft, + width: tabRect.width, + }) + } + }, [activeTab, showIndicator]) + + useEffect(() => { + updateIndicator() + + // Auto-scroll active tab into view + const activeTabElement = tabRefs.current[activeTab] + const container = tabsContainerRef.current + if (activeTabElement && container) { + const containerRect = container.getBoundingClientRect() + const tabRect = activeTabElement.getBoundingClientRect() + const scrollLeft = container.scrollLeft + const tabLeft = tabRect.left - containerRect.left + scrollLeft + const tabRight = tabLeft + tabRect.width + + if (tabLeft < scrollLeft) { + container.scrollTo({ left: tabLeft - 8, behavior: 'smooth' }) + } else if (tabRight > scrollLeft + containerRect.width) { + container.scrollTo({ left: tabRight - containerRect.width + 8, behavior: 'smooth' }) + } + } + }, [activeTab, updateIndicator]) + + useEffect(() => { + const container = tabsContainerRef.current + if (container) { + container.addEventListener('scroll', updateIndicator) + window.addEventListener('resize', updateIndicator) + return () => { + container.removeEventListener('scroll', updateIndicator) + window.removeEventListener('resize', updateIndicator) + } + } + }, [updateIndicator]) + + const activeTabContent = allTabs.find(tab => tab.id === activeTab)?.content + + return ( +
+ {/* Tabs Header */} +
+
+ {allTabs.map((tab) => { + const Icon = tab.icon + const isActive = activeTab === tab.id + + if (variant === "pills") { + return ( + + ) + } + + return ( + + ) + })} +
+ + {/* Animated Indicator */} + {showIndicator && variant === "underline" && ( + + )} +
+ + {/* Content */} +
+ + {children ? ( + + {children} + + ) : activeTabContent ? ( + + {activeTabContent} + + ) : <>No content in the selected tab yet!} + +
+
+ ) +} + +export default Tabs diff --git a/src/components/shared/UserProfileDropdown.tsx b/src/components/shared/UserProfileDropdown.tsx index 4d48793..6f2e0f1 100644 --- a/src/components/shared/UserProfileDropdown.tsx +++ b/src/components/shared/UserProfileDropdown.tsx @@ -14,7 +14,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/clsx" import { profileMenuItems } from "../layout/header/content" import Link from "next/link" @@ -57,20 +57,39 @@ export const UserProfileDropdown: React.FC = ({ - + {/* Header Section */} -
+
@@ -81,14 +100,14 @@ export const UserProfileDropdown: React.FC = ({

{user?.name || "User"}

-

+

{user?.email || "user@example.com"}

{/* Stats Cards */} -
+

{stats.daysStreak || 0} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx index 51e507b..de7937d 100644 --- a/src/components/ui/avatar.tsx +++ b/src/components/ui/avatar.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as AvatarPrimitive from "@radix-ui/react-avatar" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/clsx" const Avatar = React.forwardRef< React.ElementRef, diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index 9c16f42..1280a6d 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -1,7 +1,7 @@ import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/clsx" const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", @@ -15,6 +15,11 @@ const badgeVariants = cva( destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground", + emerald: "bg-emerald-100 border-emerald-200 text-emerald-700", + purple: "bg-purple-100 border-purple-200 text-purple-700", + blue: "bg-blue-100 border-blue-200 text-blue-700", + amber: "bg-amber-100 border-amber-200 text-amber-700", + solid: "bg-emerald-600 border-emerald-600 text-white", }, severity: { low: "bg-emerald-50 border-emerald-200 text-emerald-600", @@ -31,6 +36,7 @@ const badgeVariants = cva( export interface BadgeProps extends React.HTMLAttributes, VariantProps { + variant?: "default" | "secondary" | "destructive" | "outline" | "emerald" | "purple" | "blue" | "amber" | "solid" severity?: "low" | "medium" | "high" } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 9036d8f..95a5c38 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,7 +1,7 @@ "use client"; import * as React from "react"; import { tv, type VariantProps } from "tailwind-variants"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/clsx"; import Link from "next/link"; const buttonVariants = tv({ @@ -21,7 +21,7 @@ const buttonVariants = tv({ "outline-purple": "border border-purple-600 text-purple-700 hover:bg-purple-50", // Ghost variants - ghost: "hover:bg-gray-100 hover:text-gray-900 bg-transparent", + ghost: "hover:bg-gray-200/50 hover:text-gray-900 bg-transparent", "ghost-emerald": "hover:bg-emerald-50 hover:text-emerald-900 bg-transparent", "ghost-red": "hover:bg-red-50 hover:text-red-900 bg-transparent", "ghost-blue": "hover:bg-blue-50 hover:text-blue-900 bg-transparent", @@ -32,10 +32,11 @@ const buttonVariants = tv({ "link-red": "text-red-600 underline-offset-4 hover:underline bg-transparent shadow-none", "link-blue": "text-blue-600 underline-offset-4 hover:underline bg-transparent shadow-none", - // Destructive / Secondary / Transparent + // Destructive / Secondary / Transparent / Faded destructive: "bg-red-600 text-white hover:bg-red-700", secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200", transparent: "bg-transparent text-gray-700 hover:bg-gray-50/50", + faded: "bg-gray-100/50 text-gray-600 hover:bg-gray-100/70 opacity-70", }, size: { sm: "h-[36px] px-3 text-sm", @@ -61,6 +62,7 @@ export interface ButtonProps rippleClassName?: string rippleGoesFast?: boolean showRipple?: boolean + shouldScale?: boolean } interface Ripple { @@ -80,14 +82,33 @@ export const Button = ({ rippleClassName, rippleGoesFast = false, showRipple = false, + shouldScale = false, ...props }: ButtonProps & Omit, keyof ButtonProps>) => { const [ripples, setRipples] = React.useState([]); + const [isPressed, setIsPressed] = React.useState(false); const rippleKey = React.useRef(0); const Component: React.ElementType = href ? Link : component ?? (asChild ? "div" : "button"); + // Global mouse up handler - reset scale + React.useEffect(() => { + if (isPressed && shouldScale) { + const handleGlobalMouseUp = () => setIsPressed(false) + window.addEventListener("mouseup", handleGlobalMouseUp); + return () => window.removeEventListener("mouseup", handleGlobalMouseUp) + } + }, [isPressed, shouldScale]); + + // Mouse down handler + const handleMouseDown = (e: React.MouseEvent) => { + if (shouldScale) setIsPressed(true) + if (props.onMouseDown) props.onMouseDown(e); + }; + + // Mouse up handler const handleMouseUp = (e: React.MouseEvent) => { + if (shouldScale) setIsPressed(false) if (props.onMouseUp) props.onMouseUp(e); const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); @@ -96,11 +117,10 @@ export const Button = ({ const key = rippleKey.current++; setRipples((prev) => [...prev, { x, y, key }]); - - // remove ripple after animation setTimeout(() => setRipples((prev) => prev.filter((r) => r.key !== key)), 500); }; + return ( ({ "focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 overflow-hidden px-[30px]", buttonVariants({ variant, size, className }), )} - onMouseDown={props.onMouseDown} + style={{ + transform: shouldScale && isPressed ? "scale(0.90)" : "scale(1)", + transition: "transform 0.1s ease-out", + ...props.style, + }} + onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} {...props} href={href} diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..81ccc5f --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" +import { cn } from "@/lib/utils/clsx" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } + diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx new file mode 100644 index 0000000..2deb9ce --- /dev/null +++ b/src/components/ui/drawer.tsx @@ -0,0 +1,111 @@ +"use client"; +import { cn } from "@/lib/utils/clsx"; +import * as React from "react"; +import { Drawer as DrawerPrimitive } from "vaul"; + +const DrawerTrigger = DrawerPrimitive.Trigger; +const DrawerPortal = DrawerPrimitive.Portal; +const DrawerClose = DrawerPrimitive.Close; + +const Drawer = ({ + shouldScaleBackground = true, + ...props +}: React.ComponentProps) => ( + +); + +const DrawerOverlay = ({ + overlayStyle, +}: React.ComponentPropsWithoutRef & { + overlayStyle?: string; +}) => ( + +) + +const DrawerContent = ({ + className, + overlayStyle, + shouldShowOverlay = true, + children, + ...props +}: React.ComponentPropsWithoutRef & { + overlayStyle?: string; + shouldShowOverlay?: boolean; +}) => ( + + {shouldShowOverlay && } + + {children} + + +); + +const DrawerThumb = ({ + className, + wrapperStyle, + thumbSize = "md", +}: React.HTMLAttributes & { + thumbSize?: "xs" | "sm" | "md" | "lg"; + wrapperStyle?: string; +}) => ( +
+
+
+); + +const DrawerTitle = ({ + className, + ...props +}: React.ComponentPropsWithoutRef) => ( + +); + +const DrawerDescription = ({ + className, + ...props +}: React.ComponentPropsWithoutRef) => { + return ( + + ); +}; + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerTitle, + DrawerThumb, + DrawerDescription, +}; \ No newline at end of file diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index 16a0506..130b727 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { Check, ChevronRight, Circle } from "lucide-react" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/clsx" const DropdownMenu = DropdownMenuPrimitive.Root @@ -58,12 +58,15 @@ DropdownMenuSubContent.displayName = const DropdownMenuContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { + collisionPadding?: number + } +>(({ className, sideOffset = 8, collisionPadding = 16, ...props }, ref) => ( { filteredItems?: SearchItem[] onItemSelect?: (item: SearchItem) => void className?: string + containerClassName?: string } function Input({ @@ -26,6 +27,7 @@ function Input({ search = false, filteredItems = [], onItemSelect, + containerClassName, ...props }: InputProps) { const [showDropdown, setShowDropdown] = React.useState(false) @@ -53,7 +55,7 @@ function Input({ } return ( -
+
{search && ( search && filteredItems.length > 0 && setShowDropdown(true)} {...props} /> @@ -91,7 +93,7 @@ function Input({ animate={{ opacity: 1, y: 0, scale: 1 }} exit={{ opacity: 0, y: -10, scale: 0.95 }} transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] }} - className="absolute top-full mt-2 w-full bg-white border border-gray-200 rounded-sm shadow-lg z-[100] overflow-hidden" + className="absolute top-full mt-2 w-full bg-white border border-layout-separator rounded-sm shadow-lg z-[100] overflow-hidden" >
{filteredItems.map((item, index) => { @@ -103,8 +105,8 @@ function Input({ initial={{ opacity: 0, x: -10 }} animate={{ opacity: 1, x: 0 }} transition={{ - duration: 0.15, - delay: index * 0.03, + duration: 0.2, + delay: index * 0.06, ease: [0.4, 0, 0.2, 1] }} className={cn( diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx index 355e924..6fd3690 100644 --- a/src/components/ui/popover.tsx +++ b/src/components/ui/popover.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as PopoverPrimitive from "@radix-ui/react-popover" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/clsx" const Popover = PopoverPrimitive.Root @@ -11,13 +11,16 @@ const PopoverTrigger = PopoverPrimitive.Trigger const PopoverContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { + collisionPadding?: number + } +>(({ className, align = "center", sideOffset = 8, collisionPadding = 16, ...props }, ref) => ( { + value?: number + max?: number +} + +const Progress = React.forwardRef( + ({ className, value = 0, max = 100, ...props }, ref) => { + const percentage = Math.min(Math.max((value / max) * 100, 0), 100) + + return ( +
+
+
+ ) + } +) +Progress.displayName = "Progress" + +export { Progress } + diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 0000000..d9fc6de --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,101 @@ +"use client" + +import * as React from "react" +import { tv, type VariantProps } from "tailwind-variants" +import { cn } from "@/lib/utils/clsx" + +const switchVariants = tv({ + base: [ + "relative inline-flex shrink-0 cursor-pointer rounded-full transition-colors duration-300 ease-in-out", + "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2", + "focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50", + ], + variants: { + size: { + sm: "w-9 h-[calc(12px+6px)]", + md: "w-11 h-[calc(16px+6px)]", + lg: "w-14 h-[calc(20px+6px)]", + }, + variant: { + default: [ + "bg-gray-300", + "data-[checked=true]:bg-emerald-600", + ], + faded: [ + "bg-gray-200", + "data-[checked=true]:bg-emerald-500/70", + ], + }, + }, + defaultVariants: { + size: "md", + variant: "default", + }, +}) + +const switchThumbVariants = tv({ + base: [ + "absolute top-1/2 -translate-y-1/2 rounded-full bg-white transition-all duration-300 ease-in-out", + "shadow-[0_2px_6px_rgba(0,0,0,0.2),0_1px_2px_rgba(0,0,0,0.1)]", + "border border-gray-100/50", + ], + variants: { + size: { + // 100% - thumb width - right padding + sm: [ + "w-3 h-3 left-[3px]", + "data-[checked=true]:left-[calc(100%-12px-3px)]", + ], + md: [ + "h-4 w-4 left-[3px]", + "data-[checked=true]:left-[calc(100%-16px-3px)]", + ], + lg: [ + "h-5 w-5 left-[3px]", + "data-[checked=true]:left-[calc(100%-20px-3px)]", + ], + }, + }, + defaultVariants: { + size: "md", + }, +}) + +export interface SwitchProps + extends Omit, "onChange">, + VariantProps { + checked?: boolean + onCheckedChange?: (checked: boolean) => void +} + +const Switch = React.forwardRef( + ({ className, size, variant, checked = false, onCheckedChange, disabled, ...props }, ref) => { + const handleClick = () => { + if (!disabled && onCheckedChange) { + onCheckedChange(!checked) + } + } + + return ( + + ) + } +) + +Switch.displayName = "Switch" +export { Switch, switchVariants } diff --git a/src/config/settings.ts b/src/config/settings.ts deleted file mode 100644 index 8dea778..0000000 --- a/src/config/settings.ts +++ /dev/null @@ -1,17 +0,0 @@ -// config/defaultUserSettings.ts - -export const defaultUserSettings = { - liveAdhan: false, - darkMode: false, - language: "en", - enableHijriDate: true, - showPrayerNotifications: true, - preferredReciter: "mishary-rashid", - enableLiveAdhan: true, - enableQiblaCompass: true, - enableDuaCategories: true, - enableTafsir: false, // beta maybe - enableMultiLanguageQuran: true, - arabicFontSize: 16, - fontSize: 1 -}; diff --git a/src/hooks/useBreakpoint.ts b/src/hooks/useBreakpoint.ts index 04a0de9..fd09818 100644 --- a/src/hooks/useBreakpoint.ts +++ b/src/hooks/useBreakpoint.ts @@ -10,7 +10,7 @@ const breakpoints: Record = { xs: 480, sm: 640, md: 768, - lg: 991, + lg: 1024, xl: 1170, "2xl": 1280, }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts deleted file mode 100644 index bd0c391..0000000 --- a/src/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} diff --git a/src/store/userSettings.ts b/src/store/userSettings.ts index 19bebf0..dabd531 100644 --- a/src/store/userSettings.ts +++ b/src/store/userSettings.ts @@ -1,7 +1,10 @@ // useSettings.ts import { create } from "zustand"; -import { defaultUserSettings } from "@/config/settings"; + +const defaultUserSettings = { + liveAdhan: true, +} const useSettings = create((set) => ({ ...defaultUserSettings, diff --git a/tailwind.config.ts b/tailwind.config.ts index 2a7e19f..f2bbfbf 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,4 +1,4 @@ -import type { Config } from "tailwindcss"; +import type { Config } from "tailwindcss"; const config: Config = { darkMode: ["class"], @@ -11,10 +11,10 @@ const config: Config = { extend: { // Font Family - Exact match to Figma Make - fontFamily: { + fontFamily: { heading: ["var(--font-heading)", "ui-sans-serif", "system-ui", "sans-serif"], body: ["var(--font-noto)", "ui-sans-serif", "system-ui", "sans-serif"], - arabic: ["var(--font-noto-arabic)", "serif"], + arabic: ["var(--font-noto-arabic)", "serif"], }, // Screens @@ -22,13 +22,13 @@ const config: Config = { xs: "480px", sm: "640px", md: "768px", - lg: "991px", + lg: "1024px", xl: "1170px", "2xl": "1280px", }, // Colors - colors: { + colors: { emerald: { "50": "var(--color-emerald-50)", "100": "var(--color-emerald-100)", @@ -95,6 +95,8 @@ const config: Config = { DEFAULT: "var(--color-blue-500)", }, + "layout-separator": "#e5e7eb", + // Radix UI / shadcn colors - Required for Radix UI components background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", @@ -125,7 +127,7 @@ const config: Config = { border: "hsl(var(--border))", input: "hsl(var(--input))", "input-background": "hsl(var(--input-background))", - ring: "hsl(var(--ring))", + ring: "hsl(var(--ring))", chart: { "1": "hsl(var(--chart-1))", "2": "hsl(var(--chart-2))", @@ -143,7 +145,7 @@ const config: Config = { }, // Border Radius - Exact match to Figma Make - borderRadius: { + borderRadius: { sm: "4px", md: "8px", lg: "12px",