├── src
├── vite-env.d.ts
├── index.css
├── main.tsx
├── components
│ ├── CountryClock.tsx
│ ├── LocaleClock.tsx
│ ├── Input.tsx
│ └── PrayerTimes.tsx
├── assets
│ └── react.svg
└── App.tsx
├── public
├── preview1.gif
└── vite.svg
├── postcss.config.cjs
├── vite.config.ts
├── tsconfig.node.json
├── .gitignore
├── index.html
├── tailwind.config.cjs
├── tsconfig.json
├── package.json
└── README.md
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/public/preview1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MohanadOO/prayer-time-api/HEAD/public/preview1.gif
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .inputRTL {
6 | text-align: right;
7 | unicode-bidi: bidi-override;
8 | direction: rtl;
9 | }
10 |
11 | .inputLTR {
12 | text-align: left;
13 | unicode-bidi: bidi-override;
14 | direction: ltr;
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Prayer Times
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import '@fontsource/cairo'
5 | import '@fontsource/cairo/600.css'
6 | import '@fontsource/cairo/700.css'
7 | import '@fontsource/cairo/900.css'
8 | import './index.css'
9 | import { Toaster } from 'react-hot-toast'
10 |
11 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
12 |
13 |
14 |
15 |
16 | )
17 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 |
3 | const defaultTheme = require('tailwindcss/defaultTheme')
4 | module.exports = {
5 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
6 | theme: {
7 | extend: {
8 | colors: {
9 | primary: {
10 | 800: '#22313F',
11 | 700: '#34495E',
12 | 400: '#8DC6FF',
13 | 200: '#E4F1FE',
14 | },
15 | },
16 | fontFamily: {
17 | cairo: ['Cairo', ...defaultTheme.fontFamily.sans],
18 | },
19 | },
20 | },
21 | plugins: [require('@tailwindcss/forms')],
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/CountryClock.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 |
3 | const localeTimeString = new Date()
4 | export default function CountryClock({ timeZone }: { timeZone: string }) {
5 | const [currentTime, setCurrentTime] = useState(
6 | localeTimeString.toLocaleTimeString('en-US', { timeZone })
7 | )
8 |
9 | useEffect(() => {
10 | const interval = setInterval(() => {
11 | const timestamp = new Date().getTime()
12 | const updatedTime = new Date(timestamp)
13 | setCurrentTime(updatedTime.toLocaleTimeString('en-US', { timeZone }))
14 | }, 1000)
15 | return () => clearInterval(interval)
16 | }, [])
17 |
18 | return (
19 | {currentTime}
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/LocaleClock.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 |
3 | const localeTimeString = new Date().toLocaleTimeString()
4 | export default function LocaleClock() {
5 | const [currentTime, setCurrentTime] = useState(localeTimeString)
6 |
7 | useEffect(() => {
8 | const interval = setInterval(() => {
9 | const timestamp = new Date().getTime()
10 | const updatedTime = new Date(timestamp)
11 | setCurrentTime(updatedTime.toLocaleTimeString())
12 | }, 1000)
13 | return () => clearInterval(interval)
14 | }, [])
15 |
16 | return (
17 |
18 | الوقت المحلي
19 | {currentTime}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prayer-time-api",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@fontsource/cairo": "^4.5.10",
13 | "axios": "^0.30.2",
14 | "framer-motion": "^7.3.6",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-hot-toast": "^2.4.0",
18 | "react-icons": "^4.4.0",
19 | "react-spinners-kit": "^1.9.1"
20 | },
21 | "devDependencies": {
22 | "@tailwindcss/forms": "^0.5.3",
23 | "@types/react": "^18.0.17",
24 | "@types/react-dom": "^18.0.6",
25 | "@vitejs/plugin-react": "^5.1.2",
26 | "autoprefixer": "^10.4.12",
27 | "postcss": "^8.5.6",
28 | "tailwindcss": "^3.1.8",
29 | "typescript": "^4.6.4",
30 | "vite": "^7.2.7"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # City prayer time API 🕌
2 |
3 | This project was done by using the open-source [Aladhan API](https://aladhan.com/) which has many Islamic API options to use in Islamic sites or sites visited by Muslims.
4 |
5 | ## What is Prayer Times API?
6 |
7 | Prayer Times API has many endpoints that return JSON including the Five Prayers times in a specific city, country, address, or an exact month of the year.
8 |
9 | ## Timings By City
10 |
11 | In this project I used the Timing By City endpoint to search for prayer times based on the city written.
12 |
13 | ## Preview 📺
14 |
15 | 
16 |
17 | ## Tools Used ⚙️
18 |
19 | - [React](https://reactjs.org/) - JS library
20 | - [Tailwind CSS](https://tailwindcss.com/) - CSS framework
21 | - [Framer Motion](https://www.framer.com/motion/) - A production-ready motion library for React.
22 |
23 | ## Created By Mohanad ✍️
24 |
25 | - Website - [Mohanad Portfolio](https://portfolio-mohanadoo.vercel.app/)
26 | - Frontend Mentor - [@MohanadOO\_](https://twitter.com/MohanadOO_)
27 | - Twitter - [@Mohanad_OOO](https://twitter.com/Mohanad_OOO)
28 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Input.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { BiSearchAlt } from 'react-icons/bi'
3 |
4 | type InputType = {
5 | inputRef: React.RefObject
6 | }
7 |
8 | export default function Input({ inputRef }: InputType) {
9 | const [searchInput, setSearchInput] = useState('')
10 |
11 | function handleInput(e: React.ChangeEvent) {
12 | setSearchInput(() => e.target.value)
13 |
14 | if (inputRef.current !== null) {
15 | const firstLetter = inputRef.current.value[0]
16 | const inputLength = inputRef.current.value.length
17 | if (inputLength === 1) {
18 | if (
19 | 65 <= firstLetter.charCodeAt(0) &&
20 | 122 >= firstLetter.charCodeAt(0)
21 | ) {
22 | inputRef.current.classList.add('inputLTR')
23 | } else {
24 | inputRef.current.classList.remove('inputLTR')
25 | }
26 | }
27 | }
28 | }
29 |
30 | return (
31 |
32 |
33 |
42 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/PrayerTimes.tsx:
--------------------------------------------------------------------------------
1 | import { motion } from 'framer-motion'
2 |
3 | type PrayerTimesType = {
4 | prayerTimes: {
5 | Fajr: string
6 | Sunrise: string
7 | Dhuhr: string
8 | Asr: string
9 | Maghrib: string
10 | Isha: string
11 | }
12 | }
13 |
14 | export default function PrayerTimes({ prayerTimes }: PrayerTimesType) {
15 | function toArabic(prayerName: string) {
16 | switch (prayerName) {
17 | case 'Fajr':
18 | return 'الفجر'
19 | case 'Sunrise':
20 | return 'الشروق'
21 | case 'Dhuhr':
22 | return 'الظهر'
23 | case 'Asr':
24 | return 'العصر'
25 | case 'Maghrib':
26 | return 'المغرب'
27 | case 'Isha':
28 | return 'العشاء'
29 | }
30 | }
31 |
32 | function convertTime(time: string) {
33 | const split = time.split(':')
34 | const hours = Number(split[0])
35 | const minutes = split[1]
36 |
37 | if (hours < 12) return time + ' AM'
38 |
39 | return `${
40 | hours - 12 > 10
41 | ? `0${hours - 12}:${minutes} PM`
42 | : `${hours}:${minutes} PM`
43 | }`
44 | }
45 |
46 | return (
47 |
53 | {Object.entries(prayerTimes).map((prayer) => {
54 | return (
55 |
60 | {convertTime(prayer[1])}
61 | {toArabic(prayer[0])}
62 |
63 | )
64 | })}
65 |
66 | )
67 | }
68 |
69 | const prayerListContainerVariants = {
70 | initial: { opacity: 0 },
71 | animate: {
72 | opacity: 1,
73 | transition: {
74 | staggerChildren: 0.2,
75 | },
76 | },
77 | }
78 |
79 | const prayerVariant = {
80 | initial: { x: '100%' },
81 | animate: { x: '0' },
82 | }
83 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react'
2 |
3 | import { toast } from 'react-hot-toast'
4 | import { motion } from 'framer-motion'
5 |
6 | import axios from 'axios'
7 | import Input from './components/Input'
8 |
9 | import PrayerTimes from './components/PrayerTimes'
10 | import LocaleClock from './components/LocaleClock'
11 | import CountryClock from './components/CountryClock'
12 | import { CircleSpinner } from 'react-spinners-kit'
13 |
14 | const ALADHAN_API_BASE_URL = 'https://api.aladhan.com/v1/timingsByCity'
15 | const BIGDATACLOUD_BASE_URL =
16 | 'https://api.bigdatacloud.net/data/reverse-geocode-client'
17 |
18 | type DateType = {
19 | gregorian: string
20 | hijri: {
21 | day: string
22 | month: { number: number }
23 | weekday: { ar: string }
24 | year: string
25 | }
26 | }
27 |
28 | type LocationNameType = {
29 | city: string
30 | country: string
31 | timezone: string
32 | }
33 |
34 | type PrayerTimesType = {
35 | Fajr: string
36 | Sunrise: string
37 | Dhuhr: string
38 | Asr: string
39 | Maghrib: string
40 | Isha: string
41 | }
42 |
43 | function App() {
44 | const inputRef = useRef(null)
45 |
46 | const [isLoading, setIsLoading] = useState(false)
47 | const [isError, setIsError] = useState(false)
48 |
49 | const [prayerTimes, setPrayerTimes] = useState(null)
50 | const [date, setDate] = useState(null)
51 | const [locationName, setLocationName] = useState(
52 | null
53 | )
54 |
55 | function handleSubmit(e: React.FormEvent) {
56 | e.preventDefault()
57 | if (inputRef.current !== null) {
58 | if (inputRef.current.value === '') return
59 | setIsLoading(true)
60 | searchForLocation(inputRef.current.value)
61 | }
62 | }
63 |
64 | async function searchForLocation(city: string) {
65 | axios
66 | .get(ALADHAN_API_BASE_URL, {
67 | params: {
68 | city,
69 | country: city,
70 | },
71 | })
72 | .then((response) => {
73 | const data = response.data.data
74 |
75 | const { day, month, weekday, year } = data.date.hijri
76 | setDate({
77 | gregorian: data.date.readable,
78 | hijri: { day, month: month.number, weekday, year },
79 | })
80 |
81 | const { Fajr, Sunrise, Dhuhr, Asr, Maghrib, Isha } = data.timings
82 | setPrayerTimes({ Fajr, Sunrise, Dhuhr, Asr, Maghrib, Isha })
83 |
84 | const { latitude, longitude, timezone } = data.meta
85 | searchLocationName(latitude, longitude, timezone)
86 | })
87 | .catch((err) => {
88 | console.error(err)
89 | setIsLoading(false)
90 | setIsError(true)
91 | toast.error('لا يوجد مكان بهذا الأسم')
92 | })
93 | }
94 |
95 | async function searchLocationName(
96 | latitude: number,
97 | longitude: number,
98 | timezone: string
99 | ) {
100 | axios
101 | .get(BIGDATACLOUD_BASE_URL, {
102 | params: {
103 | latitude,
104 | longitude,
105 | localityLanguage: 'ar',
106 | },
107 | })
108 | .then((response) => {
109 | const { city, countryName } = response.data
110 | setLocationName({ city, country: countryName, timezone })
111 | })
112 | .catch((err) => {
113 | console.error(err)
114 | if (inputRef.current !== null) {
115 | setLocationName({
116 | city: inputRef.current.value,
117 | country: '',
118 | timezone,
119 | })
120 | }
121 | })
122 | .finally(() => {
123 | setIsLoading(false)
124 | setIsError(false)
125 | })
126 | }
127 |
128 | const hijriDate = `(${date?.hijri.year}/${date?.hijri.month}/${date?.hijri.day})`
129 | const gregorianDate = date?.gregorian
130 |
131 | return (
132 |
133 |
134 |
146 |
147 | {isLoading ? (
148 |
149 |
150 |
151 | ) : isError ? (
152 | ❌
153 | ) : (
154 |
155 | {locationName === null ? (
156 |
157 | اكتب اسم المدينة في خانة البحث
158 |
159 | ) : (
160 | <>
161 |
162 | {locationName.city} {',' + locationName.country}
163 |
164 |
165 |
166 |
{date?.hijri.weekday.ar}
167 |
168 |
169 |
{gregorianDate}
170 |
{hijriDate}
171 |
172 |
173 |
174 |
175 | >
176 | )}
177 |
178 | )}
179 |
180 | )
181 | }
182 |
183 | export default App
184 |
--------------------------------------------------------------------------------