├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── README.md ├── app-refactoring.todo ├── app.config.ts ├── app.vue ├── assets ├── css │ ├── animation.css │ ├── core.css │ └── main.css └── fonts │ ├── Al-Qalam-Quran.ttf │ ├── UthmanTN1_Ver10.otf │ └── quran-font.ttf ├── components ├── A3DCard.vue ├── About.vue ├── App │ ├── Confirm.vue │ ├── Divider.vue │ ├── LanguageToggle.vue │ ├── Search │ │ ├── Input.vue │ │ ├── Results.vue │ │ └── Wrapper.vue │ ├── Spinner.vue │ └── Toggle.vue ├── AudioPlayer.vue ├── Auth │ ├── Banner.vue │ ├── Land.vue │ ├── Login.vue │ └── Registration.vue ├── Base │ ├── Header.vue │ ├── Links.vue │ └── Settings.vue ├── ChapterQuickActions.vue ├── ColorsPlaceholder.vue ├── OngoingMedia.vue ├── ProTips.vue └── Sections.vue ├── composables ├── use-apis.ts ├── use-app-info.ts ├── use-chapters.ts ├── use-fetch-apis.ts ├── use-guest.ts └── use-quran-reciters.ts ├── db └── quran.json ├── i18n.config.ts ├── layouts └── default.vue ├── localizations ├── ar.json └── en.json ├── middleware └── auth.ts ├── nuxt.config.ts ├── package.json ├── pages ├── index.vue └── quran │ ├── chapter │ └── [id] │ │ ├── index.vue │ │ └── tafsir │ │ └── [tafsir_chapter_id].vue │ ├── index.vue │ ├── juz │ └── [id].vue │ └── verse │ └── [verse_id].vue ├── pnpm-lock.yaml ├── public ├── favicon.ico ├── reciters │ ├── Abdelbasset-abdessamad.jpeg │ ├── afasy.jpeg │ ├── al-hosari.jpeg │ ├── alqatami.jpeg │ └── minshawi.jpeg └── visuals │ ├── azkar.svg │ ├── matt.svg │ └── quran.svg ├── refactoring.todo ├── schemas ├── chapters.ts ├── hijir-date.ts ├── juzs.ts └── search-quran.ts ├── server ├── api │ ├── hijri-date.ts │ ├── quran │ │ ├── chapter │ │ │ ├── [chapter_id].ts │ │ │ ├── chapter_verses │ │ │ │ └── [chapter_id].ts │ │ │ └── recitations │ │ │ │ └── [chapter_id].ts │ │ ├── chapters.ts │ │ ├── juz │ │ │ └── id.ts │ │ └── juzs.ts │ └── search-quran.ts └── tsconfig.json ├── stores ├── audio-player.ts └── auth.ts ├── tailwind.config.ts ├── tsconfig.json ├── types ├── index.d.ts └── server-schemas │ ├── Chapter.d.ts │ └── hijri-date.d.ts └── utils ├── AudioPlayer.ts ├── Debug.ts ├── authHelpers.ts ├── generics.ts └── validators.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .nuxt 4 | .nitro 5 | .cache 6 | dist 7 | 8 | # Node dependencies 9 | node_modules 10 | 11 | # Logs 12 | logs 13 | *.log 14 | 15 | # Misc 16 | .DS_Store 17 | .fleet 18 | .idea 19 | 20 | # Local env files 21 | .env 22 | .env.* 23 | !.env.example 24 | 25 | 26 | service-account.json -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.localesPaths": [ 3 | "localizations" 4 | ], 5 | "i18n-ally.sourceLanguage": "en", 6 | "cSpell.words": [ 7 | "abdulbasit", 8 | "alqatami", 9 | "Basit", 10 | "containerheight", 11 | "minshawi", 12 | "murattal" 13 | ] 14 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Muslim 2 | App for every Muslim around the world 3 | -------------------------------------------------------------------------------- /app-refactoring.todo: -------------------------------------------------------------------------------- 1 | [1]: localizations (i18n/nuxt) ✅ 2 | [2]: ZOD ✅ 3 | [3]: Time Hijry ✅ 4 | [4]: Also make the cursor affect the circle shadow anywhere from the screen ✅ 5 | [5]: authentication system 6 | [6]: islamic radio [arabic + english] 7 | [7]: verse of the day 8 | [8]: prayers time + next pray counter 9 | [9]: hadith 10 | [10]: popular youtube channels to help the muslims through the path 11 | [11]: islamic resources (books, articles, libraries, ...etc) 12 | [12]: play the whole 13 | 14 | //-> i18n:extend-messages is deprecated. That hook will be removed feature at the time of the v8 official release. @nuxtjs/i18n 8:06:31 PM 15 | If you're using it, please use i18n:registerModule instead. 16 | 17 | // https://alquran.cloud/tajweed-guide -------------------------------------------------------------------------------- /app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | ui: { 3 | primary: 'green', 4 | gray: 'zinc', 5 | grayColors: ['slate', 'cool', 'zinc', 'neutral', 'stone'], 6 | 7 | dropdown: { 8 | container: 9 | 'max-h-[200px] overflow-x-hidden overflow-y-auto shadow-lg rounded-lg border border-slate-200 dark:border-slate-800 ring-1 ring-black ring-opacity-5', 10 | 11 | item: { 12 | icon: { 13 | base: '', 14 | active: '', 15 | inactive: '', 16 | }, 17 | }, 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 58 | 110 | -------------------------------------------------------------------------------- /assets/css/animation.css: -------------------------------------------------------------------------------- 1 | .slideIn-fade-enter-active, 2 | .slideIn-fade-leave-active { 3 | transition: all 0.3s ease-out; 4 | } 5 | 6 | .slideIn-fade-enter-from { 7 | transform: translateY(-5px) scale(0.6); 8 | opacity: 0; 9 | } 10 | 11 | .slideIn-fade-leave-to { 12 | opacity: 0; 13 | } 14 | 15 | .slide-fade-enter-active, 16 | .slide-fade-leave-active { 17 | transition: all 0.5s ease-in-out; 18 | } 19 | 20 | .slide-fade-enter-from, 21 | .slide-fade-leave-to { 22 | transform: scale(0.95); 23 | opacity: 0; 24 | } 25 | 26 | /* slide down */ 27 | .slide-down-enter-active, 28 | .slide-down-leave-active { 29 | transition: all 0.3s ease-out; 30 | } 31 | 32 | .slide-down-enter-from { 33 | transform: translateY(-50svh); 34 | opacity: 0; 35 | backdrop-filter: blur(100px); 36 | } 37 | 38 | .slide-down-leave-to { 39 | opacity: 0; 40 | backdrop-filter: blur(100px); 41 | } 42 | 43 | .island-pop-enter-active, 44 | .island-pop-leave-active { 45 | transition: all 0.3s ease; 46 | } 47 | 48 | .island-pop-enter-from { 49 | transform: scale(0.5) translateY(-100vh); 50 | } 51 | .island-pop-leave-to { 52 | z-index: 0; 53 | opacity: 0; 54 | transform: scale(0.8); 55 | } 56 | 57 | .island-pop-down-enter-active, 58 | .island-pop-down-leave-active { 59 | animation: heartbeat 1s infinite; 60 | } 61 | 62 | .island-pop-down-enter-from { 63 | transform: scale(0.5) translateY(100%); 64 | } 65 | .island-pop-down-leave-to { 66 | z-index: 0; 67 | opacity: 0; 68 | transform: scale(0.8); 69 | } 70 | 71 | @keyframes heartbeat { 72 | 0% { 73 | transform: scale(1); 74 | } 75 | 50% { 76 | transform: scale(1.2); 77 | } 78 | 100% { 79 | transform: scale(1); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /assets/css/core.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'KFGQPC Uthman Taha Naskh'; 3 | src: url(../fonts/UthmanTN1_Ver10.otf); 4 | font-weight: bold; 5 | } 6 | -------------------------------------------------------------------------------- /assets/css/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-tap-highlight-color: transparent; 3 | -webkit-touch-callout: none; 4 | } 5 | 6 | .font-quranic { 7 | font-family: 'KFGQPC Uthman Taha Naskh', sans-serif; 8 | } 9 | 10 | body { 11 | @apply font-sans; 12 | } 13 | -------------------------------------------------------------------------------- /assets/fonts/Al-Qalam-Quran.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedragab20/Muslim/69b6acfc9f68d4bbf47d3b47a83475ccb3cee65c/assets/fonts/Al-Qalam-Quran.ttf -------------------------------------------------------------------------------- /assets/fonts/UthmanTN1_Ver10.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedragab20/Muslim/69b6acfc9f68d4bbf47d3b47a83475ccb3cee65c/assets/fonts/UthmanTN1_Ver10.otf -------------------------------------------------------------------------------- /assets/fonts/quran-font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedragab20/Muslim/69b6acfc9f68d4bbf47d3b47a83475ccb3cee65c/assets/fonts/quran-font.ttf -------------------------------------------------------------------------------- /components/A3DCard.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 89 | 114 | -------------------------------------------------------------------------------- /components/About.vue: -------------------------------------------------------------------------------- 1 | 49 | -------------------------------------------------------------------------------- /components/App/Confirm.vue: -------------------------------------------------------------------------------- 1 | 50 | 98 | -------------------------------------------------------------------------------- /components/App/Divider.vue: -------------------------------------------------------------------------------- 1 | 7 | 12 | -------------------------------------------------------------------------------- /components/App/LanguageToggle.vue: -------------------------------------------------------------------------------- 1 | 11 | 24 | -------------------------------------------------------------------------------- /components/App/Search/Input.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 40 | -------------------------------------------------------------------------------- /components/App/Search/Results.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 206 | 207 | 212 | -------------------------------------------------------------------------------- /components/App/Search/Wrapper.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 187 | -------------------------------------------------------------------------------- /components/App/Spinner.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 29 | -------------------------------------------------------------------------------- /components/App/Toggle.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 85 | -------------------------------------------------------------------------------- /components/AudioPlayer.vue: -------------------------------------------------------------------------------- 1 | 189 | 190 | 567 | 580 | -------------------------------------------------------------------------------- /components/Auth/Banner.vue: -------------------------------------------------------------------------------- 1 | 22 | 31 | -------------------------------------------------------------------------------- /components/Auth/Land.vue: -------------------------------------------------------------------------------- 1 | 14 | 70 | -------------------------------------------------------------------------------- /components/Auth/Login.vue: -------------------------------------------------------------------------------- 1 | 73 | 120 | -------------------------------------------------------------------------------- /components/Auth/Registration.vue: -------------------------------------------------------------------------------- 1 | 73 | 113 | -------------------------------------------------------------------------------- /components/Base/Header.vue: -------------------------------------------------------------------------------- 1 | 73 | 154 | -------------------------------------------------------------------------------- /components/Base/Links.vue: -------------------------------------------------------------------------------- 1 | 17 | 110 | -------------------------------------------------------------------------------- /components/Base/Settings.vue: -------------------------------------------------------------------------------- 1 | 153 | 339 | -------------------------------------------------------------------------------- /components/ChapterQuickActions.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 66 | -------------------------------------------------------------------------------- /components/ColorsPlaceholder.vue: -------------------------------------------------------------------------------- 1 | 13 | 25 | -------------------------------------------------------------------------------- /components/OngoingMedia.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 100 | -------------------------------------------------------------------------------- /components/ProTips.vue: -------------------------------------------------------------------------------- 1 | 55 | 58 | -------------------------------------------------------------------------------- /components/Sections.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /composables/use-apis.ts: -------------------------------------------------------------------------------- 1 | // will keep this file accessible to everyone, in case someone else wanted to use the endpoints 2 | import { Generics } from '../utils/generics'; // this will be running on the server, that's why i imported it here 3 | 4 | export default () => { 5 | const HIJRI_DATE_API = (date: string) => { 6 | // date example: DD-MM-YYYY 7 | 8 | return `http://api.aladhan.com/v1/gToH/${date}`; 9 | }; 10 | const SEARCH_QURAN_BY_TERM_API = (query: string, page?: number, size?: number) => { 11 | return `https://api.quran.com/api/v4/search?q=${Generics.encodeArabicText(query)}&page=${ 12 | page! || 0 13 | }&size=${size! || 5}`; 14 | }; 15 | const AYAH_RECITATION_API = (ayah_key: string) => { 16 | //TODO: in the future, give the user ability to pick from list of reciters. 17 | return `https://api.quran.com/api/v4/recitations/2/by_ayah/${ayah_key}`; 18 | }; 19 | const JUZS_API = () => { 20 | return `https://api.quran.com/api/v4/juzs`; 21 | }; 22 | const CHAPTERS_API = () => { 23 | return `https://api.quran.com/api/v4/chapters`; 24 | }; 25 | const CHAPTER_RECITATION_API = (chapter: number) => { 26 | //TODO: in the future, give the user ability to pick from list of reciters. 27 | return `https://api.quran.com/api/v4/chapter_recitations/${chapter}`; 28 | }; 29 | const CHAPTERS_INFO_API = (chapter: number, english?: boolean) => { 30 | const baseUrl = `http://api.alquran.cloud/v1/surah/`; 31 | return english ? `${baseUrl}${chapter}/en.asad` : `${baseUrl}${chapter}`; 32 | }; 33 | 34 | return { 35 | HIJRI_DATE_API, 36 | SEARCH_QURAN_BY_TERM_API, 37 | AYAH_RECITATION_API, 38 | JUZS_API, 39 | CHAPTERS_API, 40 | CHAPTER_RECITATION_API, 41 | CHAPTERS_INFO_API, 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /composables/use-app-info.ts: -------------------------------------------------------------------------------- 1 | export default function useAppInfo() { 2 | const { locale } = useI18n(); 3 | const appDir = computed(() => (locale.value?.includes('ar') ? 'rtl' : 'ltr')); 4 | 5 | return { 6 | appDir, 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /composables/use-chapters.ts: -------------------------------------------------------------------------------- 1 | import quran from '~/db/quran.json'; 2 | import { Chapter } from '~/types/server-schemas/Chapter'; 3 | export const useChapters = () => { 4 | return quran.chapters as Chapter[]; 5 | }; 6 | -------------------------------------------------------------------------------- /composables/use-fetch-apis.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { 3 | chapterRecitationSchema, 4 | chapterSchema, 5 | chaptersSchema, 6 | verseSchema, 7 | } from '~/schemas/chapters'; 8 | import { dateSchema } from '~/schemas/hijir-date'; 9 | import { juzsSchema } from '~/schemas/juzs'; 10 | import { searchQuranSchema } from '~/schemas/search-quran'; 11 | export default () => { 12 | const fetchHijriDate = () => { 13 | try { 14 | const { data: date } = useLazyFetch('/api/hijri-date', { 15 | transform: (data) => dateSchema.parse(data), 16 | }); 17 | 18 | return date; 19 | } catch (error) { 20 | throw createError({ 21 | statusCode: 500, 22 | statusMessage: 'Count not fetch data', 23 | }); 24 | } 25 | }; 26 | const fetchQuranByTerm = async (term: string, page?: number, size?: number) => { 27 | try { 28 | const results = await $fetch(`/api/search-quran`, { 29 | params: { 30 | query: term, 31 | page: page! || 0, 32 | size: size! || 5, 33 | }, 34 | }); 35 | 36 | return searchQuranSchema.parse(results); 37 | } catch (error) { 38 | throw createError({ 39 | statusCode: 500, 40 | statusMessage: 'Count not fetch data', 41 | }); 42 | } 43 | }; 44 | 45 | const fetchJuzs = async () => { 46 | try { 47 | const { data } = await useFetch('/api/quran/juzs', { 48 | transform: (data) => juzsSchema.parse(data), 49 | }); 50 | 51 | return data.value; 52 | } catch (error) { 53 | throw createError({ 54 | statusCode: 500, 55 | statusMessage: 'Count not fetch Juz data', 56 | }); 57 | } 58 | }; 59 | 60 | const fetchChapters = async () => { 61 | try { 62 | const { data } = await useFetch('/api/quran/chapters', { 63 | key: 'chapters', 64 | transform: (data) => chaptersSchema.parse(data), 65 | }); 66 | 67 | return data.value; 68 | } catch (error) { 69 | throw createError({ 70 | statusCode: 500, 71 | statusMessage: 'Count not fetch Chapter data', 72 | }); 73 | } 74 | }; 75 | 76 | /** 77 | * @todo: play verse audio 78 | * @todo: copy verse 79 | */ 80 | 81 | const fetchChapter = async (chapter_id: number) => { 82 | try { 83 | const { data } = await useFetch(`/api/quran/chapter/${chapter_id}`, { 84 | key: `chapter-${chapter_id}`, 85 | transform: (data) => chapterSchema.parse(data), 86 | }); 87 | 88 | return data; 89 | } catch (error) { 90 | throw createError({ 91 | statusCode: 500, 92 | statusMessage: 'Count not fetch Chapter data', 93 | }); 94 | } 95 | }; 96 | 97 | const fetchChapterInfo = async (chapter_id: number) => { 98 | try { 99 | const { data } = await useFetch(`/api/quran/chapter/chapter_verses/${chapter_id}`, { 100 | key: `chapter-info-${chapter_id}`, 101 | transform: (data) => z.array(verseSchema).parse(data), 102 | }); 103 | 104 | return data.value; 105 | } catch (error) { 106 | throw createError({ 107 | statusCode: 500, 108 | statusMessage: 'Count not fetch Chapter info', 109 | }); 110 | } 111 | }; 112 | const fetchQuranRecitations = async (chapter_id: number) => { 113 | try { 114 | const { data } = await useFetch(`/api/quran/chapter/recitations/${chapter_id}`, { 115 | key: `recitation-${chapter_id}`, 116 | transform: (data) => z.array(chapterRecitationSchema).parse(data), 117 | }); 118 | 119 | return data.value; 120 | } catch (error) { 121 | throw createError({ 122 | statusCode: 500, 123 | statusMessage: 'Count not fetch Chapter info', 124 | }); 125 | } 126 | }; 127 | 128 | return { 129 | fetchHijriDate, 130 | fetchQuranByTerm, 131 | fetchJuzs, 132 | fetchChapters, 133 | fetchChapter, 134 | fetchChapterInfo, 135 | fetchQuranRecitations, 136 | }; 137 | }; 138 | -------------------------------------------------------------------------------- /composables/use-guest.ts: -------------------------------------------------------------------------------- 1 | import { useAuthStore } from '~/stores/auth'; 2 | export default function () { 3 | const authStore = useAuthStore(); 4 | const user = useCurrentUser()?.value?.uid; 5 | 6 | function clickHandler(fn?: Function): void { 7 | if (!user) { 8 | authStore.chooseAuthLand('login'); 9 | authStore.toggleAuthLanded(true); 10 | 11 | return; 12 | } 13 | 14 | fn?.(); 15 | } 16 | 17 | return { 18 | clickHandler, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /composables/use-quran-reciters.ts: -------------------------------------------------------------------------------- 1 | // Credits: https://quranicaudio.com/ 2 | 3 | export default () => { 4 | const abdulbasit_mujawwad = (surah: number) => { 5 | if (!surah || surah < 1 || surah > 114) 6 | throw createError({ 7 | statusCode: 400, 8 | statusMessage: 'Surah number is invalid', 9 | }); 10 | 11 | // number format: 0 12 | return { 13 | reciter_id: 'abdulbasit_mujawwad', 14 | name: { 15 | en: 'Abdul Basit Abdul Samad', 16 | ar: 'عبد الباسط عبد الصمد', 17 | }, 18 | type: { 19 | en: 'Mujawwad', 20 | ar: 'مجود', 21 | }, 22 | url: `https://download.quranicaudio.com/qdc/abdul_baset/mujawwad/${surah}.mp3`, 23 | poster: '/reciters/Abdelbasset-abdessamad.jpeg', 24 | }; 25 | }; 26 | const abdulbasit_murattal = (surah: number) => { 27 | if (!surah || surah < 1 || surah > 114) 28 | throw createError({ 29 | statusCode: 400, 30 | statusMessage: 'Surah number is invalid', 31 | }); 32 | 33 | // number format: 0 34 | return { 35 | reciter_id: 'abdulbasit_murattal', 36 | name: { 37 | en: 'Abdul Basit Abdul Samad', 38 | ar: 'عبد الباسط عبد الصمد', 39 | }, 40 | type: { 41 | en: 'Murattal', 42 | ar: 'مرتل', 43 | }, 44 | url: `https://download.quranicaudio.com/qdc/abdul_baset/murattal/${surah}.mp3`, 45 | poster: '/reciters/Abdelbasset-abdessamad.jpeg', 46 | }; 47 | }; 48 | 49 | const hosari = (surah: number) => { 50 | if (!surah || surah < 1 || surah > 114) 51 | throw createError({ 52 | statusCode: 400, 53 | statusMessage: 'Surah number is invalid', 54 | }); 55 | 56 | // number format: 000 57 | const surahNumber = surah.toString().padStart(3, '0'); 58 | return { 59 | reciter_id: 'hosari_mujawwad', 60 | name: { 61 | en: 'Mahmoud Khaleel Al-Husary', 62 | ar: 'محمود خليل الحصري', 63 | }, 64 | type: { 65 | en: 'Mujawwad', 66 | ar: 'مجود', 67 | }, 68 | url: `https://download.quranicaudio.com/quran/mahmood_khaleel_al-husaree_iza3a/${surahNumber}.mp3`, 69 | poster: '/reciters/al-hosari.jpeg', 70 | }; 71 | }; 72 | const minshawi_murattal = (surah: number) => { 73 | if (!surah || surah < 1 || surah > 114) 74 | throw createError({ 75 | statusCode: 400, 76 | statusMessage: 'Surah number is invalid', 77 | }); 78 | 79 | // number format: 000 80 | const surahNumber = surah.toString().padStart(3, '0'); 81 | return { 82 | reciter_id: 'minshawi_murattal', 83 | name: { 84 | en: 'Muhammad Siddiq Al-Minshawi', 85 | ar: 'محمد صديق المنشاوي', 86 | }, 87 | type: { 88 | en: 'Murattal', 89 | ar: 'مرتل', 90 | }, 91 | url: `https://download.quranicaudio.com/quran/muhammad_siddeeq_al-minshaawee/${surahNumber}.mp3`, 92 | poster: '/reciters/minshawi.jpeg', 93 | }; 94 | }; 95 | const minshawi_mujawwad = (surah: number) => { 96 | if (!surah || surah < 1 || surah > 114) 97 | throw createError({ 98 | statusCode: 400, 99 | statusMessage: 'Surah number is invalid', 100 | }); 101 | 102 | // number format: 000 103 | const surahNumber = surah.toString().padStart(3, '0'); 104 | return { 105 | reciter_id: 'minshawi_mujawwad', 106 | name: { 107 | en: 'Muhammad Siddiq Al-Minshawi', 108 | ar: 'محمد صديق المنشاوي', 109 | }, 110 | type: { 111 | en: 'Mujawwad', 112 | ar: 'مجود', 113 | }, 114 | url: `https://download.quranicaudio.com/quran/minshawi_mujawwad/${surahNumber}.mp3`, 115 | poster: '/reciters/minshawi.jpeg', 116 | }; 117 | }; 118 | const alqatami = (surah: number) => { 119 | if (!surah || surah < 1 || surah > 114) 120 | throw createError({ 121 | statusCode: 400, 122 | statusMessage: 'Surah number is invalid', 123 | }); 124 | 125 | // number format: 000 126 | const surahNumber = surah.toString().padStart(3, '0'); 127 | return { 128 | reciter_id: 'alqatami_murattal', 129 | name: { 130 | en: 'Nasser Alqatami', 131 | ar: 'ناصر القطامي', 132 | }, 133 | type: { 134 | en: 'Murattal', 135 | ar: 'مرتل', 136 | }, 137 | url: `https://download.quranicaudio.com/quran/nasser_bin_ali_alqatami/${surahNumber}.mp3`, 138 | poster: '/reciters/alqatami.jpeg', 139 | }; 140 | }; 141 | const afasy = (surah: number) => { 142 | if (!surah || surah < 1 || surah > 114) 143 | throw createError({ 144 | statusCode: 400, 145 | statusMessage: 'Surah number is invalid', 146 | }); 147 | 148 | // number format: 000 149 | const surahNumber = surah.toString().padStart(3, '0'); 150 | return { 151 | reciter_id: 'afasy_murattal', 152 | name: { 153 | en: 'Mishary Alafasy', 154 | ar: 'مشاري العفاسي', 155 | }, 156 | type: { 157 | en: 'Murattal', 158 | ar: 'مرتل', 159 | }, 160 | url: `https://download.quranicaudio.com/quran/mishaari_raashid_al_3afaasee/${surahNumber}.mp3`, 161 | poster: '/reciters/afasy.jpeg', 162 | }; 163 | }; 164 | 165 | return { 166 | abdulbasit_mujawwad, 167 | abdulbasit_murattal, 168 | hosari, 169 | minshawi_mujawwad, 170 | minshawi_murattal, 171 | alqatami, 172 | afasy, 173 | }; 174 | }; 175 | -------------------------------------------------------------------------------- /db/quran.json: -------------------------------------------------------------------------------- 1 | { 2 | "chapters": [ 3 | { 4 | "number": 1, 5 | "arabic": "الفاتحة", 6 | "arabicWithTashkeel": "الْفَاتِحَة", 7 | "english": "Al-Fātiĥah", 8 | "englishTranslated": "The Opening" 9 | }, 10 | { 11 | "number": 2, 12 | "arabic": "البقرة", 13 | "arabicWithTashkeel": "الْبَقَرَة", 14 | "english": "Al-Baqarah", 15 | "englishTranslated": "The Cow" 16 | }, 17 | { 18 | "number": 3, 19 | "arabic": "آل عمران", 20 | "arabicWithTashkeel": "آلِ عِمْرَان", 21 | "english": "Āli Imrān", 22 | "englishTranslated": "Family of Imran" 23 | }, 24 | { 25 | "number": 4, 26 | "arabic": "النساء", 27 | "arabicWithTashkeel": "النِّسَاء", 28 | "english": "An-Nisā'", 29 | "englishTranslated": "The Women" 30 | }, 31 | { 32 | "number": 5, 33 | "arabic": "المائدة", 34 | "arabicWithTashkeel": "الْمَائِدَة", 35 | "english": "Al-Mā'idah", 36 | "englishTranslated": "The Table Spread" 37 | }, 38 | { 39 | "number": 6, 40 | "arabic": "الأنعام", 41 | "arabicWithTashkeel": "الْأَنْعَام", 42 | "english": "Al-'Anām", 43 | "englishTranslated": "The Cattle" 44 | }, 45 | { 46 | "number": 7, 47 | "arabic": "الأعراف", 48 | "arabicWithTashkeel": "الْأَعْرَاف", 49 | "english": "Al-'A`rāf", 50 | "englishTranslated": "The Heights" 51 | }, 52 | { 53 | "number": 8, 54 | "arabic": "الأنفال", 55 | "arabicWithTashkeel": "الْأَنْفَال", 56 | "english": "Al-'Anfāl", 57 | "englishTranslated": "The Spoils of War" 58 | }, 59 | { 60 | "number": 9, 61 | "arabic": "التوبة", 62 | "arabicWithTashkeel": "التَّوْبَة", 63 | "english": "At-Tawbah", 64 | "englishTranslated": "The Repentance" 65 | }, 66 | { 67 | "number": 10, 68 | "arabic": "يونس", 69 | "arabicWithTashkeel": "يُونُس", 70 | "english": "Yūnus", 71 | "englishTranslated": "Jonah" 72 | }, 73 | { 74 | "number": 11, 75 | "arabic": "هود", 76 | "arabicWithTashkeel": "هُود", 77 | "english": "Hūd", 78 | "englishTranslated": "Hud" 79 | }, 80 | { 81 | "number": 12, 82 | "arabic": "يوسف", 83 | "arabicWithTashkeel": "يُوسُف", 84 | "english": "Yūsuf", 85 | "englishTranslated": "Joseph" 86 | }, 87 | { 88 | "number": 13, 89 | "arabic": "الرعد", 90 | "arabicWithTashkeel": "الرَّعْد", 91 | "english": "Ar-Ra`d", 92 | "englishTranslated": "The Thunder" 93 | }, 94 | { 95 | "number": 14, 96 | "arabic": "ابراهيم", 97 | "arabicWithTashkeel": "إِبْرَاهِيم", 98 | "english": "Ibrāhīm", 99 | "englishTranslated": "Abraham" 100 | }, 101 | { 102 | "number": 15, 103 | "arabic": "الحجر", 104 | "arabicWithTashkeel": "الْحِجْر", 105 | "english": "Al-Ĥijr", 106 | "englishTranslated": "The Rocky Tract" 107 | }, 108 | { 109 | "number": 16, 110 | "arabic": "النحل", 111 | "arabicWithTashkeel": "النَّحْل", 112 | "english": "An-Naĥl", 113 | "englishTranslated": "The Bee" 114 | }, 115 | { 116 | "number": 17, 117 | "arabic": "الإسراء", 118 | "arabicWithTashkeel": "الْإِسْرَاء", 119 | "english": "Al-'Isrā'", 120 | "englishTranslated": "The Night Journey" 121 | }, 122 | { 123 | "number": 18, 124 | "arabic": "الكهف", 125 | "arabicWithTashkeel": "الْكَهْف", 126 | "english": "Al-Kahf", 127 | "englishTranslated": "The Cave" 128 | }, 129 | { 130 | "number": 19, 131 | "arabic": "مريم", 132 | "arabicWithTashkeel": "مَرْيَم", 133 | "english": "Maryam", 134 | "englishTranslated": "Mary" 135 | }, 136 | { 137 | "number": 20, 138 | "arabic": "طه", 139 | "arabicWithTashkeel": "طه", 140 | "english": "Ţāhā", 141 | "englishTranslated": "Ta-Ha" 142 | }, 143 | { 144 | "number": 21, 145 | "arabic": "الأنبياء", 146 | "arabicWithTashkeel": "الْأَنْبِيَاء", 147 | "english": "Al-'Anbiyā'", 148 | "englishTranslated": "The Prophets" 149 | }, 150 | { 151 | "number": 22, 152 | "arabic": "الحج", 153 | "arabicWithTashkeel": "الْحَجّ", 154 | "english": "Al-Ĥajj", 155 | "englishTranslated": "The Pilgrimage" 156 | }, 157 | { 158 | "number": 23, 159 | "arabic": "المؤمنون", 160 | "arabicWithTashkeel": "الْمُؤْمِنُون", 161 | "english": "Al-Mu'minūn", 162 | "englishTranslated": "The Believers" 163 | }, 164 | { 165 | "number": 24, 166 | "arabic": "النور", 167 | "arabicWithTashkeel": "النُّور", 168 | "english": "An-Nūr", 169 | "englishTranslated": "The Light" 170 | }, 171 | { 172 | "number": 25, 173 | "arabic": "الفرقان", 174 | "arabicWithTashkeel": "الْفُرْقَان", 175 | "english": "Al-Furqān", 176 | "englishTranslated": "The Criterion" 177 | }, 178 | { 179 | "number": 26, 180 | "arabic": "الشعراء", 181 | "arabicWithTashkeel": "الشُّعَرَاء", 182 | "english": "Ash-Shu`arā'", 183 | "englishTranslated": "The Poets" 184 | }, 185 | { 186 | "number": 27, 187 | "arabic": "النمل", 188 | "arabicWithTashkeel": "النَّمْل", 189 | "english": "An-Naml", 190 | "englishTranslated": "The Ant" 191 | }, 192 | { 193 | "number": 28, 194 | "arabic": "القصص", 195 | "arabicWithTashkeel": "الْقَصَص", 196 | "english": "Al-Qaşaş", 197 | "englishTranslated": "The Stories" 198 | }, 199 | { 200 | "number": 29, 201 | "arabic": "العنكبوت", 202 | "arabicWithTashkeel": "الْعَنْكَبُوت", 203 | "english": "Al-`Ankabūt", 204 | "englishTranslated": "The Spider" 205 | }, 206 | { 207 | "number": 30, 208 | "arabic": "الروم", 209 | "arabicWithTashkeel": "الرُّوم", 210 | "english": "Ar-Rūm", 211 | "englishTranslated": "The Romans" 212 | }, 213 | { 214 | "number": 31, 215 | "arabic": "لقمان", 216 | "arabicWithTashkeel": "لُقْمَان", 217 | "english": "Luqmān", 218 | "englishTranslated": "Luqman" 219 | }, 220 | { 221 | "number": 32, 222 | "arabic": "السجدة", 223 | "arabicWithTashkeel": "السَّجْدَة", 224 | "english": "As-Sajdah", 225 | "englishTranslated": "The Prostration" 226 | }, 227 | { 228 | "number": 33, 229 | "arabic": "الأحزاب", 230 | "arabicWithTashkeel": "الْأَحْزَاب", 231 | "english": "Al-'Aĥzāb", 232 | "englishTranslated": "The Combined Forces" 233 | }, 234 | { 235 | "number": 34, 236 | "arabic": "سبأ", 237 | "arabicWithTashkeel": "سَبَإ", 238 | "english": "Sabā'", 239 | "englishTranslated": "Sheba" 240 | }, 241 | { 242 | "number": 35, 243 | "arabic": "فاطر", 244 | "arabicWithTashkeel": "فَاطِر", 245 | "english": "Fāţir", 246 | "englishTranslated": "The Originator" 247 | }, 248 | { 249 | "number": 36, 250 | "arabic": "يس", 251 | "arabicWithTashkeel": "يس", 252 | "english": "Yā-Sīn", 253 | "englishTranslated": "Ya Sin" 254 | }, 255 | { 256 | "number": 37, 257 | "arabic": "الصافات", 258 | "arabicWithTashkeel": "الصَّافَّات", 259 | "english": "Aş-Şāffāt", 260 | "englishTranslated": "Those who set the Ranks" 261 | }, 262 | { 263 | "number": 38, 264 | "arabic": "ص", 265 | "arabicWithTashkeel": "ص", 266 | "english": "Şād", 267 | "englishTranslated": "Sad" 268 | }, 269 | { 270 | "number": 39, 271 | "arabic": "الزمر", 272 | "arabicWithTashkeel": "الزُّمَر", 273 | "english": "Az-Zumar", 274 | "englishTranslated": "The Troops" 275 | }, 276 | { 277 | "number": 40, 278 | "arabic": "غافر", 279 | "arabicWithTashkeel": "غَافِر", 280 | "english": "Ghāfir", 281 | "englishTranslated": "The Forgiver" 282 | }, 283 | { 284 | "number": 41, 285 | "arabic": "فصلت", 286 | "arabicWithTashkeel": "فُصِّلَتْ", 287 | "english": "Fuşşilat", 288 | "englishTranslated": "Explained in Detail" 289 | }, 290 | { 291 | "number": 42, 292 | "arabic": "الشورى", 293 | "arabicWithTashkeel": "الشُّورَى", 294 | "english": "Ash-Shūrā", 295 | "englishTranslated": "The Consultation" 296 | }, 297 | { 298 | "number": 43, 299 | "arabic": "الزخرف", 300 | "arabicWithTashkeel": "الزُّخْرُف", 301 | "english": "Az-Zukhruf", 302 | "englishTranslated": "The Gold Adornments" 303 | }, 304 | { 305 | "number": 44, 306 | "arabic": "الدخان", 307 | "arabicWithTashkeel": "الدُّخَان", 308 | "english": "Ad-Dukhān", 309 | "englishTranslated": "The Smoke" 310 | }, 311 | { 312 | "number": 45, 313 | "arabic": "الجاثية", 314 | "arabicWithTashkeel": "الْجَاثِيَة", 315 | "english": "Al-Jāthiyah", 316 | "englishTranslated": "The Crouching" 317 | }, 318 | { 319 | "number": 46, 320 | "arabic": "الأحقاف", 321 | "arabicWithTashkeel": "الْأَحْقَاف", 322 | "english": "Al-'Aĥqāf", 323 | "englishTranslated": "The Wind-Curved Sandhills" 324 | }, 325 | { 326 | "number": 47, 327 | "arabic": "محمد", 328 | "arabicWithTashkeel": "مُحَمَّد", 329 | "english": "Muĥammad", 330 | "englishTranslated": "Muhammad" 331 | }, 332 | { 333 | "number": 48, 334 | "arabic": "الفتح", 335 | "arabicWithTashkeel": "الْفَتْح", 336 | "english": "Al-Fatĥ", 337 | "englishTranslated": "The Victory" 338 | }, 339 | { 340 | "number": 49, 341 | "arabic": "الحجرات", 342 | "arabicWithTashkeel": "الْحُجُرَات", 343 | "english": "Al-Ĥujurāt", 344 | "englishTranslated": "The Rooms" 345 | }, 346 | { 347 | "number": 50, 348 | "arabic": "ق", 349 | "arabicWithTashkeel": "ق", 350 | "english": "Qāf", 351 | "englishTranslated": "Qaf" 352 | }, 353 | { 354 | "number": 51, 355 | "arabic": "الذاريات", 356 | "arabicWithTashkeel": "الذَّارِيَات", 357 | "english": "Adh-Dhāriyāt", 358 | "englishTranslated": "The Winnowing Winds" 359 | }, 360 | { 361 | "number": 52, 362 | "arabic": "الطور", 363 | "arabicWithTashkeel": "الطُّور", 364 | "english": "Aţ-Ţūr", 365 | "englishTranslated": "The Mount" 366 | }, 367 | { 368 | "number": 53, 369 | "arabic": "النجم", 370 | "arabicWithTashkeel": "النَّجْم", 371 | "english": "An-Najm", 372 | "englishTranslated": "The Star" 373 | }, 374 | { 375 | "number": 54, 376 | "arabic": "القمر", 377 | "arabicWithTashkeel": "الْقَمَر", 378 | "english": "Al-Qamar", 379 | "englishTranslated": "The Moon" 380 | }, 381 | { 382 | "number": 55, 383 | "arabic": "الرحمن", 384 | "arabicWithTashkeel": "الرَّحْمَٰن", 385 | "english": "Ar-Raĥmān", 386 | "englishTranslated": "The Beneficent" 387 | }, 388 | { 389 | "number": 56, 390 | "arabic": "الواقعة", 391 | "arabicWithTashkeel": "الْوَاقِعَة", 392 | "english": "Al-Wāqi`ah", 393 | "englishTranslated": "The Inevitable" 394 | }, 395 | { 396 | "number": 57, 397 | "arabic": "الحديد", 398 | "arabicWithTashkeel": "الْحَدِيد", 399 | "english": "Al-Ĥadīd", 400 | "englishTranslated": "The Iron" 401 | }, 402 | { 403 | "number": 58, 404 | "arabic": "المجادلة", 405 | "arabicWithTashkeel": "الْمُجَادَلَة", 406 | "english": "Al-Mujādila", 407 | "englishTranslated": "The Pleading Woman" 408 | }, 409 | { 410 | "number": 59, 411 | "arabic": "الحشر", 412 | "arabicWithTashkeel": "الْحَشْر", 413 | "english": "Al-Ĥashr", 414 | "englishTranslated": "The Exile" 415 | }, 416 | { 417 | "number": 60, 418 | "arabic": "الممتحنة", 419 | "arabicWithTashkeel": "الْمُمْتَحَنَة", 420 | "english": "Al-Mumtaĥanah", 421 | "englishTranslated": "She that is to be examined" 422 | }, 423 | { 424 | "number": 61, 425 | "arabic": "الصف", 426 | "arabicWithTashkeel": "الصَّفّ", 427 | "english": "Aş-Şaf", 428 | "englishTranslated": "The Ranks" 429 | }, 430 | { 431 | "number": 62, 432 | "arabic": "الجمعة", 433 | "arabicWithTashkeel": "الْجُمُعَة", 434 | "english": "Al-Jumu`ah", 435 | "englishTranslated": "Friday" 436 | }, 437 | { 438 | "number": 63, 439 | "arabic": "المنافقون", 440 | "arabicWithTashkeel": "الْمُنَافِقُونَ", 441 | "english": "Al-Munāfiqūn", 442 | "englishTranslated": "The Hypocrites" 443 | }, 444 | { 445 | "number": 64, 446 | "arabic": "التغابن", 447 | "arabicWithTashkeel": "التَّغَابُن", 448 | "english": "At-Taghābun", 449 | "englishTranslated": "Mutual Disillusion" 450 | }, 451 | { 452 | "number": 65, 453 | "arabic": "الطلاق", 454 | "arabicWithTashkeel": "الطَّلَاق", 455 | "english": "Aţ-Ţalāq", 456 | "englishTranslated": "Divorce" 457 | }, 458 | { 459 | "number": 66, 460 | "arabic": "التحريم", 461 | "arabicWithTashkeel": "التَّحْرِيم", 462 | "english": "At-Taĥrīm", 463 | "englishTranslated": "The Prohibition" 464 | }, 465 | { 466 | "number": 67, 467 | "arabic": "الملك", 468 | "arabicWithTashkeel": "الْمُلْك", 469 | "english": "Al-Mulk", 470 | "englishTranslated": "The Sovereignty" 471 | }, 472 | { 473 | "number": 68, 474 | "arabic": "القلم", 475 | "arabicWithTashkeel": "الْقَلَم", 476 | "english": "Al-Qalam", 477 | "englishTranslated": "The Pen" 478 | }, 479 | { 480 | "number": 69, 481 | "arabic": "الحاقة", 482 | "arabicWithTashkeel": "الْحَاقَّة", 483 | "english": "Al-Ĥāqqah", 484 | "englishTranslated": "The Reality" 485 | }, 486 | { 487 | "number": 70, 488 | "arabic": "المعارج", 489 | "arabicWithTashkeel": "الْمَعَارِج", 490 | "english": "Al-Ma`ārij", 491 | "englishTranslated": "The Ascending Stairways" 492 | }, 493 | { 494 | "number": 71, 495 | "arabic": "نوح", 496 | "arabicWithTashkeel": "نُوح", 497 | "english": "Nūĥ", 498 | "englishTranslated": "Noah" 499 | }, 500 | { 501 | "number": 72, 502 | "arabic": "الجن", 503 | "arabicWithTashkeel": "الْجِنّ", 504 | "english": "Al-Jinn", 505 | "englishTranslated": "The Jinn" 506 | }, 507 | { 508 | "number": 73, 509 | "arabic": "المزمل", 510 | "arabicWithTashkeel": "الْمُزَّمِّل", 511 | "english": "Al-Muzzammil", 512 | "englishTranslated": "The Enshrouded One" 513 | }, 514 | { 515 | "number": 74, 516 | "arabic": "المدثر", 517 | "arabicWithTashkeel": "الْمُدَّثِّر", 518 | "english": "Al-Muddathir", 519 | "englishTranslated": "The Cloaked One" 520 | }, 521 | { 522 | "number": 75, 523 | "arabic": "القيامة", 524 | "arabicWithTashkeel": "الْقِيَامَة", 525 | "english": "Al-Qiyāmah", 526 | "englishTranslated": "The Resurrection" 527 | }, 528 | { 529 | "number": 76, 530 | "arabic": "الإنسان", 531 | "arabicWithTashkeel": "الْإِنسَان", 532 | "english": "Al-'Insān", 533 | "englishTranslated": "Man" 534 | }, 535 | { 536 | "number": 77, 537 | "arabic": "المرسلات", 538 | "arabicWithTashkeel": "الْمُرْسَلَات", 539 | "english": "Al-Mursalāt", 540 | "englishTranslated": "The Emissaries" 541 | }, 542 | { 543 | "number": 78, 544 | "arabic": "النبإ", 545 | "arabicWithTashkeel": "النَّبَأ", 546 | "english": "An-Naba'", 547 | "englishTranslated": "The Tidings" 548 | }, 549 | { 550 | "number": 79, 551 | "arabic": "النازعات", 552 | "arabicWithTashkeel": "النَّازِعَات", 553 | "english": "An-Nāzi`āt", 554 | "englishTranslated": "Those who drag forth" 555 | }, 556 | { 557 | "number": 80, 558 | "arabic": "عبس", 559 | "arabicWithTashkeel": "عَبَسَ", 560 | "english": "Abasa", 561 | "englishTranslated": "He frowned" 562 | }, 563 | { 564 | "number": 81, 565 | "arabic": "التكوير", 566 | "arabicWithTashkeel": "التَّكْوِير", 567 | "english": "At-Takwīr", 568 | "englishTranslated": "The Overthrowing" 569 | }, 570 | { 571 | "number": 82, 572 | "arabic": "الإنفطار", 573 | "arabicWithTashkeel": "الْإِنْفِطَار", 574 | "english": "Al-'Infiţār", 575 | "englishTranslated": "The Cleaving" 576 | }, 577 | { 578 | "number": 83, 579 | "arabic": "المطففين", 580 | "arabicWithTashkeel": "الْمُطَفِّفِين", 581 | "english": "Al-Muţaffifīn", 582 | "englishTranslated": "Defrauding" 583 | }, 584 | { 585 | "number": 84, 586 | "arabic": "الإنشقاق", 587 | "arabicWithTashkeel": "الْإِنْشِقَاق", 588 | "english": "Al-'Inshiqāq", 589 | "englishTranslated": "The Splitting Open" 590 | }, 591 | { 592 | "number": 85, 593 | "arabic": "البروج", 594 | "arabicWithTashkeel": "الْبُرُوج", 595 | "english": "Al-Burūj", 596 | "englishTranslated": "The Mansions of the Stars" 597 | }, 598 | { 599 | "number": 86, 600 | "arabic": "الطارق", 601 | "arabicWithTashkeel": "الطَّارِق", 602 | "english": "Aţ-Ţāriq", 603 | "englishTranslated": "The Morning Star" 604 | }, 605 | { 606 | "number": 87, 607 | "arabic": "الأعلى", 608 | "arabicWithTashkeel": "الْأَعْلى", 609 | "english": "Al-'A`lá", 610 | "englishTranslated": "The Most High" 611 | }, 612 | { 613 | "number": 88, 614 | "arabic": "الغاشية", 615 | "arabicWithTashkeel": "الْغَاشِيَة", 616 | "english": "Al-Ghāshiyah", 617 | "englishTranslated": "The Overwhelming" 618 | }, 619 | { 620 | "number": 89, 621 | "arabic": "الفجر", 622 | "arabicWithTashkeel": "الْفَجْر", 623 | "english": "Al-Fajr", 624 | "englishTranslated": "The Dawn" 625 | }, 626 | { 627 | "number": 90, 628 | "arabic": "البلد", 629 | "arabicWithTashkeel": "الْبَلَد", 630 | "english": "Al-Balad", 631 | "englishTranslated": "The City" 632 | }, 633 | { 634 | "number": 91, 635 | "arabic": "الشمس", 636 | "arabicWithTashkeel": "الشَّمْس", 637 | "english": "Ash-Shams", 638 | "englishTranslated": "The Sun" 639 | }, 640 | { 641 | "number": 92, 642 | "arabic": "الليل", 643 | "arabicWithTashkeel": "اللَّيْل", 644 | "english": "Al-Layl", 645 | "englishTranslated": "The Night" 646 | }, 647 | { 648 | "number": 93, 649 | "arabic": "الضحى", 650 | "arabicWithTashkeel": "الضُّحَىٰ", 651 | "english": "Ađ-Đuĥaá", 652 | "englishTranslated": "The Morning Hours" 653 | }, 654 | { 655 | "number": 94, 656 | "arabic": "الشرح", 657 | "arabicWithTashkeel": "الشَّرْح", 658 | "english": "Ash-Sharĥ", 659 | "englishTranslated": "The Relief" 660 | }, 661 | { 662 | "number": 95, 663 | "arabic": "التين", 664 | "arabicWithTashkeel": "التِّين", 665 | "english": "At-Tīn", 666 | "englishTranslated": "The Fig" 667 | }, 668 | { 669 | "number": 96, 670 | "arabic": "العلق", 671 | "arabicWithTashkeel": "الْعَلَق", 672 | "english": "Al-`Alaq", 673 | "englishTranslated": "The Clot" 674 | }, 675 | { 676 | "number": 97, 677 | "arabic": "القدر", 678 | "arabicWithTashkeel": "الْقَدْر", 679 | "english": "Al-Qadr", 680 | "englishTranslated": "The Power" 681 | }, 682 | { 683 | "number": 98, 684 | "arabic": "البينة", 685 | "arabicWithTashkeel": "الْبَيِّنَة", 686 | "english": "Al-Bayyinah", 687 | "englishTranslated": "The Clear Proof" 688 | }, 689 | { 690 | "number": 99, 691 | "arabic": "الزلزلة", 692 | "arabicWithTashkeel": "الزَّلْزَلَة", 693 | "english": "Az-Zalzalah", 694 | "englishTranslated": "The Earthquake" 695 | }, 696 | { 697 | "number": 100, 698 | "arabic": "العاديات", 699 | "arabicWithTashkeel": "الْعَادِيَات", 700 | "english": "Al-`Ādiyāt", 701 | "englishTranslated": "The Chargers" 702 | }, 703 | { 704 | "number": 101, 705 | "arabic": "القارعة", 706 | "arabicWithTashkeel": "الْقَارِعَة", 707 | "english": "Al-Qāri`ah", 708 | "englishTranslated": "The Calamity" 709 | }, 710 | { 711 | "number": 102, 712 | "arabic": "التكاثر", 713 | "arabicWithTashkeel": "التَّكَاثُر", 714 | "english": "At-Takāthur", 715 | "englishTranslated": "The Rivalry in worldly increase" 716 | }, 717 | { 718 | "number": 103, 719 | "arabic": "العصر", 720 | "arabicWithTashkeel": "الْعَصْر", 721 | "english": "Al-`Aşr", 722 | "englishTranslated": "The Declining Day" 723 | }, 724 | { 725 | "number": 104, 726 | "arabic": "الهمزة", 727 | "arabicWithTashkeel": "الْهُمَزَة", 728 | "english": "Al-Humazah", 729 | "englishTranslated": "The Traducer" 730 | }, 731 | { 732 | "number": 105, 733 | "arabic": "الفيل", 734 | "arabicWithTashkeel": "الْفِيل", 735 | "english": "Al-Fīl", 736 | "englishTranslated": "The Elephant" 737 | }, 738 | { 739 | "number": 106, 740 | "arabic": "قريش", 741 | "arabicWithTashkeel": "قُرَيْش", 742 | "english": "Quraysh", 743 | "englishTranslated": "Quraysh" 744 | }, 745 | { 746 | "number": 107, 747 | "arabic": "الماعون", 748 | "arabicWithTashkeel": "الْمَاعُونَ", 749 | "english": "Al-Mā`ūn", 750 | "englishTranslated": "The Small Kindnesses" 751 | }, 752 | { 753 | "number": 108, 754 | "arabic": "الكوثر", 755 | "arabicWithTashkeel": "الْكَوْثَر", 756 | "english": "Al-Kawthar", 757 | "englishTranslated": "The Abundance" 758 | }, 759 | { 760 | "number": 109, 761 | "arabic": "الكافرون", 762 | "arabicWithTashkeel": "الْكَافِرُونَ", 763 | "english": "Al-Kāfirūn", 764 | "englishTranslated": "The Disbelievers" 765 | }, 766 | { 767 | "number": 110, 768 | "arabic": "النصر", 769 | "arabicWithTashkeel": "النَّصْر", 770 | "english": "An-Naşr", 771 | "englishTranslated": "The Divine Support" 772 | }, 773 | { 774 | "number": 111, 775 | "arabic": "المسد", 776 | "arabicWithTashkeel": "الْمَسَد", 777 | "english": "Al-Masad", 778 | "englishTranslated": "The Palm Fiber" 779 | }, 780 | { 781 | "number": 112, 782 | "arabic": "الإخلاص", 783 | "arabicWithTashkeel": "الْإِخْلَاص", 784 | "english": "Al-'Ikhlāş", 785 | "englishTranslated": "Sincerity" 786 | }, 787 | { 788 | "number": 113, 789 | "arabic": "الفلق", 790 | "arabicWithTashkeel": "الْفَلَق", 791 | "english": "Al-Falaq", 792 | "englishTranslated": "The Daybreak" 793 | }, 794 | { 795 | "number": 114, 796 | "arabic": "الناس", 797 | "arabicWithTashkeel": "النَّاس", 798 | "english": "An-Nās", 799 | "englishTranslated": "Mankind" 800 | } 801 | ], 802 | "juzs": [ 803 | { 804 | "number": 1, 805 | "arabic": "الجزء الأول", 806 | "arabicWithTashkeel": "الْجُزْءُ الْأَوَّلُ", 807 | "english": "Juz 1" 808 | }, 809 | { 810 | "number": 2, 811 | "arabic": "الجزء الثاني", 812 | "arabicWithTashkeel": "الْجُزْءُ الثَّانِي", 813 | "english": "Juz 2" 814 | }, 815 | { 816 | "number": 3, 817 | "arabic": "الجزء الثالث", 818 | "arabicWithTashkeel": "الْجُزْءُ الثَّالِثُ", 819 | "english": "Juz 3" 820 | }, 821 | { 822 | "number": 4, 823 | "arabic": "الجزء الرابع", 824 | "arabicWithTashkeel": "الْجُزْءُ الرَّابِعُ", 825 | "english": "Juz 4" 826 | }, 827 | { 828 | "number": 5, 829 | "arabic": "الجزء الخامس", 830 | "arabicWithTashkeel": "الْجُزْءُ الْخَامِسُ", 831 | "english": "Juz 5" 832 | }, 833 | { 834 | "number": 6, 835 | "arabic": "الجزء السادس", 836 | "arabicWithTashkeel": "الْجُزْءُ السَّادِسُ", 837 | "english": "Juz 6" 838 | }, 839 | { 840 | "number": 7, 841 | "arabic": "الجزء السابع", 842 | "arabicWithTashkeel": "الْجُزْءُ السَّابِعُ", 843 | "english": "Juz 7" 844 | }, 845 | { 846 | "number": 8, 847 | "arabic": "الجزء الثامن", 848 | "arabicWithTashkeel": "الْجُزْءُ الثَّامِنُ", 849 | "english": "Juz 8" 850 | }, 851 | { 852 | "number": 9, 853 | "arabic": "الجزء التاسع", 854 | "arabicWithTashkeel": "الْجُزْءُ التَّاسِعُ", 855 | "english": "Juz 9" 856 | }, 857 | { 858 | "number": 10, 859 | "arabic": "الجزء العاشر", 860 | "arabicWithTashkeel": "الْجُزْءُ الْعَاشِرُ", 861 | "english": "Juz 10" 862 | }, 863 | { 864 | "number": 11, 865 | "arabic": "الجزء الحادي عشر", 866 | "arabicWithTashkeel": "الْجُزْءُ الْحَادِي عَشَرَ", 867 | "english": "Juz 11" 868 | }, 869 | { 870 | "number": 12, 871 | "arabic": "الجزء الثاني عشر", 872 | "arabicWithTashkeel": "الْجُزْءُ الثَّانِي عَشَرَ", 873 | "english": "Juz 12" 874 | }, 875 | { 876 | "number": 13, 877 | "arabic": "الجزء الثالث عشر", 878 | "arabicWithTashkeel": "الْجُزْءُ الثَّالِثُ عَشَرَ", 879 | "english": "Juz 13" 880 | }, 881 | { 882 | "number": 14, 883 | "arabic": "الجزء الرابع عشر", 884 | "arabicWithTashkeel": "الْجُزْءُ الرَّابِعُ عَشَرَ", 885 | "english": "Juz 14" 886 | }, 887 | { 888 | "number": 15, 889 | "arabic": "الجزء الخامس عشر", 890 | "arabicWithTashkeel": "الْجُزْءُ الْخَامِسُ عَشَرَ", 891 | "english": "Juz 15" 892 | }, 893 | { 894 | "number": 16, 895 | "arabic": "الجزء السادس عشر", 896 | "arabicWithTashkeel": "الْجُزْءُ السَّادِسُ عَشَرَ", 897 | "english": "Juz 16" 898 | }, 899 | { 900 | "number": 17, 901 | "arabic": "الجزء السابع عشر", 902 | "arabicWithTashkeel": "الْجُزْءُ السَّابِعُ عَشَرَ", 903 | "english": "Juz 17" 904 | }, 905 | { 906 | "number": 18, 907 | "arabic": "الجزء الثامن عشر", 908 | "arabicWithTashkeel": "الْجُزْءُ الثَّامِنُ عَشَرَ", 909 | "english": "Juz 18" 910 | }, 911 | { 912 | "number": 19, 913 | "arabic": "الجزء التاسع عشر", 914 | "arabicWithTashkeel": "الْجُزْءُ التَّاسِعُ عَشَرَ", 915 | "english": "Juz 19" 916 | }, 917 | { 918 | "number": 20, 919 | "arabic": "الجزء العشرون", 920 | "arabicWithTashkeel": "الْجُزْءُ الْعِشْرُونَ", 921 | "english": "Juz 20" 922 | }, 923 | { 924 | "number": 21, 925 | "arabic": "الجزء الحادي والعشرون", 926 | "arabicWithTashkeel": "الْجُزْءُ الْحَادِي وَالْعِشْرُونَ", 927 | "english": "Juz 21" 928 | }, 929 | { 930 | "number": 22, 931 | "arabic": "الجزء الثاني والعشرون", 932 | "arabicWithTashkeel": "الْجُزْءُ الثَّانِي وَالْعِشْرُونَ", 933 | "english": "Juz 22" 934 | }, 935 | { 936 | "number": 23, 937 | "arabic": "الجزء الثالث والعشرون", 938 | "arabicWithTashkeel": "الْجُزْءُ الثَّالِثُ وَالْعِشْرُونَ", 939 | "english": "Juz 23" 940 | }, 941 | { 942 | "number": 24, 943 | "arabic": "الجزء الرابع والعشرون", 944 | "arabicWithTashkeel": "الْجُزْءُ الرَّابِعُ وَالْعِشْرُونَ", 945 | "english": "Juz 24" 946 | }, 947 | { 948 | "number": 25, 949 | "arabic": "الجزء الخامس والعشرون", 950 | "arabicWithTashkeel": "الْجُزْءُ الْخَامِسُ وَالْعِشْرُونَ", 951 | "english": "Juz 25" 952 | }, 953 | { 954 | "number": 26, 955 | "arabic": "الجزء السادس والعشرون", 956 | "arabicWithTashkeel": "الْجُزْءُ السَّادِسُ وَالْعِشْرُونَ", 957 | "english": "Juz 26" 958 | }, 959 | { 960 | "number": 27, 961 | "arabic": "الجزء السابع والعشرون", 962 | "arabicWithTashkeel": "الْجُزْءُ السَّابِعُ وَالْعِشْرُونَ", 963 | "english": "Juz 27" 964 | }, 965 | { 966 | "number": 28, 967 | "arabic": "الجزء الثامن والعشرون", 968 | "arabicWithTashkeel": "الْجُزْءُ الثَّامِنُ وَالْعِشْرُونَ", 969 | "english": "Juz 28" 970 | }, 971 | { 972 | "number": 29, 973 | "arabic": "الجزء التاسع والعشرون", 974 | "arabicWithTashkeel": "الْجُزْءُ التَّاسِعُ وَالْعِشْرُونَ", 975 | "english": "Juz 29" 976 | }, 977 | { 978 | "number": 30, 979 | "arabic": "الجزء الثلاثون", 980 | "arabicWithTashkeel": "الْجُزْءُ الثَّلَاثُونَ", 981 | "english": "Juz 30" 982 | } 983 | ] 984 | } 985 | -------------------------------------------------------------------------------- /i18n.config.ts: -------------------------------------------------------------------------------- 1 | import ar from './localizations/ar.json'; 2 | import en from './localizations/en.json'; 3 | 4 | export default defineI18nConfig(() => ({ 5 | legacy: false, 6 | locale: 'en', 7 | messages: { 8 | ar, 9 | en, 10 | }, 11 | })); 12 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /localizations/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "base": { 3 | "appTitle": "مُسْلِم", 4 | "settings": "الإعدادات", 5 | "me": "أنا", 6 | "thisIsTheGuy": "هو ده الراجل ياباشا", 7 | "introAyah": "كُنتُمْ خَيْرَ أُمَّةٍ أُخْرِجَتْ لِلنَّاسِ تَأْمُرُونَ بِالْمَعْرُوفِ وَتَنْهَوْنَ عَنِ الْمُنكَرِ وَتُؤْمِنُونَ بِاللَّهِ ۗ وَلَوْ آمَنَ أَهْلُ الْكِتَابِ لَكَانَ خَيْرًا لَّهُم ۚ مِّنْهُمُ الْمُؤْمِنُونَ وَأَكْثَرُهُمُ الْفَاسِقُونَ", 8 | "introAyahNumber": "[ سورة آل عمران : 110]", 9 | "appStory": "مُسْلِم هو تطبيق بسيط يساعدك على تأدية أورادك اليومية كمُسْلِم.", 10 | "ayah": "آية", 11 | "ayat": "آيات", 12 | "from": "من", 13 | "loadMore": "المزيد" 14 | }, 15 | "settings": { 16 | "primaryColor": "اللون الأساسي", 17 | "secondaryColor": "اللون الثانوي", 18 | "language": "اللغة", 19 | "rememberSettings": "تذكر الإعدادات", 20 | "apply": "تطبيق", 21 | "updatedToast": "تم تحديث الإعدادات" 22 | }, 23 | "links": { 24 | "aboutTheDeveloper": "عن المطور", 25 | "proTips": "اختصارات", 26 | "login": "تسجيل الدخول", 27 | "logout": "تسجيل الخروج", 28 | "register": "إنشاء حساب" 29 | }, 30 | "explore": "استكشف", 31 | "sections": "الأقسام", 32 | "sectionsList": { 33 | "quraan": "ٱلْقُرْآنِ", 34 | "duaa": "ٱلدُّعَاءِ", 35 | "azkaar": "ٱلْأَذْكَارِ", 36 | "prayers": "ٱلصَّلَوَاتِ", 37 | "nation": "ٱلْأُمَّة" 38 | }, 39 | "quraanSearch": { 40 | "search": "ابحث", 41 | "copy": "نسخ", 42 | "recite": "تلاوة", 43 | "more": "مزيد", 44 | "copiedAya": "تم نسخ الآية", 45 | "copiedAyaDesc": "يمكنك الآن لصقها في أي مكان🎉" 46 | }, 47 | "juz": "جزء", 48 | "juzs": "أجزاء", 49 | "chapter": "سورة", 50 | "chapters": "سور", 51 | "playSurah": "تلاوة ", 52 | "tafsir": "تفسير", 53 | "pause": "إيقاف", 54 | "download": "تحميل", 55 | "downloaded": "تم التحميل", 56 | "read": "قراءة", 57 | "text-size": "حجم النص", 58 | "changeReciter": "تغيير المقرئ", 59 | "reciters": "القُرَّاء", 60 | "bookMode": "عرض ككتاب", 61 | "text-sizes": { 62 | "small": "صغير", 63 | "medium": "متوسط", 64 | "large": "كبير", 65 | "x-large": "كبير جداً", 66 | "xx-large": "كبير جداً جداً" 67 | }, 68 | "footer": { 69 | "madeWith": "صُنع بواسطة", 70 | "by": "بواسطة" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /localizations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "base": { 3 | "appTitle": "Muslim", 4 | "settings": "Settings", 5 | "me": "Me", 6 | "introAyah": "You are the best nation produced [as an example] for mankind. You enjoin what is right and forbid what is wrong and believe in Allah. If only the People of the Scripture had believed, it would have been better for them. Among them are believers, but most of them are defiantly disobedient", 7 | "introAyahNumber": "[ Surah Al Imran : 110]", 8 | "appStory": "Muslim is a simple app that helps you with your daily Awrad as a Muslim.", 9 | "ayah": "Ayah", 10 | "ayat": "Ayat", 11 | "from": "from", 12 | "loadMore": "Load More" 13 | }, 14 | "settings": { 15 | "primaryColor": "Primary Color", 16 | "secondaryColor": "Secondary Color", 17 | "language": "Language", 18 | "rememberSettings": "Remember Settings", 19 | "apply": "Apply", 20 | "updatedToast": "Settings Updated" 21 | }, 22 | "links": { 23 | "aboutTheDeveloper": "About the Developer", 24 | "proTips": "Pro Tips", 25 | "thisIsTheGuy": "This is the guy", 26 | "login": "Login", 27 | "logout": "Logout", 28 | "register": "Register" 29 | }, 30 | "explore": "Explore", 31 | "sections": "Sections", 32 | "sectionsList": { 33 | "quraan": "quraan", 34 | "duaa": "duaa", 35 | "azkaar": "azkaar", 36 | "prayers": "prayers", 37 | "nation": "nation" 38 | }, 39 | "quraanSearch": { 40 | "search": "search", 41 | "copy": "copy", 42 | "recite": "recite", 43 | "more": "more", 44 | "copiedAya": "Ayah has been copied to your clipboard!", 45 | "copiedAyaDesc": "You can paste it anywhere you want now.🎉" 46 | }, 47 | "juz": "Juz'", 48 | "juzs": "Juz's", 49 | "chapter": "Chapter", 50 | "chapters": "Chapters", 51 | "playSurah": "Play Surah", 52 | "tafsir": "Tafsir (explanation)", 53 | "pause": "Pause", 54 | "download": "Download", 55 | "downloaded": "Downloaded", 56 | "read": "Read", 57 | "changeReciter": "Change Reciter", 58 | "reciters": "Reciters", 59 | "bookMode": "Book Mode", 60 | "text-size": "Text Size", 61 | "text-sizes": { 62 | "small": "Small", 63 | "medium": "Medium", 64 | "large": "Large", 65 | "x-large": "X-Large", 66 | "xx-large": "XX-Large" 67 | }, 68 | "footer": { 69 | "madeWith": "Made with", 70 | "by": "by" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /middleware/auth.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtRouteMiddleware(async (to, from) => { 2 | const user = useCurrentUser(); 3 | 4 | if (!user) { 5 | return navigateTo({ 6 | path: '/', 7 | query: { 8 | redirect: to.fullPath, 9 | }, 10 | }); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | css: ['@/assets/css/core.css', '@/assets/css/main.css', '@/assets/css/animation.css'], 3 | modules: ['@pinia/nuxt', '@nuxthq/ui', '@nuxtjs/i18n'], 4 | i18n: { 5 | vueI18n: 'i18n.config', 6 | detectBrowserLanguage: { 7 | useCookie: true, 8 | fallbackLocale: 'en', 9 | }, 10 | strategy: 'no_prefix', 11 | locales: [ 12 | { 13 | code: 'en', 14 | file: 'en.json', 15 | }, 16 | { 17 | code: 'ar', 18 | file: 'ar.json', 19 | }, 20 | ], 21 | langDir: 'localizations', 22 | defaultLocale: 'en', 23 | }, 24 | // vuefire: { 25 | // /* We're gonna pause the authentication work until we finish the important parts in the aoo */ 26 | // auth: true, 27 | // // admin: { 28 | // // serviceAccount: './service-account.json', 29 | // // }, 30 | // config: { 31 | // apiKey: import.meta.env.VITE_FIREBASE_API_KEY, 32 | // authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN, 33 | // projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID, 34 | // storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET, 35 | // messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID, 36 | // appId: import.meta.env.VITE_FIREBASE_APP_ID, 37 | // measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID, 38 | // }, 39 | // }, 40 | colorMode: { 41 | preference: 'dark', 42 | fallback: 'dark', 43 | }, 44 | pinia: { 45 | autoImports: ['defineStore', ['defineStore', 'definePiniaStore']], 46 | }, 47 | devtools: { enabled: true }, 48 | }); 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "muslim", 3 | "private": true, 4 | "scripts": { 5 | "build": "nuxt build", 6 | "dev": "nuxt dev", 7 | "generate": "nuxt generate", 8 | "preview": "nuxt preview", 9 | "postinstall": "nuxt prepare" 10 | }, 11 | "dependencies": { 12 | "@pinia/nuxt": "^0.4.11", 13 | "axios": "^1.4.0", 14 | "firebase": "^9.22.2", 15 | "gsap": "^3.11.5", 16 | "nuxt-vuefire": "^0.1.6", 17 | "zod": "^3.21.4" 18 | }, 19 | "devDependencies": { 20 | "@nuxt/devtools": "^0.6.1", 21 | "@nuxthq/ui": "^2.4.0", 22 | "@nuxtjs/google-fonts": "^3.0.1", 23 | "@nuxtjs/i18n": "8.0.0-beta.12", 24 | "@types/node": "^20.3.1", 25 | "firebase-admin": "^11.8.0", 26 | "nuxt": "^3.5.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 60 | 138 | -------------------------------------------------------------------------------- /pages/quran/chapter/[id]/index.vue: -------------------------------------------------------------------------------- 1 | 140 | 141 | 397 | -------------------------------------------------------------------------------- /pages/quran/chapter/[id]/tafsir/[tafsir_chapter_id].vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /pages/quran/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 96 | -------------------------------------------------------------------------------- /pages/quran/juz/[id].vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | -------------------------------------------------------------------------------- /pages/quran/verse/[verse_id].vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedragab20/Muslim/69b6acfc9f68d4bbf47d3b47a83475ccb3cee65c/public/favicon.ico -------------------------------------------------------------------------------- /public/reciters/Abdelbasset-abdessamad.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedragab20/Muslim/69b6acfc9f68d4bbf47d3b47a83475ccb3cee65c/public/reciters/Abdelbasset-abdessamad.jpeg -------------------------------------------------------------------------------- /public/reciters/afasy.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedragab20/Muslim/69b6acfc9f68d4bbf47d3b47a83475ccb3cee65c/public/reciters/afasy.jpeg -------------------------------------------------------------------------------- /public/reciters/al-hosari.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedragab20/Muslim/69b6acfc9f68d4bbf47d3b47a83475ccb3cee65c/public/reciters/al-hosari.jpeg -------------------------------------------------------------------------------- /public/reciters/alqatami.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedragab20/Muslim/69b6acfc9f68d4bbf47d3b47a83475ccb3cee65c/public/reciters/alqatami.jpeg -------------------------------------------------------------------------------- /public/reciters/minshawi.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedragab20/Muslim/69b6acfc9f68d4bbf47d3b47a83475ccb3cee65c/public/reciters/minshawi.jpeg -------------------------------------------------------------------------------- /public/visuals/azkar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/visuals/matt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/visuals/quran.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /refactoring.todo: -------------------------------------------------------------------------------- 1 | 1.. updating packages 2 | 2.. updating the UX 3 | 3.. refactoring the infrastructure 4 | 4.. updating the database to rely more on consistent local data (islamic_app_database) - optional for quran 5 | 5.. fixing the localizations bug - replace i18n with kalemah 6 | 6.. using consistent well-known fonts -------------------------------------------------------------------------------- /schemas/chapters.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const chapterSchema = z.object({ 4 | number: z.number(), 5 | arabic: z.string(), 6 | arabicWithTashkeel: z.string(), 7 | english: z.string(), 8 | englishTranslated: z.string(), 9 | }); 10 | 11 | export const chaptersSchema = z.array(chapterSchema); 12 | 13 | export const Ayah = z.object({ 14 | number: z.number(), 15 | text: z.string(), 16 | numberInSurah: z.number(), 17 | juz: z.number(), 18 | manzil: z.number(), 19 | page: z.number(), 20 | ruku: z.number(), 21 | hizbQuarter: z.number(), 22 | sajda: z.boolean(), 23 | }); 24 | 25 | export const chapterInfoSchema = z.object({ 26 | reciter_id: z.string(), 27 | number: z.number(), 28 | name: z.string(), 29 | englishName: z.string(), 30 | englishNameTranslation: z.string(), 31 | revelationType: z.string(), 32 | numberOfAyahs: z.number(), 33 | ayahs: z.array(Ayah), 34 | }); 35 | 36 | export const verseSchema = z.object({ 37 | verse_key: z.string(), 38 | verse_number: z.number(), 39 | verse_arabic: z.string(), 40 | verse_english: z.string(), 41 | sajda: z.boolean(), 42 | }); 43 | 44 | export const chapterRecitationSchema = z.object({ 45 | name: z.object({ 46 | en: z.string(), 47 | ar: z.string(), 48 | }), 49 | type: z.object({ 50 | en: z.string(), 51 | ar: z.string(), 52 | }), 53 | url: z.string(), 54 | poster: z.string(), 55 | reciter_id: z.string(), 56 | }); 57 | -------------------------------------------------------------------------------- /schemas/hijir-date.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const dateSchema = z.object({ 4 | day: z.object({ 5 | dayNumber: z.string(), 6 | en: z.string(), 7 | ar: z.string(), 8 | }), 9 | month: z.object({ 10 | number: z.number(), 11 | en: z.string(), 12 | ar: z.string(), 13 | }), 14 | year: z.string(), 15 | }); 16 | -------------------------------------------------------------------------------- /schemas/juzs.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const juz = z.object({ 4 | id: z.number(), 5 | juz_number: z.number(), 6 | first_verse_id: z.number(), 7 | last_verse_id: z.number(), 8 | verses_count: z.number(), 9 | }); 10 | 11 | export const juzsSchema = z.array(juz); 12 | -------------------------------------------------------------------------------- /schemas/search-quran.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | export const searchQuranSchema = z.object({ 3 | query: z.string(), 4 | total_results: z.number(), 5 | current_page: z.number(), 6 | total_pages: z.number(), 7 | results: z.array(z.any()), 8 | }); 9 | -------------------------------------------------------------------------------- /server/api/hijri-date.ts: -------------------------------------------------------------------------------- 1 | import useApis from '../../composables/use-apis'; 2 | import { HijriDate } from '../../types/server-schemas/hijri-date'; 3 | import { Generics } from '../../utils/generics'; 4 | 5 | export default defineEventHandler(async () => { 6 | const currentDate = Generics.formateDate(new Date()) as string; 7 | 8 | const { HIJRI_DATE_API } = useApis(); 9 | 10 | const url = HIJRI_DATE_API(currentDate); 11 | 12 | const response: HijriDate = await $fetch(url); 13 | 14 | if (response.status?.toLocaleLowerCase() === 'ok') { 15 | const day = response.data.hijri.weekday; 16 | const month = response.data.hijri.month; 17 | const year = response.data.hijri.year; 18 | 19 | return { 20 | day: { 21 | dayNumber: response.data.hijri.day, 22 | ...day, 23 | }, 24 | month, 25 | year, 26 | }; 27 | } else { 28 | throw createError({ 29 | statusCode: 500, 30 | statusMessage: 'Wrong response', 31 | }); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /server/api/quran/chapter/[chapter_id].ts: -------------------------------------------------------------------------------- 1 | import { useChapters } from './../../../../composables/use-chapters'; 2 | import { Chapter } from './../../../../types/server-schemas/Chapter.d'; 3 | export default defineEventHandler((event: any) => { 4 | const chapter_id = +event.context.params.chapter_id; 5 | const chapters = useChapters(); 6 | const chapter = chapters.find((chapter: Chapter) => chapter.number === chapter_id); 7 | 8 | if (!chapter) { 9 | throw createError({ 10 | statusCode: 404, 11 | statusMessage: 'Chapter not found', 12 | }); 13 | } else { 14 | return chapter; 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /server/api/quran/chapter/chapter_verses/[chapter_id].ts: -------------------------------------------------------------------------------- 1 | import useApis from '../../../../../composables/use-apis'; 2 | 3 | export default defineEventHandler(async (event: any) => { 4 | const chapter_id = +event.context.params.chapter_id as number; 5 | 6 | const { CHAPTERS_INFO_API } = useApis(); 7 | const urlAr = CHAPTERS_INFO_API(chapter_id); 8 | const urlEn = CHAPTERS_INFO_API(chapter_id, true); 9 | 10 | const resAr: any = await $fetch(urlAr); 11 | const resEn: any = await $fetch(urlEn); 12 | 13 | const res = { 14 | arabic: resAr.data, 15 | english: resEn.data, 16 | }; 17 | 18 | const arAyah = res.arabic.ayahs; 19 | const enAyah = res.english.ayahs; 20 | 21 | // ayah:chapter 22 | 23 | return arAyah.map((ayah: any, index: number) => { 24 | return { 25 | verse_key: `${ayah.number}:${ayah.juz}`, 26 | verse_number: ayah.number, 27 | verse_arabic: ayah.text, 28 | verse_english: enAyah[index].text, 29 | sajda: ayah.sajda, 30 | }; 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /server/api/quran/chapter/recitations/[chapter_id].ts: -------------------------------------------------------------------------------- 1 | import useQuranReciters from '../../../../../composables/use-quran-reciters'; 2 | export default defineEventHandler((event: any) => { 3 | const chapter_id = +event.context.params.chapter_id; 4 | const recitations: any = useQuranReciters(); 5 | 6 | return Object.keys(recitations).map((reciter: any) => { 7 | return recitations[reciter](chapter_id); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /server/api/quran/chapters.ts: -------------------------------------------------------------------------------- 1 | import { useChapters } from './../../../composables/use-chapters'; 2 | export default defineEventHandler(async (event) => { 3 | const chapters = useChapters(); 4 | 5 | if (!chapters) { 6 | throw createError({ 7 | statusCode: 404, 8 | statusMessage: 'Chapters not found', 9 | }); 10 | } else { 11 | return chapters; 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /server/api/quran/juz/id.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler((event) => { 2 | return 'Hello id' 3 | }) 4 | -------------------------------------------------------------------------------- /server/api/quran/juzs.ts: -------------------------------------------------------------------------------- 1 | import useApis from '../../../composables/use-apis'; 2 | export default defineEventHandler(async (event) => { 3 | const { JUZS_API } = useApis(); 4 | const url = JUZS_API(); 5 | const res: any = await $fetch(url); 6 | 7 | return res.juzs.map((juz: any) => { 8 | const { verse_mapping, ...rest } = juz; 9 | return rest; 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /server/api/search-quran.ts: -------------------------------------------------------------------------------- 1 | import useApis from '../../composables/use-apis'; 2 | 3 | export default defineEventHandler(async (event) => { 4 | const searchParams = new URLSearchParams(event.req.url); 5 | 6 | // const q = searchParams.get('query'); 7 | const pageNumber = searchParams.get('page'); 8 | const pageSize = searchParams.get('size'); 9 | // Extract the query string from the URL 10 | const queryString = event.req.url?.split('?')[1]; 11 | 12 | // Split the query string into an array of key-value pairs 13 | const queryParams = queryString?.split('&'); 14 | 15 | // Initialize a variable to store the value of the "q" parameter 16 | let qValue = null; 17 | 18 | // Iterate through the query parameters 19 | for (const param of queryParams!) { 20 | const [key, value] = param.split('='); 21 | 22 | // Check if the parameter key is "q" 23 | if (key === 'query') { 24 | qValue = value; 25 | break; 26 | } 27 | } 28 | 29 | const { SEARCH_QURAN_BY_TERM_API } = useApis(); 30 | 31 | const url = SEARCH_QURAN_BY_TERM_API(qValue!, +pageNumber!, +pageSize!); 32 | const response = await $fetch(url); 33 | 34 | //@ts-ignore 35 | return response.search || []; 36 | }); 37 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /stores/audio-player.ts: -------------------------------------------------------------------------------- 1 | export const useAudioPlayerStore = defineStore('audio-player', () => { 2 | const audio = ref(); 3 | const playerInfo = ref([]); 4 | const status = ref(false); 5 | const currentTime = ref(0); 6 | const playerProgress = ref({}); 7 | const setAudio = (newAudio: any) => { 8 | audio.value = newAudio; 9 | }; 10 | const setPlayerInfo = (newPlayerInfo: any) => { 11 | playerInfo.value = newPlayerInfo; 12 | }; 13 | const setStatus = (newStatus: boolean) => { 14 | status.value = newStatus; 15 | audio.value.isPlaying = newStatus; 16 | }; 17 | const setCurrentTime = (newCurrentTime: number) => { 18 | currentTime.value = newCurrentTime; 19 | audio.value.isPlaying = true; 20 | }; 21 | const toggle = () => { 22 | if (status.value) { 23 | audio.value.pause(); 24 | } else { 25 | audio.value.play(); 26 | } 27 | setStatus(!status.value); 28 | }; 29 | const setPlayerProgress = (newPlayerProgress: any) => { 30 | playerProgress.value = newPlayerProgress; 31 | }; 32 | 33 | const headerPlayerOpened = ref(false); 34 | const setHeaderPlayerOpened = (newHeaderPlayerOpened?: boolean) => { 35 | if (!newHeaderPlayerOpened) { 36 | headerPlayerOpened.value = !headerPlayerOpened.value; 37 | 38 | return; 39 | } 40 | 41 | headerPlayerOpened.value = newHeaderPlayerOpened; 42 | }; 43 | 44 | return { 45 | audio, 46 | playerInfo, 47 | currentTime, 48 | setAudio, 49 | setStatus, 50 | setCurrentTime, 51 | setPlayerInfo, 52 | toggle, 53 | playerProgress, 54 | setPlayerProgress, 55 | headerPlayerOpened, 56 | setHeaderPlayerOpened, 57 | }; 58 | }); 59 | -------------------------------------------------------------------------------- /stores/auth.ts: -------------------------------------------------------------------------------- 1 | import { AuthLand, Errors } from '@/types'; 2 | export const useAuthStore = defineStore('auth', () => { 3 | const Errors = ref>({}); 4 | 5 | const setErrors = (errors: Partial) => { 6 | Errors.value = errors; 7 | }; 8 | 9 | const authLanded = ref(false); 10 | const toggleAuthLanded = (payload?: boolean) => { 11 | if (payload && [true, false].includes(payload)) { 12 | authLanded.value = payload; 13 | return; 14 | } 15 | 16 | authLanded.value = !authLanded.value; 17 | }; 18 | 19 | const chosenAuthLand = ref('login'); // fallback to login 20 | const chooseAuthLand = (land: AuthLand): void => { 21 | if (!land) return; 22 | 23 | chosenAuthLand.value = land; 24 | }; 25 | 26 | return { 27 | authLanded, 28 | toggleAuthLanded, 29 | Errors, 30 | chosenAuthLand, 31 | setErrors, 32 | chooseAuthLand, 33 | }; 34 | }); 35 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | interface AddVariation { 2 | addVariant: any; 3 | } 4 | 5 | module.exports = { 6 | plugins: [ 7 | function (payload: AddVariation) { 8 | payload.addVariant('initial', 'html :where(&)'); 9 | }, 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface Errors { 2 | code?: number; 3 | message?: string; 4 | } 5 | 6 | // types 7 | export type AuthLand = 'login' | 'register' | 'forgot' | 'reset' | 'verify'; 8 | -------------------------------------------------------------------------------- /types/server-schemas/Chapter.d.ts: -------------------------------------------------------------------------------- 1 | export interface Chapter { 2 | number: number; 3 | arabic: string; 4 | arabicWithTashkeel: string; 5 | english: string; 6 | englishTranslated: string; 7 | } 8 | 9 | export interface Verse { 10 | verse_key: string; 11 | verse_number: number; 12 | verse_arabic: string; 13 | verse_english: string; 14 | sajda: boolean; 15 | } 16 | 17 | export interface Reciter { 18 | reciter_id: string; 19 | name: { 20 | en: string; 21 | ar: string; 22 | }; 23 | type: { 24 | en: string; 25 | ar: string; 26 | }; 27 | url: string; 28 | poster: string; 29 | reciter: string; 30 | } 31 | -------------------------------------------------------------------------------- /types/server-schemas/hijri-date.d.ts: -------------------------------------------------------------------------------- 1 | export interface HijriDate { 2 | code: number; 3 | status: string; 4 | data: Data; 5 | } 6 | 7 | export interface Data { 8 | hijri: Hijri; 9 | gregorian: Gregorian; 10 | } 11 | 12 | export interface Hijri { 13 | date: string; 14 | format: string; 15 | day: string; 16 | weekday: Weekday; 17 | month: Month; 18 | year: string; 19 | designation: Designation; 20 | holidays: any[]; 21 | } 22 | 23 | export interface Weekday { 24 | en: string; 25 | ar: string; 26 | } 27 | 28 | export interface Month { 29 | number: number; 30 | en: string; 31 | ar: string; 32 | } 33 | 34 | export interface Designation { 35 | abbreviated: string; 36 | expanded: string; 37 | } 38 | 39 | export interface Gregorian { 40 | date: string; 41 | format: string; 42 | day: string; 43 | weekday: Weekday2; 44 | month: Month2; 45 | year: string; 46 | designation: Designation2; 47 | } 48 | 49 | export interface Weekday2 { 50 | en: string; 51 | } 52 | 53 | export interface Month2 { 54 | number: number; 55 | en: string; 56 | } 57 | 58 | export interface Designation2 { 59 | abbreviated: string; 60 | expanded: string; 61 | } 62 | 63 | export interface FilteredHijriDate { 64 | day: Weekday; 65 | month: Month; 66 | year: string; 67 | } 68 | -------------------------------------------------------------------------------- /utils/AudioPlayer.ts: -------------------------------------------------------------------------------- 1 | export default class AudioPlayer { 2 | public readonly audio: HTMLAudioElement; 3 | private isPlaying: boolean; 4 | public readonly info: any[]; // some data you might need to store in the audio player instance to use later for any purpose 5 | public readonly url: string; 6 | 7 | constructor(url: string, info?: any[]) { 8 | this.url = url; 9 | this.audio = new Audio(url); 10 | this.isPlaying = false; 11 | this.info = info || []; 12 | } 13 | 14 | public toggle(): void { 15 | if (this.isPlaying) { 16 | this.audio.pause(); 17 | } else { 18 | this.audio.play(); 19 | } 20 | 21 | this.isPlaying = !this.isPlaying; 22 | } 23 | 24 | public play(): void { 25 | this.audio.play(); 26 | this.isPlaying = true; 27 | } 28 | 29 | public stop(): void { 30 | this.audio.pause(); 31 | this.setCurrentTime(0); 32 | this.isPlaying = false; 33 | } 34 | 35 | public pause(): void { 36 | this.audio.pause(); 37 | this.isPlaying = false; 38 | } 39 | 40 | public onEnded(callback?: () => void): void { 41 | this.audio.addEventListener('ended', () => { 42 | this.isPlaying = false; 43 | callback?.(); 44 | }); 45 | } 46 | // on time update 47 | public onTimeUpdate(callback?: (time: number) => void): void { 48 | this.audio.addEventListener('timeupdate', () => { 49 | callback?.(this.audio.currentTime); 50 | }); 51 | } 52 | 53 | //onBuffering 54 | public onBuffering(callback?: () => void): void { 55 | this.audio.addEventListener('waiting', () => { 56 | callback?.(); 57 | }); 58 | } 59 | // on buffering end 60 | public onPlaying(callback?: () => void): void { 61 | this.audio.addEventListener('playing', () => { 62 | callback?.(); 63 | }); 64 | } 65 | setIsPlaying(isPlaying: boolean): void { 66 | this.isPlaying = isPlaying; 67 | } 68 | 69 | public setVolume(volume: number): void { 70 | this.audio.volume = volume; 71 | } 72 | 73 | public setPlaybackRate(rate: number): void { 74 | this.audio.playbackRate = rate; 75 | } 76 | 77 | public setCurrentTime(time: number): void { 78 | if (!time || time === Infinity) return; 79 | if (time < 0) { 80 | time = 0; 81 | } 82 | 83 | this.audio.currentTime = time; 84 | } 85 | get duration(): number { 86 | return this.audio.duration; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /utils/Debug.ts: -------------------------------------------------------------------------------- 1 | interface Payload { 2 | message: string; 3 | style?: string; 4 | data?: any; 5 | source?: string; 6 | useOnProduction?: boolean; 7 | } 8 | 9 | export default class Debug { 10 | static log(payload?: Partial, ...args: any[]) { 11 | if (process.env.NODE_ENV !== 'development' && !payload?.useOnProduction) return; 12 | 13 | if (!payload?.message && args.length) { 14 | console.log( 15 | `%c${args[0]}`, 16 | `${ 17 | payload?.style || 18 | 'padding: 2px; font-weight: bold; color: brown; border-radius: 2px; background-color: #f5f5f5;' 19 | }` 20 | ); 21 | 22 | return; 23 | } 24 | 25 | console.log( 26 | `%c${payload?.message || '🎉'} ${payload?.source ? `(${payload?.source})` : ''}`, 27 | `${ 28 | payload?.style || 29 | 'padding: 2px; font-weight: bold; color: brown; border-radius: 2px; background-color: #f5f5f5;' 30 | }`, 31 | payload?.data 32 | ); 33 | } 34 | static warn(payload: Partial) { 35 | if (process.env.NODE_ENV !== 'development' && !payload.useOnProduction) return; 36 | 37 | console.warn( 38 | `%c${payload.message || '🚧'} ${payload.source ? `(${payload.source})` : ''}`, 39 | `${ 40 | payload.style || 41 | 'padding: 2px; font-weight: bold; color: #000; border-radius: 2px; background-color: #ded3afa5;' 42 | }`, 43 | payload.data 44 | ); 45 | } 46 | static error(payload: Partial) { 47 | if (process.env.NODE_ENV !== 'development' && !payload.useOnProduction) return; 48 | 49 | console.error( 50 | `%c${payload.message || '🚨'} ${payload.source ? `(${payload.source})` : ''}`, 51 | `${ 52 | payload.style || 53 | 'padding: 2px; font-weight: bold; color: #fff; border-radius: 2px; background-color: #f44336;' 54 | }`, 55 | payload.data 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /utils/authHelpers.ts: -------------------------------------------------------------------------------- 1 | import { FirestoreUser } from '@/types'; 2 | import { User } from 'firebase/auth'; 3 | import { 4 | Firestore, 5 | addDoc, 6 | collection, 7 | deleteDoc, 8 | getDocs, 9 | query, 10 | updateDoc, 11 | where, 12 | } from 'firebase/firestore'; 13 | 14 | export const addFirestoreUser = async (db: Firestore, user: User | FirestoreUser) => { 15 | const { uid, email, displayName, emailVerified, photoURL } = user; 16 | 17 | const userData: FirestoreUser = { 18 | uid: uid as string, 19 | email: email as string, 20 | displayName: displayName as string, 21 | emailVerified: emailVerified as boolean, 22 | photoURL: photoURL as string, 23 | }; 24 | 25 | try { 26 | return await addDoc(collection(db, 'users'), userData); 27 | } catch (error) { 28 | console.error(error); 29 | throw createError({ 30 | message: "Couldn't add user", 31 | statusCode: 500, 32 | }); 33 | } 34 | }; 35 | 36 | export const updateFirestoreUser = async (db: Firestore, user: FirestoreUser) => { 37 | try { 38 | const querySnapshot = await getDocs( 39 | query(collection(db, 'users'), where('uid', '==', user.uid)) 40 | ); 41 | 42 | querySnapshot?.forEach(async (doc) => { 43 | const docRef = doc.ref; 44 | if (docRef) { 45 | await updateDoc(docRef, { ...user }); 46 | } 47 | }); 48 | } catch (error) { 49 | console.error(error); 50 | throw createError({ 51 | message: "Couldn't update user", 52 | statusCode: 500, 53 | }); 54 | } 55 | }; 56 | 57 | export const deleteFirestoreUser = async (db: Firestore, user: FirestoreUser) => { 58 | if (!user.uid) { 59 | throw createError({ 60 | message: "Send user's uid", 61 | statusCode: 400, 62 | }); 63 | } 64 | 65 | try { 66 | const querySnapshot = await getDocs( 67 | query(collection(db, 'users'), where('uid', '==', user.uid)) 68 | ); 69 | 70 | querySnapshot?.forEach(async (doc) => { 71 | const docRef = doc.ref; 72 | if (docRef) { 73 | await deleteDoc(docRef); 74 | } 75 | }); 76 | } catch (error) { 77 | console.error(error); 78 | throw createError({ 79 | message: "Couldn't delete user", 80 | statusCode: 500, 81 | }); 82 | } 83 | }; 84 | 85 | export const getFirestoreUser = async ( 86 | db: Firestore, 87 | uid: string 88 | ): Promise => { 89 | try { 90 | const querySnapshot = await getDocs(query(collection(db, 'users'), where('uid', '==', uid))); 91 | const doc = querySnapshot.docs[0]; // Assuming UID is unique, retrieve the first document 92 | if (doc) { 93 | const user = doc.data() as FirestoreUser; 94 | return user; 95 | } else { 96 | return undefined; 97 | } 98 | } catch (error) { 99 | console.error(error); 100 | throw createError({ 101 | message: "Couldn't get user", 102 | statusCode: 500, 103 | }); 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /utils/generics.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export class Generics { 4 | static uuid(): string | undefined { 5 | try { 6 | const data = new Uint8Array(16); 7 | window.crypto.getRandomValues(data); 8 | 9 | data[6] = (data[6] & 0x0f) | 0x40; 10 | data[8] = (data[8] & 0x3f) | 0x80; 11 | 12 | const hex = Array.from(data) 13 | .map((byte) => byte.toString(16).padStart(2, '0')) 14 | .join(''); 15 | 16 | const sections = [ 17 | hex.substr(0, 8), 18 | hex.substr(8, 4), 19 | hex.substr(12, 4), 20 | hex.substr(16, 4), 21 | hex.substr(20, 12), 22 | ]; 23 | 24 | return sections.join('-'); 25 | } catch (error) { 26 | Debug.error({ 27 | message: '🚨 Error generating uuid', 28 | source: 'utils/generics.ts', 29 | data: error, 30 | useOnProduction: true, 31 | }); 32 | } 33 | } 34 | static valuesMatch(a: T, b: T): boolean { 35 | if (!a || !b) { 36 | Debug.warn({ 37 | message: '🚨 Error comparing values', 38 | data: { a, b }, 39 | }); 40 | } 41 | 42 | return JSON.stringify(a) === JSON.stringify(b); 43 | } 44 | static getObjectInfoSeparate(obj: object) { 45 | if (!obj) { 46 | Debug.error({ 47 | message: '🚨 Error getting object info', 48 | source: 'utils/generics.ts', 49 | data: obj, 50 | }); 51 | 52 | return; 53 | } 54 | 55 | const keys = Object.keys(obj); 56 | const values = Object.values(obj); 57 | return { keys, values }; 58 | } 59 | static dateSince(date: Date): string { 60 | if (!date) { 61 | Debug.error({ 62 | message: '🚨 Error getting date', 63 | source: 'utils/generics.ts', 64 | data: date, 65 | useOnProduction: true, 66 | }); 67 | 68 | throw createError({ 69 | message: '🚨 Error getting date', 70 | statusCode: 500, 71 | }); 72 | } 73 | 74 | const originalDate: Date = this.getObjectInfoSeparate(date)?.keys?.includes('seconds') 75 | ? // @ts-ignore 76 | new Date(date.seconds * 1000) 77 | : date; 78 | 79 | // Get the current date and time 80 | const currentDate: Date = new Date(); 81 | 82 | // Calculate the difference in milliseconds between the current date and the input date 83 | const timeDiff: number = currentDate.getTime() - originalDate.getTime(); 84 | 85 | // Convert the time difference from milliseconds to seconds 86 | const seconds: number = Math.floor(timeDiff / 1000); 87 | 88 | // Convert seconds to minutes 89 | const minutes: number = Math.floor(seconds / 60); 90 | 91 | // Convert minutes to hours 92 | const hours: number = Math.floor(minutes / 60); 93 | 94 | // Convert hours to days 95 | const days: number = Math.floor(hours / 24); 96 | 97 | // Convert days to weeks 98 | const weeks: number = Math.floor(days / 7); 99 | 100 | // Convert weeks to months (approximate) 101 | const months: number = Math.floor(weeks / 4.348); 102 | 103 | // Convert months to years 104 | const years: number = Math.floor(months / 12); 105 | 106 | // Create a string to represent the time since the input date 107 | let result: string = ''; 108 | 109 | if (years > 0) { 110 | result += `${years % 365.25}y`; 111 | } 112 | 113 | if (months > 0) { 114 | if (result.length > 0) { 115 | result += ', '; 116 | } 117 | result += `${months % 12}mo`; 118 | } 119 | 120 | if (weeks > 0) { 121 | if (result.length > 0) { 122 | result += ', '; 123 | } 124 | result += `${weeks % 7}w`; 125 | } 126 | 127 | if (days > 0) { 128 | if (result.length > 0) { 129 | result += ', '; 130 | } 131 | result += `${days}d`; 132 | } 133 | 134 | if (hours > 0) { 135 | if (result.length > 0) { 136 | result += ', '; 137 | } 138 | result += `${hours % 24}h`; 139 | } 140 | 141 | if (minutes > 0) { 142 | if (result.length > 0) { 143 | result += ', '; 144 | } 145 | result += `${(minutes % 60)?.toFixed(0) !== '0' ? `${(minutes % 60)?.toFixed(0)}m` : ''}`; 146 | } 147 | 148 | // Return the resulting string 149 | return result.length > 0 ? result + ' ago' : 'just now'; 150 | } 151 | static allDataValid(obj: T): boolean { 152 | if (!obj || typeof obj !== 'object') { 153 | Debug.error({ 154 | message: '🚨 Error validating values', 155 | source: 'utils/generics.ts', 156 | data: obj, 157 | }); 158 | 159 | return false; 160 | } 161 | 162 | // check if all values are valid 163 | for (const key in obj) { 164 | if (!obj.hasOwnProperty(key)) { 165 | continue; 166 | } 167 | 168 | const value = obj[key]; 169 | 170 | if (Array.isArray(value) && value.length === 0) { 171 | return false; 172 | } 173 | 174 | if (typeof value === 'string' && !value.trim()) { 175 | return false; 176 | } 177 | 178 | if (typeof value === 'number' && value === 0) { 179 | return false; 180 | } 181 | 182 | if (typeof value === 'object' && Object.keys(value as object).length === 0) { 183 | return false; 184 | } 185 | 186 | if (typeof value === 'boolean' && !value) { 187 | return false; 188 | } 189 | } 190 | 191 | return true; 192 | } 193 | static clone = (obj: T): T => { 194 | if (!obj) { 195 | Debug.error({ 196 | message: '🚨 Error cloning object', 197 | source: 'utils/generics.ts', 198 | data: obj, 199 | }); 200 | 201 | throw createError({ 202 | message: '🚨 Error cloning object', 203 | statusCode: 500, 204 | }); 205 | } 206 | 207 | return JSON.parse(JSON.stringify(obj)); 208 | }; 209 | static formatNumber = (number: number): string => { 210 | if (!number) { 211 | Debug.error({ 212 | message: '🚨 Error formatting number', 213 | source: 'utils/generics.ts', 214 | data: number, 215 | }); 216 | 217 | throw createError({ 218 | message: '🚨 Error formatting number', 219 | statusCode: 500, 220 | }); 221 | } 222 | 223 | return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); 224 | }; 225 | static sortByDate = (a: Date, b: Date): number => { 226 | //TODO: Refactor this 227 | if (!a || !b) { 228 | Debug.error({ 229 | message: '🚨 Error sorting dates', 230 | source: 'utils/generics.ts', 231 | data: { a, b }, 232 | }); 233 | 234 | throw createError({ 235 | message: '🚨 Error sorting dates', 236 | statusCode: 500, 237 | }); 238 | } 239 | 240 | const originalDateA: Date = this.getObjectInfoSeparate(a)?.keys?.includes('seconds') 241 | ? // @ts-ignore 242 | new Date(a.seconds * 1000) 243 | : a; 244 | 245 | const originalDateB: Date = this.getObjectInfoSeparate(b)?.keys?.includes('seconds') 246 | ? // @ts-ignore 247 | new Date(b.seconds * 1000) 248 | : b; 249 | 250 | return originalDateA.getTime() - originalDateB.getTime(); 251 | }; 252 | static formateDate = (date: Date): string => { 253 | if (!date || typeof date !== 'object') { 254 | return ''; 255 | } 256 | 257 | const currentDate = new Date(); 258 | 259 | // Extract day, month, and year 260 | const day = String(currentDate.getDate()).padStart(2, '0'); 261 | const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Months are zero-based 262 | const year = currentDate.getFullYear(); 263 | 264 | // Create the formatted date string 265 | const formattedDate = day + '-' + month + '-' + year; 266 | 267 | return formattedDate ? formattedDate : date?.toLocaleDateString(); 268 | }; 269 | static isArabicText(text: string): boolean { 270 | // Regular expression to check if the text contains Arabic characters 271 | const arabicRegex = /[\u0600-\u06FF]/; 272 | 273 | return arabicRegex.test(text); 274 | } 275 | /** 276 | * Encodes Arabic text if it contains Arabic characters, otherwise returns the text as it is. 277 | * @param {string} text - The text to be encoded if it contains Arabic characters. 278 | * @returns {string} The encoded Arabic text, or the original text if it's in English. 279 | */ 280 | static encodeArabicText(text: string): string { 281 | if (Generics.isArabicText(text)) { 282 | // Text contains Arabic characters, so encode it 283 | return encodeURIComponent(text); 284 | } else { 285 | // Text is in English, so send it as it is 286 | return text; 287 | } 288 | } 289 | static copyToClipboard = (text: string): Promise => { 290 | return new Promise((resolve, reject) => { 291 | if (!text) { 292 | Debug.error({ 293 | message: '🚨 Error copying text to clipboard', 294 | source: 'utils/generics.ts', 295 | data: text, 296 | }); 297 | 298 | reject( 299 | createError({ 300 | message: '🚨 Error copying text to clipboard', 301 | statusCode: 500, 302 | }) 303 | ); 304 | } 305 | 306 | navigator.clipboard 307 | .writeText(text) 308 | .then(() => { 309 | resolve(); 310 | }) 311 | .catch((error) => { 312 | reject(error); 313 | }); 314 | }); 315 | }; 316 | static async downloadFile(url: string): Promise { 317 | const response = await axios({ 318 | method: 'GET', 319 | url, 320 | responseType: 'blob', 321 | }); 322 | const fileName = url.split('/').pop(); 323 | const downloadUrl = window.URL.createObjectURL(new Blob([response.data])); 324 | const link = document.createElement('a')! as HTMLAnchorElement; 325 | link.href = downloadUrl; 326 | link.download = fileName!; 327 | link.click(); 328 | window.URL.revokeObjectURL(downloadUrl); 329 | } 330 | static calculatePercentage(number: number, reference: number): number { 331 | if (reference === 0) { 332 | throw createError({ 333 | statusCode: 500, 334 | statusMessage: 'Reference number cannot be zero.', 335 | }); 336 | } 337 | 338 | return (number / reference) * 100; 339 | } 340 | static convertToMinutes(number: number): number { 341 | return Math.floor(number / 60); 342 | } 343 | static convertPercentageToSeconds(percentage: number, totalDuration: number): number { 344 | const timeInSeconds = (percentage / 100) * totalDuration; 345 | return timeInSeconds; 346 | } 347 | static formatTimeProgress(seconds: number, totalDuration: number): string { 348 | const currentMinutes = Generics.convertToMinutes(seconds) || 0; 349 | const totalMinutes = Generics.convertToMinutes(totalDuration) || 0; 350 | const currentSeconds = seconds % 60 || 0; 351 | const totalSeconds = totalDuration % 60 || 0; 352 | 353 | return `${currentMinutes}:${currentSeconds.toFixed(0)} / ${totalMinutes}:${totalSeconds.toFixed( 354 | 0 355 | )}`; 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /utils/validators.ts: -------------------------------------------------------------------------------- 1 | // email validation 2 | export const validateEmail = (email: string): boolean => { 3 | const re = /\S+@\S+\.\S+/; 4 | return re.test(email); 5 | }; 6 | // password validation 7 | export const validatePassword = (password: string): boolean => { 8 | // at least 8 characters long, contain one uppercase letter, one lowercase, one number and one special character 9 | const re = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,}$/; 10 | return re.test(password); 11 | 12 | //TEST:: 12345678aA@ 13 | }; 14 | 15 | // url validation 16 | export const validateUrl = (url: string): boolean => { 17 | const re = /^(ftp|http|https):\/\/[^ "]+$/; 18 | return re.test(url); 19 | }; 20 | 21 | export const isCDNUrl = (url: string): boolean => { 22 | const cdnRegex = /^(https?:\/\/)?(?:[a-z]+\.)?(?:cdn\.)[^\s/$.?#].[^\s]*$/i; 23 | return cdnRegex.test(url); 24 | }; 25 | --------------------------------------------------------------------------------