├── .eslintrc.json
├── .gitignore
├── README.md
├── app
├── api
│ └── geolocation
│ │ └── routes.ts
├── brand.ico
├── globals.css
├── layout.tsx
├── niat
│ ├── Niat.tsx
│ └── page.tsx
├── page.tsx
├── resep
│ ├── Resep.tsx
│ └── page.tsx
└── tadarus
│ ├── Tadarus.tsx
│ ├── page.tsx
│ └── surah
│ └── [id]
│ ├── Surah.tsx
│ └── page.tsx
├── components
├── audioButton
│ └── page.tsx
├── audioButtonAyahs
│ └── page.tsx
├── geocoding
│ └── page.tsx
├── hero
│ └── page.tsx
├── notification
│ └── page.tsx
├── particle
│ └── page.tsx
├── quran
│ └── page.tsx
├── ramadhanCountdown
│ └── page.tsx
├── star
│ └── page.tsx
├── surahDetail
│ └── page.tsx
└── weather
│ └── page.tsx
├── fonts
├── AmazingRamadhan.otf
├── Grako-Demo-Regular.otf
├── Kahlil.otf
├── OpenSans-Bold.otf
├── QiyamuRamadhan.otf
├── RamadhanAmazing-jEnDv.ttf
├── SanshiroDemo-Black.otf
├── coolvetica rg.otf
└── monasgrotesk-bold.otf
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── brand.ico
├── img
│ ├── bunderan.png
│ ├── bxs-home.svg
│ ├── gapura.png
│ ├── gapura2.png
│ ├── home.png
│ └── lantern.png
├── next.svg
└── vercel.svg
├── redux
├── rootReducer.ts
├── store.ts
├── surahActions.ts
└── surahReducer.ts
├── tailwind.config.ts
├── tsconfig.json
└── utils
└── config.ts
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/app/api/geolocation/routes.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function GET(request: Request) {
4 | try {
5 | const { searchParams } = new URL(request.url);
6 | const latitude = searchParams.get('latitude');
7 | const longitude = searchParams.get('longitude');
8 |
9 | const API_KEY = process.env.OPENCAGE_API_KEY;
10 |
11 | const response = await fetch(
12 | `https://api.opencagedata.com/geocode/v1/json?q=${latitude}+${longitude}&key=${API_KEY}&language=id`
13 | );
14 |
15 | const data = await response.json();
16 | return NextResponse.json(data);
17 |
18 |
19 | } catch (error) {
20 | return NextResponse.json(
21 | { error: 'Failed to fetch location' },
22 | { status: 500 }
23 | );
24 | }
25 | }
--------------------------------------------------------------------------------
/app/brand.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/app/brand.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
6 |
7 | @font-face {
8 | font-family: 'open';
9 | src: url('../fonts/OpenSans-Bold.otf');
10 | }
11 |
12 | @font-face {
13 | font-family: 'sansi';
14 | src: url('../fonts/SanshiroDemo-Black.otf');
15 | }
16 |
17 | @font-face {
18 | font-family: 'aramadhan';
19 | src: url('../fonts/AmazingRamadhan.otf');
20 | }
21 |
22 | @font-face {
23 | font-family: 'grako';
24 | src: url('../fonts/Grako-Demo-Regular.otf');
25 | }
26 |
27 | @font-face {
28 | font-family: 'monas';
29 | src: url('../fonts/monasgrotesk-bold.otf');
30 | }
31 |
32 | @font-face {
33 | font-family: 'kahlil';
34 | src: url('../fonts/Kahlil.otf');
35 | }
36 |
37 | @font-face {
38 | font-family: 'qramadhan';
39 | src: url('../fonts/QiyamuRamadhan.otf');
40 | }
41 |
42 | @font-face {
43 | font-family: 'aaramadhan';
44 | src: url('../fonts/RamadhanAmazing-jEnDv.ttf');
45 | }
46 |
47 | @font-face {
48 | font-family: 'coolvetica';
49 | src: url('../fonts/coolvetica\ rg.otf');
50 | }
51 |
52 | /* styles/starryBackground.css */
53 | .bodi {
54 | font-family: "Poppins", sans-serif;
55 | font-weight: 300;
56 | font-style: normal;
57 | background: linear-gradient(30deg, #0d0a0b, #0d2818, #0d0a0b);
58 | color: white;
59 | background-size: 200% auto;
60 | animation: gradientAnimation 50s ease-in-out infinite;
61 | overflow-x: hidden;
62 | min-height: 100vh;
63 | margin: 0;
64 | position: relative;
65 | }
66 |
67 | @keyframes gradientAnimation {
68 | 0% { background-position: 0% 50%; }
69 | 50% { background-position: 100% 50%; }
70 | 100% { background-position: 0% 50%; }
71 | }
72 |
73 | .stars {
74 | position: fixed;
75 | top: 0;
76 | left: 0;
77 | width: 100%;
78 | height: 100%;
79 | pointer-events: none;
80 | z-index: 0;
81 | }
82 |
83 | .star {
84 | position: absolute;
85 | width: 2px;
86 | height: 2px;
87 | background: white;
88 | border-radius: 50%;
89 | opacity: 0;
90 | animation: twinkle var(--duration) ease-in-out infinite;
91 | animation-delay: var(--delay);
92 | }
93 |
94 | @keyframes twinkle {
95 | 0%, 100% { opacity: 0; transform: scale(0.3); }
96 | 50% { opacity: 0.8; transform: scale(1); }
97 | }
98 |
99 | .bodi-niat {
100 | min-height: 117vh;
101 | }
102 |
103 | .bodi-resep {
104 | min-height: 100vh;
105 | }
106 |
107 | .bodi-tadarus {
108 | min-height: 100vh;
109 | }
110 |
111 | @media only screen and (max-width: 640px) {
112 | .bodi {
113 | min-height: 100vh;
114 | }
115 |
116 | .bodi-hero {
117 | min-height: 160vh;
118 | }
119 |
120 | /*.parent {
121 | display: grid;
122 | grid-template-columns: 1fr;
123 | grid-template-rows: repeat(12, 1fr);
124 | grid-column-gap: 0px;
125 | grid-row-gap: 16px;
126 | }*/
127 |
128 |
129 | }
130 |
131 | @media only screen and (max-width: 414px) {
132 | .bodi-hero {
133 | min-height: 215vh
134 | }
135 |
136 | .bodi-niat {
137 | min-height: 80vh;
138 | }
139 | }
140 |
141 |
142 |
143 | @keyframes gradientAnimation {
144 | 0% {
145 | background-position: 0% 50%; /* Mulai dari posisi kiri tengah */
146 | }
147 | 50% {
148 | background-position: 100% 50%; /* Posisi di tengah jalan saat animasi */
149 | }
150 | 100% {
151 | background-position: 0% 50%; /* Kembali ke posisi awal */
152 | }
153 | }
154 |
155 |
156 | .poppins-thin {
157 | font-family: "Poppins", sans-serif;
158 | font-weight: 100;
159 | font-style: normal;
160 | }
161 |
162 | .poppins-extralight {
163 | font-family: "Poppins", sans-serif;
164 | font-weight: 200;
165 | font-style: normal;
166 | }
167 |
168 | .poppins-light {
169 | font-family: "Poppins", sans-serif;
170 | font-weight: 300;
171 | font-style: normal;
172 | }
173 |
174 | .poppins-regular {
175 | font-family: "Poppins", sans-serif;
176 | font-weight: 400;
177 | font-style: normal;
178 | }
179 |
180 | .poppins-medium {
181 | font-family: "Poppins", sans-serif;
182 | font-weight: 500;
183 | font-style: normal;
184 | }
185 |
186 | .poppins-semibold {
187 | font-family: "Poppins", sans-serif;
188 | font-weight: 600;
189 | font-style: normal;
190 | }
191 |
192 | .poppins-bold {
193 | font-family: "Poppins", sans-serif;
194 | font-weight: 700;
195 | font-style: normal;
196 | }
197 |
198 | .poppins-extrabold {
199 | font-family: "Poppins", sans-serif;
200 | font-weight: 800;
201 | font-style: normal;
202 | }
203 |
204 | .poppins-black {
205 | font-family: "Poppins", sans-serif;
206 | font-weight: 900;
207 | font-style: normal;
208 | }
209 |
210 | .poppins-thin-italic {
211 | font-family: "Poppins", sans-serif;
212 | font-weight: 100;
213 | font-style: italic;
214 | }
215 |
216 | .poppins-extralight-italic {
217 | font-family: "Poppins", sans-serif;
218 | font-weight: 200;
219 | font-style: italic;
220 | }
221 |
222 | .poppins-light-italic {
223 | font-family: "Poppins", sans-serif;
224 | font-weight: 300;
225 | font-style: italic;
226 | }
227 |
228 | .poppins-regular-italic {
229 | font-family: "Poppins", sans-serif;
230 | font-weight: 400;
231 | font-style: italic;
232 | }
233 |
234 | .poppins-medium-italic {
235 | font-family: "Poppins", sans-serif;
236 | font-weight: 500;
237 | font-style: italic;
238 | }
239 |
240 | .poppins-semibold-italic {
241 | font-family: "Poppins", sans-serif;
242 | font-weight: 600;
243 | font-style: italic;
244 | }
245 |
246 | .poppins-bold-italic {
247 | font-family: "Poppins", sans-serif;
248 | font-weight: 700;
249 | font-style: italic;
250 | }
251 |
252 | .poppins-extrabold-italic {
253 | font-family: "Poppins", sans-serif;
254 | font-weight: 800;
255 | font-style: italic;
256 | }
257 |
258 | .poppins-black-italic {
259 | font-family: "Poppins", sans-serif;
260 | font-weight: 900;
261 | font-style: italic;
262 | }
263 |
264 | .opensans {
265 | font-family: 'open';
266 | }
267 |
268 | .sanshiro {
269 | font-family: 'sansi';
270 | }
271 |
272 | .grako {
273 | font-family: 'grako';
274 | }
275 |
276 | .monas {
277 | font-family: 'monas';
278 | }
279 |
280 | .kahlil {
281 | font-family: 'kahlil';
282 | }
283 |
284 | .aramadhan {
285 | font-family: 'aaramadhan';
286 | }
287 |
288 | .qramadhan {
289 | font-family: 'qramadhan';
290 | }
291 |
292 | .coolvetica {
293 | font-family: 'coolvetica';
294 | }
295 |
296 | .active-box {
297 | background-image: linear-gradient(to bottom, #0d2818, #0d0a0b);
298 | background-size: 100% 200%; /* Menentukan ukuran latar belakang */
299 | animation: rareEffect 1s infinite ease-in-out alternate; /* Durasi, jenis, dan iterasi animasi */
300 | }
301 |
302 | .active-shadow {
303 | box-shadow: 0px 0px 25px -1px rgba(0,0,0,0.42);
304 | }
305 |
306 | .pagination {
307 | display: flex;
308 | justify-content: center;
309 | align-items: center;
310 | margin-top: 2rem;
311 | }
312 | .page-link {
313 | padding: 0.5rem 1rem;
314 | margin: 0 0.25rem;
315 | border: 1px solid #3e664e; /* Warna border yang sama dengan div di atasnya */
316 | border-radius: 4px;
317 | color: #3e664e;
318 | cursor: pointer;
319 | }
320 | .page-link:hover {
321 | background-color: #3e664e;
322 | color: white;
323 | }
324 | .active {
325 | background-color: #3e664e;
326 | color: whitesmoke;
327 |
328 | }
329 | .disabled {
330 | opacity: 0.5;
331 | cursor: not-allowed;
332 | }
333 |
334 | input[type=range]::-webkit-slider-runnable-track {
335 | background-color: #3e664e; /* Ubah warna sesuai kebutuhan Anda */
336 | }
337 |
338 |
339 | @keyframes rareEffect {
340 | 0% {
341 | background-position: 0% 0%; /* Posisi awal gradient */
342 | }
343 | 100% {
344 | background-position: 0% 100%; /* Posisi akhir gradient */
345 | }
346 | }
347 |
348 | @keyframes fadeIn {
349 | from {
350 | opacity: 0;
351 | transform: translateY(-10px);
352 | }
353 | to {
354 | opacity: 1;
355 | transform: translateY(0);
356 | }
357 | }
358 |
359 | .animate-fade-in {
360 | animation: fadeIn 0.5s ease-out forwards;
361 | }
362 |
363 |
364 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 | import StarryBackground from "@/components/star/page";
5 |
6 |
7 | export const metadata: Metadata = {
8 | title: "Awas Imsak! - Jadwal Sholat & Imsak",
9 | icons: {
10 | icon: '/brand.ico'
11 | },
12 | description: "Awas Imsak! adalah portal yang menghadirkan jadwal sholat dan imsak dengan akurat dan mudah diakses",
13 | applicationName: 'Awas Imsak!',
14 | referrer: 'origin-when-cross-origin',
15 | keywords: ['puasa', 'imsak', 'sholat', 'ramadhan', 'jadwal imsak', 'jadwal sholat', 'quran', 'baca quran', 'puasa ramadhan', 'bulan ramadhan'],
16 | authors: [{ name: 'Klaw' }, { name: 'Muhammad Dimas', url: 'https://klaw.my.id' }],
17 | creator: 'Klaw',
18 | publisher: 'RIOT REVENGER',
19 | formatDetection: {
20 | email: false,
21 | address: false,
22 | telephone: false,
23 | },
24 | openGraph: {
25 | title: 'Awas Imsak! - Jadwal Sholat & Imsak',
26 | description: 'Awas Imsak! adalah portal yang menghadirkan jadwal sholat dan imsak dengan akurat dan mudah diakses',
27 | url: 'https://imsak.my.id',
28 | siteName: 'Awas Imsak!',
29 | type: 'website',
30 | },
31 | };
32 |
33 | export default function RootLayout({
34 | children,
35 | }: Readonly<{
36 | children: React.ReactNode;
37 | }>) {
38 | return (
39 |
40 |
41 |
42 |
43 | {children}
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/app/niat/Niat.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 |
3 | const Niat = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 | ⬅ Back to Home
12 |
13 |
14 |
15 |
29 | Awas Lupa Niat
30 |
31 |
Awas kawan jangan sampai lupa!
32 |
33 |
34 |
35 |
36 | Niat Puasa Ramadhan untuk Sehari:
37 |
38 |
39 | نَوَيْتُ صَوْمَ غَدٍ عَنْ أَدَاءِ فَرْضِ شَهْرِ رَمَضَانَ هَذِهِ
40 | السَّنَةِ لِلّٰهِ تَعَالَى
41 |
42 |
43 | Nawaitu shauma ghadin 'an ada'i fardhi syahri Ramadhana
44 | hadzihis sanati lillahi ta'ala.
45 |
46 |
47 | Artinya: “Aku niat berpuasa esok hari untuk menunaikan kewajiban
48 | puasa bulan Ramadhan tahun ini, karena Allah Ta'ala.”
49 |
50 |
51 |
52 |
53 | Niat Puasa Ramadhan untuk Sebulan Penuh:
54 |
55 |
56 | نَوَيْتُ صَوْمَ جَمِيْعِ شَهْرِ رَمَضَانِ هٰذِهِ السَّنَةِ فَرْضًا
57 | لِلّٰهِ تَعَالَى
58 |
59 |
60 | Nawaitu shauma jami'i syahri Ramadhani hadzihis sanati
61 | fardhan lillahi ta'ala.
62 |
63 |
64 | Artinya: “Aku niat berpuasa di sepanjang bulan Ramadhan tahun ini
65 | dengan mengikuti pendapat Imam Malik, wajib karena Allah
66 | Ta'ala.”
67 |
68 |
69 |
70 |
71 |
72 |
73 | Awas Lupa Tadarus
74 |
75 |
76 |
77 |
78 | Awas Lupa Buka
79 |
80 |
81 |
82 |
83 |
84 | );
85 | }
86 |
87 | export default Niat
--------------------------------------------------------------------------------
/app/niat/page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import type { Metadata } from "next";
3 | import Niat from './Niat'
4 |
5 | export const metadata: Metadata = {
6 | title: "Awas Imsak! - Niat Puasa Bulan Ramadhan",
7 | description: "Awas Imsak! adalah portal yang menghadirkan jadwal sholat dan imsak dengan akurat dan mudah diakses",
8 | };
9 |
10 | const NiatPage = () => {
11 | return (
12 |
13 | )
14 | }
15 |
16 | export default NiatPage
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useState, useEffect } from 'react';
4 | import Hero from '../components/hero/page'
5 |
6 | export default function Home() {
7 |
8 | const [countdown, setCountdown] = useState('');
9 | const [countdownFinished, setCountdownFinished] = useState(false);
10 |
11 | // state baru untuk control tampil/hilang text
12 | const [showText, setShowText] = useState(true);
13 |
14 | useEffect(() => {
15 |
16 | const targetDate = new Date('2024-03-11T18:30:00').getTime();
17 |
18 | const timer = setInterval(() => {
19 |
20 | const now = new Date().getTime();
21 |
22 | const distance = targetDate - now;
23 |
24 | if(distance <= 0) {
25 |
26 | clearInterval(timer);
27 |
28 | setCountdownFinished(true);
29 | setShowText(true);
30 |
31 | setTimeout(() => {
32 | setCountdown('');
33 | setShowText(false);
34 | }, 3000);
35 |
36 | } else {
37 |
38 | // hitung countdown
39 | const days = Math.floor(distance / (1000 * 60 * 60 * 24));
40 | const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
41 | const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
42 | const seconds = Math.floor((distance % (1000 * 60)) / 1000);
43 |
44 | setCountdown(`${days} hari ${hours} jam ${minutes} menit ${seconds} detik`);
45 | }
46 |
47 | }, 1000);
48 |
49 | return () => clearInterval(timer);
50 |
51 | }, []);
52 |
53 | // effect untuk handle timeout
54 | useEffect(() => {
55 | if(countdownFinished) {
56 | setTimeout(() => {
57 | setShowText(false);
58 | }, 3000);
59 | }
60 | }, [countdownFinished]);
61 |
62 | return (
63 | <>
64 | {/* Tampilkan penomoran halaman {!countdownFinished && (
65 | <>
66 |
67 |
84 | {countdown}
85 |
86 |
Menuju Puasa Ramadhan 1445 H / 2024 M
87 |
"What makes your sorry different from all your other sorrys before?"
88 |
89 |
102 | Awas Imsak!
103 |
104 |
105 | © 2024 Klaw, under RIOT REVENGER exclusive agreements.
106 |
107 |
108 |
109 | >
110 | )}
111 |
112 | {countdownFinished && showText && (
113 | <>
114 |
115 |
131 | Selamat menunaikan ibadah Puasa!
132 |
133 |
134 | >
135 | )}
136 |
137 | {countdownFinished && !showText && }*/}
138 |
139 | >
140 | );
141 | }
142 |
--------------------------------------------------------------------------------
/app/resep/Resep.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import React, { useState, useEffect } from 'react';
4 | import Image from 'next/image'
5 | import Link from 'next/link'
6 | import ReactPaginate from 'react-paginate';
7 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8 | import { faClock, faBookOpen, faSearch } from '@fortawesome/free-solid-svg-icons';
9 |
10 | interface Resep {
11 | title: string;
12 | time: string;
13 | difficulty: string;
14 | 'image-src': string;
15 | 'link-href': string;
16 | }
17 |
18 | const Resep: React.FC = () => {
19 | const [resepList, setResepList] = useState([]);
20 | const [searchKeyword, setSearchKeyword] = useState('');
21 | const [currentPage, setCurrentPage] = useState(0);
22 | const resepPerPage = 6;
23 | const [pageRangeDisplayed, setPageRangeDisplayed] = useState(5);
24 | const [marginPagesDisplayed, setMarginPagesDisplayed] = useState(2);
25 |
26 | useEffect(() => {
27 | fetch('https://mahi-unofficial.netlify.app/.netlify/functions/server/makanMalam', {
28 | method: 'GET',
29 | headers: {
30 | 'Content-Type': 'application/json',
31 | },
32 | })
33 | .then(response => {
34 | if (!response.ok) {
35 | throw new Error(`HTTP error! status: ${response.status}`);
36 | }
37 | return response.json();
38 | })
39 | .then((data: Resep[]) => setResepList(data))
40 | .catch(error => console.error('Error fetching data:', error));
41 | }, []);
42 |
43 | // Fungsi untuk menangani perubahan halaman
44 | const handlePageChange = ({ selected }: { selected: number }) => {
45 | setCurrentPage(selected);
46 | };
47 |
48 | // Fungsi untuk melakukan filter berdasarkan keyword pencarian
49 | const filteredResep = resepList.filter(resep =>
50 | resep.title.toLowerCase().includes(searchKeyword.toLowerCase())
51 | );
52 |
53 | // Menampilkan resep sesuai halaman yang dipilih
54 | const indexOfLastResep = (currentPage + 1) * resepPerPage;
55 | const indexOfFirstResep = indexOfLastResep - resepPerPage;
56 | const currentResep = filteredResep.slice(indexOfFirstResep, indexOfLastResep);
57 |
58 | // Mengubah nilai pageRangeDisplayed berdasarkan lebar layar
59 | useEffect(() => {
60 | const handleResize = () => {
61 | setPageRangeDisplayed(window.innerWidth <= 640 ? 2 : 5);
62 | setMarginPagesDisplayed(window.innerWidth <= 640 ? 0 : 2);
63 | };
64 |
65 | handleResize();
66 |
67 | window.addEventListener('resize', handleResize);
68 |
69 | return () => {
70 | window.removeEventListener('resize', handleResize);
71 | };
72 | }, []);
73 |
74 | return (
75 | <>
76 |
77 |
81 |
82 |
83 | ⬅ Back to Home
84 |
85 |
86 |
87 |
101 | Awas Lupa Masak
102 |
103 |
104 | Masa udah puasa malah ga buka gara-gara ga masak.
105 |
106 |
107 |
108 |
109 |
setSearchKeyword(e.target.value)}
114 | className="bg-[#0d1811] border border-[#3e664e] text-white px-10 py-2 rounded-lg w-[20%] mb-4 mt-5 max-[640px]:w-[60%]"
115 | />
116 |
117 |
118 |
119 |
120 |
121 |
122 | {/* Menampilkan pesan jika hasil pencarian tidak ditemukan */}
123 | {filteredResep.length === 0 && (
124 |
125 |
Yah... masakannya ga ketemu, coba cari yang lain dehh
126 |
127 | )}
128 |
129 | {currentResep.map((resep, index) => (
130 |
134 |
135 |
143 |
144 |
{resep.title}
145 |
146 |
147 | {" "}
148 | {resep.time ? resep.time : " -"}
149 |
150 |
151 | {resep.difficulty ? resep.difficulty : " -"}
152 |
153 |
157 |
158 |
159 |
160 |
161 | ))}
162 |
163 |
164 | {/* Tampilkan penomoran halaman */}
165 |
←}
173 | nextLabel={→ }
174 | breakLabel={"..."}
175 | pageLinkClassName={"page-link"}
176 | previousLinkClassName={"page-link"}
177 | nextLinkClassName={"page-link"}
178 | disabledClassName={"disabled"}
179 | />
180 |
181 | >
182 | );
183 | };
184 |
185 | export default Resep;
186 |
--------------------------------------------------------------------------------
/app/resep/page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import type { Metadata } from "next";
3 | import Resep from './Resep'
4 |
5 | export const metadata: Metadata = {
6 | title: "Awas Imsak! - Resep masak untuk keluarga.",
7 | description: "Awas Imsak! adalah portal yang menghadirkan jadwal sholat dan imsak dengan akurat dan mudah diakses",
8 | };
9 |
10 | const ResepPage = () => {
11 | return (
12 |
13 | )
14 | }
15 |
16 | export default ResepPage
--------------------------------------------------------------------------------
/app/tadarus/Tadarus.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Quran from "../../components/quran/page";
4 | import { Provider } from 'react-redux';
5 | import store from '../../redux/store';
6 | import Link from 'next/link'
7 |
8 | const Tadarus = () => {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 | ⬅ Back to Home
17 |
18 |
19 |
37 | Awas Lupa Tadarus
38 |
39 |
40 | Jangan lupa tadarus di sini! Baca quran online, buat gadget anda lebih berfaedah!
41 |
42 |
43 |
48 |
49 | >
50 | );
51 | };
52 |
53 | export default Tadarus;
--------------------------------------------------------------------------------
/app/tadarus/page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import type { Metadata } from "next";
3 | import Tadarus from './Tadarus'
4 |
5 | export const metadata: Metadata = {
6 | title: "Awas Imsak! - Baca Quran Online",
7 | description: "Awas Imsak! adalah portal yang menghadirkan jadwal sholat dan imsak dengan akurat dan mudah diakses",
8 | };
9 |
10 | const TadarusPage = () => {
11 | return (
12 |
13 | )
14 | }
15 |
16 | export default TadarusPage
--------------------------------------------------------------------------------
/app/tadarus/surah/[id]/Surah.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 | import { usePathname, useRouter, useSearchParams } from "next/navigation";
5 | import SurahDetail from "../../../../components/surahDetail/page";
6 | import AudioButtonAyahs from "../../../../components/audioButtonAyahs/page"
7 | import AudioButtonWithSlider from "../../../../components/audioButton/page"
8 | import Link from 'next/link'
9 | import Image from 'next/image'
10 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11 | import { faArrowLeft, faArrowRight, faArrowRotateLeft, faHome } from '@fortawesome/free-solid-svg-icons';
12 |
13 | interface Ayah {
14 | number: {
15 | inquran: number;
16 | insurah: number;
17 | };
18 | juz: number;
19 | manzil: number;
20 | page: number;
21 | ruku: number;
22 | hizbQuarter: number;
23 | sajda: {
24 | recommended: boolean;
25 | obligatory: boolean;
26 | };
27 | text: {
28 | ar: string;
29 | read: string;
30 | };
31 | translation: {
32 | en: string;
33 | id: string;
34 | };
35 | tafsir: {
36 | id: string;
37 | en: string | null;
38 | };
39 | audio: {
40 | url: string;
41 | };
42 | }
43 |
44 | // Definisikan struktur data surah
45 | interface SurahData {
46 | number: number;
47 | ayahCount: number;
48 | sequence: number;
49 | asma: {
50 | ar: {
51 | short: string;
52 | long: string;
53 | };
54 | en: {
55 | short: string;
56 | long: string;
57 | };
58 | id: {
59 | short: string;
60 | long: string;
61 | };
62 | translation: {
63 | en: string;
64 | id: string;
65 | };
66 | };
67 | preBismillah: any;
68 | type: {
69 | ar: string;
70 | id: string;
71 | en: string;
72 | };
73 | tafsir: {
74 | id: string;
75 | en: string | null;
76 | };
77 | recitation: {
78 | full: string;
79 | };
80 | ayahs: Ayah[];
81 | }
82 |
83 | const Icons = {
84 | width: '30px',
85 | height: 'auto',
86 | '@media (min-width: 360px)': {
87 | width: '5px' // Ubah lebar gambar saat lebar layar <= 640px
88 | },
89 | fill: 'white' // Mengatur warna ikon menjadi putih
90 | };
91 |
92 | const SurahDetails: React.FC = () => {
93 | const searchParams = useSearchParams();
94 | const pathname = usePathname();
95 | const surahId = searchParams.get("surahId");
96 | const [surahData, setSurahData] = useState(null);
97 | const [showTafsir, setShowTafsir] = useState(false);
98 | const [previousSurahName, setPreviousSurahName] = useState('');
99 | const [nextSurahName, setNextSurahName] = useState('');
100 | const [isOn, setIsOn] = useState(false);
101 | const [activeAyahs, setActiveAyahs] = useState([]);
102 |
103 |
104 |
105 | {
106 | /* useEffect(() => {
107 | // Cek apakah sedang berada di lingkungan klien
108 | if (typeof window !== "undefined" && surahId) {
109 | const fetchSurahData = async () => {
110 | try {
111 | const response = await fetch(
112 | `https://quran-endpoint.vercel.app/quran/${surahId}`
113 | );
114 | if (!response.ok) {
115 | throw new Error("Failed to fetch data");
116 | }
117 | const data = await response.json();
118 | setSurahData(data);
119 | } catch (error) {
120 | console.error("Error fetching surah data:", error);
121 | }
122 | };
123 |
124 | fetchSurahData();
125 | }
126 | // Mendapatkan URL saat ini
127 | //const currentURL = window.location.href;
128 |
129 | // Memisahkan URL berdasarkan tanda '/' (slash)
130 | //const urlParts = currentURL.split("/");
131 |
132 | // Mengambil parameter terakhir
133 | //const lastParam = urlParts[urlParts.length - 1];
134 |
135 | //console.log(lastParam); // Ini akan mencetak keluar "1?"
136 |
137 | //const url = `${pathname}?${searchParams}`
138 | //console.log(url)
139 | }, [pathname, searchParams, surahId]);*/
140 | }
141 |
142 | const getPageLocalStorageKey = () => {
143 | if (typeof window !== "undefined") {
144 | return `activeAyahs_${window.location.pathname}`;
145 | }
146 | return "";
147 | };
148 |
149 | useEffect(() => {
150 | const storedData = localStorage.getItem(getPageLocalStorageKey());
151 | if (storedData) {
152 | setActiveAyahs(JSON.parse(storedData));
153 | }
154 | }, []);
155 |
156 |
157 | useEffect(() => {
158 | if (typeof window !== "undefined") {
159 | const currentURL = window.location.href;
160 |
161 | const urlParts = currentURL.split("/");
162 |
163 | const surahId = urlParts[urlParts.length - 1];
164 |
165 | const fetchSurahData = async () => {
166 | try {
167 | const response = await fetch(
168 | `https://quran-endpoint.vercel.app/quran/${surahId}`
169 | );
170 | if (!response.ok) {
171 | throw new Error("Failed to fetch data");
172 | }
173 | const data = await response.json();
174 | setSurahData(data);
175 | } catch (error) {
176 | console.error("Error fetching surah data:", error);
177 | }
178 | };
179 |
180 | if (surahId) {
181 | fetchSurahData();
182 | }
183 | }
184 | }, []);
185 |
186 | if (!surahData) {
187 | return (
188 | <>
189 |
200 | >
201 | );
202 | }
203 |
204 | const { ayahs } = surahData.data;
205 | const allAyahTextsRead = ayahs.map((ayah: { text: any }) => ayah.text.read);
206 | const allAyahTransId = ayahs.map(
207 | (ayah: { translation: any }) => ayah.translation.id
208 | );
209 | const allAyahNumbers = ayahs.map(
210 | (ayah: { number: any }) => ayah.number.insurah
211 | );
212 | const allAyahTextsAr = ayahs.map((ayah: { text: any }) => ayah.text.ar);
213 | const allAyahAudio = ayahs.map((ayah: { audio: any }) => ayah.audio.url);
214 |
215 | const navigateToSurah = (surahNumber: number) => {
216 | // Pastikan nomor surah berada dalam rentang yang valid (1-114)
217 | if (surahNumber >= 1 && surahNumber <= 114) {
218 | // Lakukan navigasi ke surah tertentu
219 | window.location.href = `/tadarus/surah/${surahNumber}`;
220 | }
221 | };
222 |
223 |
224 |
225 |
226 |
227 | const handleClick = (index: number) => {
228 | let newActiveAyahs: number[] = [];
229 | if (activeAyahs.includes(index)) {
230 | newActiveAyahs = activeAyahs.filter((ayahIndex) => ayahIndex !== index);
231 | } else {
232 | newActiveAyahs = [index];
233 | }
234 | localStorage.setItem(getPageLocalStorageKey(), JSON.stringify(newActiveAyahs));
235 | setActiveAyahs(newActiveAyahs);
236 | };
237 |
238 |
239 |
240 | return (
241 |
242 |
243 |
244 |
245 |
246 | Al-Fatihah
247 |
248 |
249 | {surahData && surahData.data.number !== 1 && (
250 | <>
251 |
navigateToSurah(surahData.data.number - 1)}
254 | disabled={surahData.data.number === 1}
255 | >
256 |
257 |
258 | >
259 | )}
260 |
261 |
262 |
263 |
264 |
265 |
266 | {surahData && surahData.data.number !== 114 && (
267 |
navigateToSurah(surahData.data.number + 1)}
270 | >
271 |
272 |
273 | )}
274 |
275 |
276 | An-Nas
277 |
278 |
279 |
280 |
{surahData.data.asma.ar.short}
281 |
295 | {surahData.data.asma.id.short}
296 |
297 |
298 |
299 |
{surahData.data.ayahCount} Ayat
300 |
{surahData.data.type.id}
301 |
302 |
303 |
setShowTafsir(!showTafsir)}
306 | >
307 | {showTafsir ? "Sembunyikan Tafsir" : "Tampilkan Tafsir"}
308 |
309 |
310 | {showTafsir && (
311 |
312 |
313 |
Tafsir:
314 |
{surahData.data.tafsir.id}
315 |
316 |
317 | )}
318 |
319 |
323 |
324 |
325 |
326 | {surahData.data.preBismillah && (
327 |
328 |
{surahData.data.preBismillah.text.ar}
329 |
330 | {surahData.data.preBismillah.text.read}
331 |
332 |
{surahData.data.preBismillah.translation.id}
333 |
334 | )}
335 | {allAyahTextsAr.map((text: string, index: number) => (
336 |
340 |
341 |
handleClick(index)}
350 | >
351 | {allAyahNumbers[index]}
352 |
353 |
354 |
{text}
355 |
356 |
{allAyahTextsRead[index]}
357 |
{allAyahTransId[index]}
358 |
359 |
360 | ))}
361 |
362 |
363 |
364 |
365 |
366 | Al-Fatihah
367 |
368 |
369 | {surahData && surahData.data.number !== 1 && (
370 | <>
371 |
navigateToSurah(surahData.data.number - 1)}
374 | disabled={surahData.data.number === 1}
375 | >
376 |
377 |
378 | >
379 | )}
380 |
381 |
382 |
383 |
384 |
385 |
386 | {surahData && surahData.data.number !== 114 && (
387 |
navigateToSurah(surahData.data.number + 1)}
390 | >
391 |
392 |
393 | )}
394 |
395 |
396 | An-Nas
397 |
398 |
399 |
400 |
401 | );
402 | };
403 |
404 | export default SurahDetails;
405 |
--------------------------------------------------------------------------------
/app/tadarus/surah/[id]/page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import type { Metadata } from "next";
3 | import Surah from './Surah'
4 |
5 | export const metadata: Metadata = {
6 | title: "Tadarus - Baca Quran Online",
7 | description: "Awas Imsak! adalah portal yang menghadirkan jadwal sholat dan imsak dengan akurat dan mudah diakses",
8 | };
9 |
10 | const SurahPage = () => {
11 | return (
12 |
13 | )
14 | }
15 |
16 | export default SurahPage
--------------------------------------------------------------------------------
/components/audioButton/page.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from 'react';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { faPlay, faPause } from '@fortawesome/free-solid-svg-icons';
4 |
5 | interface AudioButtonProps {
6 | audioSource: string;
7 | description: string;
8 | }
9 |
10 | const AudioButtonWithSlider: React.FC = ({ audioSource, description }) => {
11 | const audioRef = useRef(null);
12 | const [isPlaying, setIsPlaying] = useState(false);
13 | const [currentTime, setCurrentTime] = useState(0);
14 | const [duration, setDuration] = useState(0);
15 |
16 | const togglePlay = () => {
17 | if (audioRef.current) {
18 | if (isPlaying) {
19 | audioRef.current.pause();
20 | } else {
21 | audioRef.current.play();
22 | }
23 | setIsPlaying(!isPlaying);
24 | }
25 | };
26 |
27 | const handleTimeUpdate = () => {
28 | if (audioRef.current) {
29 | setCurrentTime(audioRef.current.currentTime);
30 | setDuration(audioRef.current.duration);
31 | }
32 | };
33 |
34 | const handleSliderChange = (e: React.ChangeEvent) => {
35 | if (audioRef.current) {
36 | const newTime = parseInt(e.target.value);
37 | audioRef.current.currentTime = newTime;
38 | setCurrentTime(newTime);
39 | }
40 | };
41 |
42 | const handleAudioEnded = () => {
43 | if (audioRef.current) {
44 | setIsPlaying(false);
45 | setCurrentTime(0);
46 | audioRef.current.currentTime = 0;
47 | }
48 | };
49 |
50 |
51 | const formatTime = (time: number) => {
52 | const minutes = Math.floor(time / 60);
53 | const seconds = Math.floor(time % 60);
54 | return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
55 | };
56 |
57 | return (
58 | <>
59 |
65 | {description}
66 |
67 |
68 |
69 | {isPlaying ? (
70 |
71 | ) : (
72 |
73 | )}
74 |
75 |
83 |
84 | {formatTime(currentTime)} / {formatTime(duration)}
85 |
86 |
87 |
88 | >
89 | );
90 | };
91 |
92 | export default AudioButtonWithSlider;
93 |
--------------------------------------------------------------------------------
/components/audioButtonAyahs/page.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from 'react';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { faPlay, faPause } from '@fortawesome/free-solid-svg-icons';
4 |
5 | const AudioButtonAyahs: React.FC<{ audioSource: string }> = ({ audioSource }) => {
6 | const audioRef = useRef(null);
7 | const [isPlaying, setIsPlaying] = useState(false);
8 |
9 | const togglePlay = () => {
10 | if (audioRef.current) {
11 | if (isPlaying) {
12 | audioRef.current.pause();
13 | } else {
14 | audioRef.current.play();
15 | }
16 | setIsPlaying(!isPlaying);
17 | }
18 | };
19 | const handleAudioEnded = () => {
20 | setIsPlaying(false); // Set isPlaying to false when audio ends
21 | };
22 |
23 | return (
24 | <>
25 |
26 |
27 |
28 | {isPlaying ? (
29 |
30 | ) : (
31 |
32 | )}
33 |
34 |
35 | >
36 | );
37 | };
38 |
39 | export default AudioButtonAyahs;
40 |
--------------------------------------------------------------------------------
/components/geocoding/page.tsx:
--------------------------------------------------------------------------------
1 | async function getGeocodingData(lat: number, lng: number) {
2 | const apiKey = process.env.OPENCAGE_API_KEY;
3 | const response = await fetch(
4 | `https://api.opencagedata.com/geocode/v1/json?q=${lat} ${lng}&key=${apiKey}&language=id`,
5 | { cache: 'no-store' } // atau 'force-cache' jika ingin di-cache
6 | );
7 |
8 | if (!response.ok) {
9 | throw new Error('Failed to fetch geocoding data');
10 | }
11 |
12 | return response.json();
13 | }
14 |
15 | export default async function Geocoding({
16 | lat,
17 | lng
18 | }: {
19 | lat: number;
20 | lng: number;
21 | }) {
22 | const data = await getGeocodingData(lat, lng);
23 |
24 | return (
25 |
26 | {/* Tampilkan data yang sudah di fetch */}
27 |
{JSON.stringify(data.results[0].formatted, null, 2)}
28 |
29 | );
30 | }
--------------------------------------------------------------------------------
/components/hero/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ChangeEvent, useEffect, useState } from "react";
4 | import Link from "next/link";
5 | import Select from "react-select";
6 | import Textra from "react-textra";
7 | import Image from "next/image";
8 | import AOS from "aos";
9 | import "aos/dist/aos.css";
10 | import { motion } from "framer-motion";
11 | import { Toaster, toast } from "react-hot-toast";
12 | import Notification from "../notification/page";
13 | import Weather from "../weather/page";
14 | import RamadhanCountdown from "../ramadhanCountdown/page";
15 |
16 | interface Location {
17 | value: string;
18 | label: string;
19 | }
20 | interface Config {
21 | opencageApiKey: string;
22 | }
23 | export const config: Config = {
24 | opencageApiKey: process.env.NEXT_PUBLIC_OPENCAGE_API_KEY || "",
25 | };
26 |
27 | const Hero = () => {
28 | useEffect(() => {
29 | AOS.init();
30 | AOS.refresh();
31 | }, []);
32 |
33 | const [locations, setLocations] = useState([]);
34 | const [selectedLocation, setSelectedLocation] = useState(
35 | null
36 | );
37 | const [isLoading, setIsLoading] = useState(true);
38 | const [prayerSchedule, setPrayerSchedule] = useState(null);
39 | const [currentTime, setCurrentTime] = useState(new Date());
40 | const [localTimeZone, setLocalTimeZone] = useState("");
41 |
42 | // Fungsi untuk mengambil data lokasi dari API
43 | const fetchData = async () => {
44 | try {
45 | const response = await fetch(
46 | "https://api.myquran.com/v2/sholat/kota/semua"
47 | );
48 | const data = await response.json();
49 | const formattedLocations = data.data.map(
50 | (location: { id: string; lokasi: string }) => ({
51 | value: location.id,
52 | label: location.lokasi,
53 | })
54 | );
55 | setLocations(formattedLocations);
56 | return formattedLocations;
57 | } catch (error) {
58 | console.error("Error fetching data:", error);
59 | toast.error("Gagal mengambil data lokasi");
60 | return [];
61 | }
62 | };
63 |
64 | const fetchPrayerSchedule = async (idKota: string) => {
65 | const currentDate = new Date();
66 | const year = currentDate.getFullYear();
67 | const month = currentDate.getMonth() + 1;
68 | const day = currentDate.getDate();
69 |
70 | try {
71 | const url = `https://api.myquran.com/v2/sholat/jadwal/${idKota}/${year}/${month}/${day}`;
72 | const response = await fetch(url);
73 | const data = await response.json();
74 | if (data && data.data && data.data.jadwal) {
75 | setPrayerSchedule(data.data.jadwal);
76 | } else {
77 | console.error("Invalid response data:", data);
78 | }
79 | } catch (error) {
80 | console.error("Error fetching prayer schedule:", error);
81 | }
82 | };
83 |
84 | const detectAndSetLocation = async (locations: Location[]) => {
85 | if ("geolocation" in navigator) {
86 | try {
87 | const position = await new Promise(
88 | (resolve, reject) => {
89 | const options = {
90 | enableHighAccuracy: true,
91 | timeout: 10000,
92 | maximumAge: 0,
93 | };
94 |
95 | navigator.geolocation.getCurrentPosition(
96 | resolve,
97 | (error: GeolocationPositionError) => {
98 | let errorMessage = "Gagal mendeteksi lokasi: ";
99 | switch (error.code) {
100 | case error.PERMISSION_DENIED:
101 | errorMessage +=
102 | "Izin lokasi ditolak. Mohon aktifkan izin lokasi di pengaturan browser Anda.";
103 | break;
104 | case error.POSITION_UNAVAILABLE:
105 | errorMessage +=
106 | "Informasi lokasi tidak tersedia. Pastikan GPS/lokasi perangkat Anda aktif.";
107 | break;
108 | case error.TIMEOUT:
109 | errorMessage +=
110 | "Waktu permintaan lokasi habis. Silakan coba lagi.";
111 | break;
112 | default:
113 | errorMessage +=
114 | error.message ||
115 | "Terjadi kesalahan yang tidak diketahui.";
116 | }
117 | reject(new Error(errorMessage));
118 | },
119 | options
120 | );
121 | }
122 | );
123 |
124 | const normalizeString = (str: string): string => {
125 | return str
126 | .toLowerCase()
127 | .replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "")
128 | .replace(/\s+/g, " ")
129 | .replace(/kabupaten/g, "kab")
130 | .replace(/kota/g, "kota")
131 | .trim();
132 | };
133 |
134 | const { latitude, longitude } = position.coords;
135 | try {
136 | const response = await fetch(
137 | `https://api.opencagedata.com/geocode/v1/json?q=${latitude}+${longitude}&key=${process.env.NEXT_PUBLIC_OPENCAGE_API_KEY}&language=id`
138 | );
139 |
140 | if (!response.ok) {
141 | throw new Error(`HTTP error! status: ${response.status}`);
142 | }
143 |
144 | const data = await response.json();
145 | if (!data.results || data.results.length === 0) {
146 | throw new Error("Tidak ada hasil lokasi yang ditemukan");
147 | }
148 |
149 | const locationDetail = data.results[0].components;
150 | const regency = locationDetail.regency;
151 | const city = locationDetail.city;
152 | const county = locationDetail.county; // Tambahan untuk kabupaten
153 | const state = locationDetail.state; // Tambahan untuk provinsi
154 |
155 | // Fungsi untuk mencari lokasi berdasarkan beberapa kriteria
156 | const findLocation = (
157 | searchTerms: string[]
158 | ): Location | undefined => {
159 | return locations.find((loc) => {
160 | const normalizedLocationLabel = normalizeString(loc.label);
161 | return searchTerms.some((term) => {
162 | const normalizedTerm = normalizeString(term);
163 | return (
164 | normalizedLocationLabel.includes(normalizedTerm) ||
165 | normalizedTerm.includes(normalizedLocationLabel)
166 | );
167 | });
168 | });
169 | };
170 |
171 | // Urutan pencarian berdasarkan prioritas
172 | let matchedLocation: Location | undefined;
173 | let detectedLocation = "";
174 |
175 | if (regency) {
176 | matchedLocation = findLocation([regency]);
177 | detectedLocation = regency;
178 | }
179 |
180 | if (!matchedLocation && city) {
181 | matchedLocation = findLocation([city]);
182 | detectedLocation = city;
183 | }
184 |
185 | if (!matchedLocation && county) {
186 | matchedLocation = findLocation([county]);
187 | detectedLocation = county;
188 | }
189 |
190 | // Fallback ke provinsi jika tidak ada yang cocok
191 | if (!matchedLocation && state) {
192 | matchedLocation = findLocation([state]);
193 | detectedLocation = state;
194 | }
195 |
196 | if (matchedLocation) {
197 | setSelectedLocation(matchedLocation);
198 | toast.success(
199 | `Lokasi terdeteksi: ${matchedLocation.label} (${detectedLocation})`,
200 | {
201 | duration: 4000,
202 | }
203 | );
204 | } else {
205 | toast.error(
206 | "Lokasi terdeteksi tetapi tidak ada dalam daftar yang tersedia",
207 | { duration: 4000 }
208 | );
209 | setSelectedLocation(locations[50]); // Default location
210 | }
211 |
212 | // Log untuk debugging
213 | console.log("Detail Lokasi:", {
214 | regency,
215 | city,
216 | county,
217 | state,
218 | detected: detectedLocation,
219 | matched: matchedLocation?.label,
220 | });
221 | } catch (apiError) {
222 | console.error("API Error:", apiError);
223 | toast.error("Gagal mengambil detail lokasi dari server", {
224 | duration: 4000,
225 | });
226 | setSelectedLocation(locations[50]);
227 | }
228 | } catch (error) {
229 | console.error("Geolocation Error:", error);
230 | toast.error(
231 | error instanceof Error ? error.message : "Gagal mendeteksi lokasi",
232 | { duration: 4000 }
233 | );
234 | setSelectedLocation(locations[50]);
235 | }
236 | } else {
237 | toast.error("Browser tidak mendukung geolocation", { duration: 4000 });
238 | setSelectedLocation(locations[50]);
239 | }
240 | setIsLoading(false);
241 | };
242 |
243 | const handleLocationChange = (selected: Location | null) => {
244 | setSelectedLocation(selected);
245 | if (selected) {
246 | fetchPrayerSchedule(selected.value);
247 | }
248 | };
249 |
250 | useEffect(() => {
251 | const initializeLocation = async () => {
252 | const locationsList = await fetchData();
253 | await detectAndSetLocation(locationsList);
254 | };
255 |
256 | initializeLocation();
257 | }, []);
258 |
259 | useEffect(() => {
260 | if (selectedLocation) {
261 | fetchPrayerSchedule(selectedLocation.value);
262 | }
263 | }, [selectedLocation]);
264 |
265 | const formatTime = (time: number) => {
266 | return time < 10 ? `0${time}` : time.toString();
267 | };
268 |
269 | useEffect(() => {
270 | const timer = setInterval(() => {
271 | setCurrentTime(new Date());
272 | }, 1000);
273 |
274 | const offsetMinutes = -currentTime.getTimezoneOffset();
275 | const offsetHours = Math.floor(offsetMinutes / 60);
276 |
277 | let timeZone = "";
278 | if (offsetHours === 7) {
279 | timeZone = "WIB";
280 | } else if (offsetHours === 8) {
281 | timeZone = "WITA";
282 | } else if (offsetHours === 9) {
283 | timeZone = "WIT";
284 | }
285 |
286 | const formattedTime = `${formatTime(currentTime.getHours())}:${formatTime(
287 | currentTime.getMinutes()
288 | )}:${formatTime(currentTime.getSeconds())}`;
289 |
290 | const timeZoneInfo = `UTC+${offsetHours} | ${formattedTime} ${timeZone}`;
291 |
292 | setLocalTimeZone(timeZoneInfo);
293 |
294 | return () => {
295 | clearInterval(timer);
296 | };
297 | }, [currentTime]);
298 |
299 | return (
300 |
301 |
302 |
303 |
304 |
305 |
320 |
321 |
339 | Awas Imsak!
340 |
341 |
346 |
356 |
357 |
364 | Rasulullah saw bersabda:
365 |
366 |
367 | ثَلَاثُ دَعَوَاتٍ مُسْتَجَابَاتٍ ؛دَعْوَةُ الصَّائِمِ وَدَعْوَةُ
368 | الْمُسَافِرِ وَدَعْوَةُ الْمَظْلُوْمِ
369 |
370 |
371 | Artinya: Ada tiga macam doa yang mustajab, yaitu doa orang yang
372 | sedang puasa, doa musafir, dan doa orang yang teraniaya (HR
373 | Baihaqi).
374 |
375 |
385 |
386 |
392 | Awas Lupa Niat
393 |
394 |
395 |
396 |
402 | Awas Lupa Tadarus
403 |
404 |
405 |
406 |
412 | Awas Lupa Masak
413 |
414 |
415 |
416 |
426 |
427 | © {new Date().getFullYear()}{" "}
428 |
434 | {" "}
435 | Muhammad Dimas,{" "}
436 |
437 | under RIOT REVENGER exclusive agreements.
438 |
439 |
440 |
441 |
442 |
443 |
449 | {/* Left side - Timezone */}
450 |
451 |
452 | {localTimeZone}
453 |
454 |
455 |
456 | {/* Right side - Weather
457 |
*/}
460 |
461 |
467 | Pilih Wilayah:
468 |
469 |
475 |
486 |
487 | {prayerSchedule && (
488 |
489 |
490 |
496 | Wilayah:
497 | {selectedLocation && selectedLocation.label}
498 |
499 |
505 | Hari / Tanggal:
506 | {prayerSchedule.tanggal}
507 |
508 |
509 |
510 |
516 | Imsak:
517 | {prayerSchedule.imsak}
518 |
519 |
520 |
526 | Subuh:
527 | {prayerSchedule.subuh}
528 |
529 |
535 | Terbit:
536 | {prayerSchedule.terbit}
537 |
538 |
544 | Dhuha:
545 | {prayerSchedule.dhuha}
546 |
547 |
553 | Dzuhur:
554 | {prayerSchedule.dzuhur}
555 |
556 |
562 | Ashar:
563 | {prayerSchedule.ashar}
564 |
565 |
571 | Maghrib:
572 | {prayerSchedule.maghrib}
573 |
574 |
580 | Isya:
581 | {prayerSchedule.isya}
582 |
583 |
584 |
585 | )}
586 |
587 |
588 |
589 | );
590 | };
591 |
592 | export default Hero;
593 |
--------------------------------------------------------------------------------
/components/notification/page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { motion, AnimatePresence } from 'framer-motion';
3 |
4 | interface NotificationProps {
5 | message: string;
6 | onClose: () => void;
7 | }
8 |
9 | const Notification: React.FC = ({ message, onClose }) => {
10 | return (
11 |
12 |
18 |
19 |
23 | X
24 |
25 |
{message}
26 |
27 |
28 |
29 | );
30 | }
31 |
32 |
33 | export default Notification
--------------------------------------------------------------------------------
/components/particle/page.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo, useState } from "react";
2 | import Particles, { initParticlesEngine } from "@tsparticles/react";
3 | import {
4 | type Container,
5 | type ISourceOptions,
6 | MoveDirection,
7 | OutMode,
8 | } from "@tsparticles/engine";
9 | // import { loadAll } from "@tsparticles/all"; // if you are going to use `loadAll`, install the "@tsparticles/all" package too.
10 | // import { loadFull } from "tsparticles"; // if you are going to use `loadFull`, install the "tsparticles" package too.
11 | import { loadSlim } from "@tsparticles/slim"; // if you are going to use `loadSlim`, install the "@tsparticles/slim" package too.
12 | // import { loadBasic } from "@tsparticles/basic"; // if you are going to use `loadBasic`, install the "@tsparticles/basic" package too.
13 |
14 | const App = () => {
15 | const [init, setInit] = useState(false);
16 |
17 | // this should be run only once per application lifetime
18 | useEffect(() => {
19 | initParticlesEngine(async (engine) => {
20 | // you can initiate the tsParticles instance (engine) here, adding custom shapes or presets
21 | // this loads the tsparticles package bundle, it's the easiest method for getting everything ready
22 | // starting from v2 you can add only the features you need reducing the bundle size
23 | //await loadAll(engine);
24 | //await loadFull(engine);
25 | await loadSlim(engine);
26 | //await loadBasic(engine);
27 | }).then(() => {
28 | setInit(true);
29 | });
30 | }, []);
31 |
32 | const particlesLoaded = async (container?: Container): Promise => {
33 | console.log(container);
34 | };
35 |
36 | const options: ISourceOptions = useMemo(
37 | () => ({
38 | background: {
39 | color: {
40 | value: "#0d47a1",
41 | },
42 | },
43 | fpsLimit: 120,
44 | interactivity: {
45 | events: {
46 | onClick: {
47 | enable: true,
48 | mode: "push",
49 | },
50 | onHover: {
51 | enable: true,
52 | mode: "repulse",
53 | },
54 | },
55 | modes: {
56 | push: {
57 | quantity: 4,
58 | },
59 | repulse: {
60 | distance: 200,
61 | duration: 0.4,
62 | },
63 | },
64 | },
65 | particles: {
66 | color: {
67 | value: "#ffffff",
68 | },
69 | links: {
70 | color: "#ffffff",
71 | distance: 150,
72 | enable: true,
73 | opacity: 0.5,
74 | width: 1,
75 | },
76 | move: {
77 | direction: MoveDirection.none,
78 | enable: true,
79 | outModes: {
80 | default: OutMode.out,
81 | },
82 | random: false,
83 | speed: 6,
84 | straight: false,
85 | },
86 | number: {
87 | density: {
88 | enable: true,
89 | },
90 | value: 80,
91 | },
92 | opacity: {
93 | value: 0.5,
94 | },
95 | shape: {
96 | type: "circle",
97 | },
98 | size: {
99 | value: { min: 1, max: 5 },
100 | },
101 | },
102 | detectRetina: true,
103 | }),
104 | [],
105 | );
106 |
107 | if (init) {
108 | return (
109 |
114 | );
115 | }
116 |
117 | return <>>;
118 | };
--------------------------------------------------------------------------------
/components/quran/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import React, { Suspense, useEffect, useState } from 'react';
4 | import { useDispatch } from 'react-redux';
5 | import { fetchSurahDataSuccess } from '../../redux/surahActions';
6 | import Link from 'next/link'
7 | import dynamic from 'next/dynamic'
8 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
9 | import { faSearch } from '@fortawesome/free-solid-svg-icons';
10 |
11 | // Dynamically import ReactPaginate with no SSR
12 | const ReactPaginate = dynamic(() => import('react-paginate'), {
13 | ssr: false
14 | });
15 |
16 | interface SurahData {
17 | number: number;
18 | ayahCount: number;
19 | sequence: number;
20 | asma: {
21 | ar: { short: string; long: string };
22 | en: { short: string; long: string };
23 | id: { short: string; long: string };
24 | translation: { en: string; id: string };
25 | };
26 | }
27 |
28 | // Loading component
29 | const LoadingSpinner = () => (
30 |
31 |
32 |
Loading...
33 |
34 | );
35 |
36 | // Main content component
37 | const QuranContent = ({
38 | loadedSurahs,
39 | searchResults,
40 | currentSurahs,
41 | loading,
42 | loadingProgress,
43 | searchTerm,
44 | handleSearch,
45 | handlePageChange,
46 | pageCount,
47 | surahsPerPage,
48 | pageRangeDisplayed,
49 | marginPagesDisplayed
50 | }: any) => {
51 | return (
52 |
53 |
67 |
68 | {searchResults.length === 0 ? (
69 |
70 |
Yah... surahnya ga ketemu, coba cari yang lain dehh
71 |
72 | ) : (
73 |
74 | {currentSurahs.map((surahData: SurahData) => (
75 |
76 |
77 |
78 |
79 | {surahData.asma.id.short}
80 |
81 |
{surahData.asma.ar.short}
82 |
83 |
84 | {surahData.asma.translation.id}
85 |
86 |
87 |
88 | ))}
89 |
90 | )}
91 |
92 | {loadedSurahs.length > surahsPerPage && (
93 |
94 | ←}
102 | nextLabel={→ }
103 | breakLabel={"..."}
104 | pageLinkClassName={"page-link"}
105 | previousLinkClassName={"page-link"}
106 | nextLinkClassName={"page-link"}
107 | disabledClassName={"disabled"}
108 | />
109 |
110 | )}
111 |
112 | );
113 | };
114 |
115 | const Quran = () => {
116 | const dispatch = useDispatch();
117 | const [mounted, setMounted] = useState(false);
118 | const [surahsPerPage] = useState(12);
119 | const [currentPage, setCurrentPage] = useState(0);
120 | const [loading, setLoading] = useState(false);
121 | const [loadedSurahs, setLoadedSurahs] = useState([]);
122 | const [loadingProgress, setLoadingProgress] = useState(0);
123 | const [pageRangeDisplayed, setPageRangeDisplayed] = useState(5);
124 | const [marginPagesDisplayed, setMarginPagesDisplayed] = useState(2);
125 | const [searchTerm, setSearchTerm] = useState('');
126 | const [searchResults, setSearchResults] = useState([]);
127 |
128 | useEffect(() => {
129 | setMounted(true);
130 | }, []);
131 |
132 | useEffect(() => {
133 | const fetchData = async () => {
134 | if (!mounted) return;
135 |
136 | setLoading(true);
137 | try {
138 | const cachedData = localStorage.getItem('surahDataList');
139 | if (cachedData) {
140 | const parsedData = JSON.parse(cachedData);
141 | dispatch(fetchSurahDataSuccess(parsedData));
142 | setLoadedSurahs(parsedData);
143 | setSearchResults(parsedData);
144 | } else {
145 | const newSurahs: SurahData[] = [];
146 | for (let i = 1; i <= 114; i++) {
147 | try {
148 | const response = await fetch(`https://quran-endpoint.vercel.app/quran/${i}`);
149 | const data = await response.json();
150 | newSurahs.push(data.data);
151 | setLoadingProgress(Math.round((i / 114) * 100));
152 | } catch (error) {
153 | console.error(`Error fetching surah ${i}:`, error);
154 | }
155 | }
156 |
157 | setLoadedSurahs(newSurahs);
158 | setSearchResults(newSurahs);
159 | localStorage.setItem('surahDataList', JSON.stringify(newSurahs));
160 | dispatch(fetchSurahDataSuccess(newSurahs));
161 | }
162 | } catch (error) {
163 | console.error('Error fetching data:', error);
164 | } finally {
165 | setLoading(false);
166 | }
167 | };
168 |
169 | fetchData();
170 | }, [dispatch, mounted]);
171 |
172 | useEffect(() => {
173 | if (!mounted) return;
174 |
175 | const handleResize = () => {
176 | setPageRangeDisplayed(window.innerWidth <= 640 ? 2 : 5);
177 | setMarginPagesDisplayed(window.innerWidth <= 640 ? 0 : 2);
178 | };
179 |
180 | window.addEventListener('resize', handleResize);
181 | handleResize();
182 |
183 | return () => window.removeEventListener('resize', handleResize);
184 | }, [mounted]);
185 |
186 | useEffect(() => {
187 | if (!mounted) return;
188 |
189 | const results = loadedSurahs.filter((surahData: SurahData) =>
190 | surahData.asma.id.short.toLowerCase().includes(searchTerm.toLowerCase())
191 | );
192 | setSearchResults(results);
193 | }, [searchTerm, loadedSurahs, mounted]);
194 |
195 | const handlePageChange = (selectedPage: { selected: number }) => {
196 | setCurrentPage(selectedPage.selected);
197 | };
198 |
199 | const handleSearch = (event: React.ChangeEvent) => {
200 | setSearchTerm(event.target.value);
201 | };
202 |
203 | const indexOfLastSurah = (currentPage + 1) * surahsPerPage;
204 | const indexOfFirstSurah = indexOfLastSurah - surahsPerPage;
205 | const currentSurahs = searchResults.slice(indexOfFirstSurah, indexOfLastSurah);
206 | const pageCount = Math.ceil(searchResults.length / surahsPerPage);
207 |
208 | if (!mounted) {
209 | return ;
210 | }
211 |
212 | return (
213 |
214 |
}>
215 | {loading && loadedSurahs.length === 0 ? (
216 |
217 |
218 |
219 | Loading... {loadingProgress}%
220 |
221 |
222 | ) : (
223 |
237 | )}
238 |
239 |
240 | );
241 | };
242 |
243 | export default Quran;
--------------------------------------------------------------------------------
/components/ramadhanCountdown/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import React, { useState, useEffect } from 'react';
4 | import AOS from "aos";
5 | import "aos/dist/aos.css";
6 |
7 | interface TimeLeft {
8 | days: number;
9 | hours: number;
10 | minutes: number;
11 | seconds: number;
12 | }
13 |
14 | const RamadhanCountdown = () => {
15 | const [timeLeft, setTimeLeft] = useState({
16 | days: 0,
17 | hours: 0,
18 | minutes: 0,
19 | seconds: 0
20 | });
21 |
22 | // Inisialisasi isVisible dari localStorage atau default ke true
23 | const [isVisible, setIsVisible] = useState(() => {
24 | if (typeof window !== 'undefined') {
25 | const saved = localStorage.getItem('countdownVisible');
26 | return saved !== null ? JSON.parse(saved) : true;
27 | }
28 | return true;
29 | });
30 |
31 | useEffect(() => {
32 | AOS.init();
33 | AOS.refresh();
34 | }, []);
35 |
36 | // Effect untuk menangani setTimeout initial display
37 | useEffect(() => {
38 | const timer = setTimeout(() => {
39 | setIsVisible(true);
40 | }, 3000);
41 |
42 | return () => clearTimeout(timer);
43 | }, []);
44 |
45 | // Effect untuk menyimpan state visibility ke localStorage
46 | useEffect(() => {
47 | if (typeof window !== 'undefined') {
48 | localStorage.setItem('countdownVisible', JSON.stringify(isVisible));
49 | }
50 | }, [isVisible]);
51 |
52 | useEffect(() => {
53 | const calculateTimeLeft = () => {
54 | const ramadhanDate = new Date('2025-03-01T00:00:00').getTime();
55 | const now = new Date().getTime();
56 | const difference = ramadhanDate - now;
57 |
58 | if (difference > 0) {
59 | setTimeLeft({
60 | days: Math.floor(difference / (1000 * 60 * 60 * 24)),
61 | hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
62 | minutes: Math.floor((difference / 1000 / 60) % 60),
63 | seconds: Math.floor((difference / 1000) % 60)
64 | });
65 | }
66 | };
67 |
68 | const timer = setInterval(calculateTimeLeft, 1000);
69 | return () => clearInterval(timer);
70 | }, []);
71 |
72 | const handleClose = () => {
73 | setIsVisible(false);
74 | };
75 |
76 | if (!isVisible) return null;
77 |
78 | return (
79 |
80 |
84 | ×
85 |
86 |
87 |
88 |
Ramadhan 2025 / 1446 H
89 |
90 | 1 Maret 2025
91 |
92 |
93 |
94 |
{timeLeft.days}
95 |
Hari
96 |
97 |
98 |
{timeLeft.hours}
99 |
Jam
100 |
101 |
102 |
{timeLeft.minutes}
103 |
Menit
104 |
105 |
106 |
{timeLeft.seconds}
107 |
Detik
108 |
109 |
110 |
111 |
112 | "What makes your sorry different from all your other sorrys before?"
113 |
114 |
115 | Awas Imsak! © {new Date().getFullYear()}{" "}
116 |
117 |
118 |
119 | );
120 | };
121 |
122 | export default RamadhanCountdown;
--------------------------------------------------------------------------------
/components/star/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import React, { useEffect } from 'react';
4 |
5 | interface StarryBackgroundProps {
6 | numberOfStars?: number;
7 | children?: React.ReactNode;
8 | }
9 |
10 | const StarryBackground: React.FC = ({
11 | numberOfStars = 100,
12 | children
13 | }) => {
14 | useEffect(() => {
15 | const createStars = () => {
16 | const container = document.getElementById('starsContainer');
17 | if (!container) return;
18 |
19 | // Clear existing stars
20 | container.innerHTML = '';
21 |
22 | for (let i = 0; i < numberOfStars; i++) {
23 | const star = document.createElement('div');
24 | star.className = 'star';
25 |
26 | const left = Math.random() * 100;
27 | const top = Math.random() * 100;
28 | const duration = 2 + Math.random() * 3;
29 | const delay = Math.random() * 3;
30 |
31 | star.style.cssText = `
32 | left: ${left}%;
33 | top: ${top}%;
34 | --duration: ${duration}s;
35 | --delay: ${delay}s;
36 | `;
37 |
38 | container.appendChild(star);
39 | }
40 | };
41 |
42 | createStars();
43 |
44 | // Cleanup function
45 | return () => {
46 | const container = document.getElementById('starsContainer');
47 | if (container) {
48 | container.innerHTML = '';
49 | }
50 | };
51 | }, [numberOfStars]);
52 |
53 | return (
54 |
55 |
56 | {children}
57 |
58 | );
59 | };
60 |
61 | export default StarryBackground;
--------------------------------------------------------------------------------
/components/surahDetail/page.tsx:
--------------------------------------------------------------------------------
1 | // components/SurahDetail.tsx
2 |
3 | // Definisikan struktur data ayah
4 | interface Ayah {
5 | number: {
6 | inquran: number;
7 | insurah: number;
8 | };
9 | juz: number;
10 | manzil: number;
11 | page: number;
12 | ruku: number;
13 | hizbQuarter: number;
14 | sajda: {
15 | recommended: boolean;
16 | obligatory: boolean;
17 | };
18 | text: {
19 | ar: string;
20 | read: string;
21 | };
22 | translation: {
23 | en: string;
24 | id: string;
25 | };
26 | tafsir: {
27 | id: string;
28 | en: string | null;
29 | };
30 | audio: {
31 | url: string;
32 | };
33 | }
34 |
35 | // Definisikan struktur data surah
36 | interface SurahData {
37 | number: number;
38 | ayahCount: number;
39 | sequence: number;
40 | asma: {
41 | ar: {
42 | short: string;
43 | long: string;
44 | };
45 | en: {
46 | short: string;
47 | long: string;
48 | };
49 | id: {
50 | short: string;
51 | long: string;
52 | };
53 | translation: {
54 | en: string;
55 | id: string;
56 | };
57 | };
58 | preBismillah: any;
59 | type: {
60 | ar: string;
61 | id: string;
62 | en: string;
63 | };
64 | tafsir: {
65 | id: string;
66 | en: string | null;
67 | };
68 | recitation: {
69 | full: string;
70 | };
71 | ayahs: Ayah[];
72 | }
73 |
74 | // Komponen SurahDetail
75 | const SurahDetail: React.FC<{ surahData: SurahData }> = ({ surahData }) => {
76 | console.log(surahData.number);
77 | return (
78 |
79 |
{surahData.ayahCount}
80 |
81 | );
82 | };
83 |
84 | export default SurahDetail;
85 |
--------------------------------------------------------------------------------
/components/weather/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import React, { useEffect, useState } from 'react';
4 |
5 | interface WeatherData {
6 | weather: Array<{
7 | main: string;
8 | description: string;
9 | }>;
10 | main: {
11 | temp: number;
12 | };
13 | }
14 |
15 | interface GeoLocation {
16 | results: Array<{
17 | formatted: string;
18 | components: {
19 | city?: string;
20 | state?: string;
21 | country?: string;
22 | };
23 | geometry: {
24 | lat: number;
25 | lng: number;
26 | };
27 | }>;
28 | }
29 |
30 | interface Location {
31 | // Tambahkan properti yang sesuai dengan tipe Location Anda
32 | }
33 |
34 | const Weather = () => {
35 | const [weatherData, setWeatherData] = useState(null);
36 | const [locationData, setLocationData] = useState(null);
37 | const [loading, setLoading] = useState(true);
38 | const [error, setError] = useState('');
39 |
40 | const detectAndSetLocation = async (locations: Location[]) => {
41 | console.log('Memulai deteksi lokasi...');
42 |
43 | if (!("geolocation" in navigator)) {
44 | setError('Geolocation tidak didukung oleh browser ini');
45 | setLoading(false);
46 | return;
47 | }
48 |
49 | try {
50 | const position = await new Promise((resolve, reject) => {
51 | console.log('Meminta izin lokasi...');
52 |
53 | const options = {
54 | enableHighAccuracy: true,
55 | timeout: 10000,
56 | maximumAge: 0
57 | };
58 |
59 | navigator.geolocation.getCurrentPosition(
60 | (pos) => {
61 | console.log('Lokasi berhasil didapatkan');
62 | resolve(pos);
63 | },
64 | (error: GeolocationPositionError) => {
65 | console.log('Error saat mendapatkan lokasi:', error);
66 | let errorMessage = "Gagal mendeteksi lokasi: ";
67 | switch (error.code) {
68 | case error.PERMISSION_DENIED:
69 | errorMessage += "Izin lokasi ditolak. Mohon aktifkan izin lokasi di pengaturan browser Anda.";
70 | break;
71 | case error.POSITION_UNAVAILABLE:
72 | errorMessage += "Informasi lokasi tidak tersedia. Pastikan GPS/lokasi perangkat Anda aktif.";
73 | break;
74 | case error.TIMEOUT:
75 | errorMessage += "Waktu permintaan lokasi habis. Silakan coba lagi.";
76 | break;
77 | default:
78 | errorMessage += error.message || "Terjadi kesalahan yang tidak diketahui.";
79 | }
80 | reject(new Error(errorMessage));
81 | },
82 | options
83 | );
84 | });
85 |
86 | const { latitude, longitude } = position.coords;
87 | console.log('Koordinat:', latitude, longitude);
88 |
89 | console.log('Mengambil data lokasi dari OpenCage...');
90 | const locationResponse = await fetch(
91 | `https://api.opencagedata.com/geocode/v1/json?q=${latitude}+${longitude}&key=${process.env.NEXT_PUBLIC_OPENCAGE_API_KEY}&language=id`
92 | );
93 |
94 | if (!locationResponse.ok) {
95 | throw new Error('Gagal mengambil data lokasi dari OpenCage');
96 | }
97 |
98 | const locationData: GeoLocation = await locationResponse.json();
99 | console.log('Data lokasi:', locationData);
100 | setLocationData(locationData);
101 |
102 | console.log('Mengambil data cuaca...');
103 | const weatherResponse = await fetch(
104 | `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${process.env.NEXT_PUBLIC_WEATHER_API_KEY}&units=metric`
105 | );
106 |
107 | if (!weatherResponse.ok) {
108 | throw new Error('Gagal mengambil data cuaca');
109 | }
110 |
111 | const weatherData: WeatherData = await weatherResponse.json();
112 | console.log('Data cuaca:', weatherData);
113 | setWeatherData(weatherData);
114 |
115 | } catch (err) {
116 | console.error('Error:', err);
117 | if (err instanceof Error) {
118 | setError(err.message);
119 | } else {
120 | setError('Terjadi kesalahan yang tidak diketahui');
121 | }
122 | } finally {
123 | setLoading(false);
124 | }
125 | };
126 |
127 | useEffect(() => {
128 | detectAndSetLocation([]);
129 | }, []);
130 |
131 | if (loading) {
132 | return (
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
Mendeteksi lokasi dan memuat data cuaca...
141 |
142 |
143 |
144 | );
145 | }
146 |
147 | if (error) {
148 | return (
149 |
150 |
151 |
{error}
152 |
{
154 | setError('');
155 | setLoading(true);
156 | detectAndSetLocation([]);
157 | }}
158 | className="mt-2 px-4 py-2 bg-red-100 hover:bg-red-200 text-red-700 rounded-md text-sm"
159 | >
160 | Coba Lagi
161 |
162 |
163 |
164 | );
165 | }
166 |
167 | return (
168 |
169 | {weatherData && locationData && locationData.results.length > 0 && (
170 |
171 |
172 |
Informasi Cuaca
173 |
174 | Lokasi: {locationData.results[0].formatted}
175 |
176 |
177 |
178 |
179 |
180 |
{weatherData.main.temp.toFixed(1)}°C
181 |
{weatherData.weather[0].main}
182 |
{weatherData.weather[0].description}
183 |
184 |
185 |
186 | )}
187 |
188 | );
189 | };
190 |
191 | export default Weather;
--------------------------------------------------------------------------------
/fonts/AmazingRamadhan.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/fonts/AmazingRamadhan.otf
--------------------------------------------------------------------------------
/fonts/Grako-Demo-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/fonts/Grako-Demo-Regular.otf
--------------------------------------------------------------------------------
/fonts/Kahlil.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/fonts/Kahlil.otf
--------------------------------------------------------------------------------
/fonts/OpenSans-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/fonts/OpenSans-Bold.otf
--------------------------------------------------------------------------------
/fonts/QiyamuRamadhan.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/fonts/QiyamuRamadhan.otf
--------------------------------------------------------------------------------
/fonts/RamadhanAmazing-jEnDv.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/fonts/RamadhanAmazing-jEnDv.ttf
--------------------------------------------------------------------------------
/fonts/SanshiroDemo-Black.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/fonts/SanshiroDemo-Black.otf
--------------------------------------------------------------------------------
/fonts/coolvetica rg.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/fonts/coolvetica rg.otf
--------------------------------------------------------------------------------
/fonts/monasgrotesk-bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/fonts/monasgrotesk-bold.otf
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | // next.config.mjs
3 | const nextConfig = {
4 | images: {
5 | domains: ['www.masakapahariini.com'],
6 | },
7 | };
8 |
9 | export default nextConfig;
10 |
11 |
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "awas-imsak",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@fortawesome/fontawesome-svg-core": "^6.5.1",
13 | "@fortawesome/free-solid-svg-icons": "^6.5.1",
14 | "@fortawesome/react-fontawesome": "^0.2.0",
15 | "@nextui-org/react": "^2.2.9",
16 | "@reduxjs/toolkit": "^2.1.0",
17 | "@tsparticles/engine": "^3.7.1",
18 | "@tsparticles/react": "^3.0.0",
19 | "aos": "^2.3.4",
20 | "bmkg-wrapper": "^2.0.0",
21 | "framer-motion": "^11.0.3",
22 | "next": "14.1.0",
23 | "react": "^18",
24 | "react-dom": "^18",
25 | "react-hot-toast": "^2.4.1",
26 | "react-paginate": "^8.2.0",
27 | "react-redux": "^9.1.0",
28 | "react-select": "^5.8.0",
29 | "react-textra": "^0.2.0",
30 | "react-tsparticles": "^2.12.2",
31 | "redux": "^5.0.1",
32 | "redux-thunk": "^3.1.0",
33 | "slick-carousel": "^1.8.1",
34 | "tsparticles": "^3.7.1"
35 | },
36 | "devDependencies": {
37 | "@types/aos": "^3.0.7",
38 | "@types/node": "^20",
39 | "@types/react": "^18",
40 | "@types/react-dom": "^18",
41 | "@types/react-slick": "^0.23.13",
42 | "autoprefixer": "^10.0.1",
43 | "eslint": "^8",
44 | "eslint-config-next": "14.1.0",
45 | "postcss": "^8",
46 | "tailwindcss": "^3.3.0",
47 | "typescript": "^5"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/brand.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/public/brand.ico
--------------------------------------------------------------------------------
/public/img/bunderan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/public/img/bunderan.png
--------------------------------------------------------------------------------
/public/img/bxs-home.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/img/gapura.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/public/img/gapura.png
--------------------------------------------------------------------------------
/public/img/gapura2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/public/img/gapura2.png
--------------------------------------------------------------------------------
/public/img/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/public/img/home.png
--------------------------------------------------------------------------------
/public/img/lantern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klawcodes/awas-imsak/81c4396c1498c86331b66efb93dbfa3da4f4cd16/public/img/lantern.png
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/redux/rootReducer.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import surahReducer from './surahReducer';
3 |
4 | const rootReducer = combineReducers({
5 | surah: surahReducer,
6 | });
7 |
8 | export default rootReducer;
9 |
--------------------------------------------------------------------------------
/redux/store.ts:
--------------------------------------------------------------------------------
1 | // store.ts
2 | import { configureStore } from '@reduxjs/toolkit';
3 | import surahReducer from './surahReducer';
4 |
5 | const store = configureStore({
6 | reducer: {
7 | surah: surahReducer,
8 | },
9 | });
10 |
11 | export default store;
12 |
--------------------------------------------------------------------------------
/redux/surahActions.ts:
--------------------------------------------------------------------------------
1 | export const FETCH_SURAH_DATA_SUCCESS = 'FETCH_SURAH_DATA_SUCCESS';
2 |
3 | export const fetchSurahDataSuccess = (surahDataList: any) => ({
4 | type: FETCH_SURAH_DATA_SUCCESS,
5 | payload: surahDataList,
6 | });
7 |
--------------------------------------------------------------------------------
/redux/surahReducer.ts:
--------------------------------------------------------------------------------
1 | import { FETCH_SURAH_DATA_SUCCESS } from './surahActions';
2 |
3 | const initialState = {
4 | surahDataList: [],
5 | };
6 |
7 | const surahReducer = (state = initialState, action: any) => {
8 | switch (action.type) {
9 | case FETCH_SURAH_DATA_SUCCESS:
10 | return {
11 | ...state,
12 | surahDataList: action.payload,
13 | };
14 | default:
15 | return state;
16 | }
17 | };
18 |
19 | export default surahReducer;
20 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13 | "gradient-conic":
14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
15 | },
16 | },
17 | },
18 | plugins: [],
19 | };
20 | export default config;
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "target": "es2015",
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "strict": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ],
26 | "paths": {
27 | "@/*": [
28 | "./*"
29 | ]
30 | }
31 | },
32 | "include": [
33 | "**/*.tsx",
34 | "!router/**/*",
35 | ".next/types/**/*.ts"
36 | ],
37 | "exclude": [
38 | "node_modules"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/utils/config.ts:
--------------------------------------------------------------------------------
1 | // utils/config.ts
2 | export const getBaseUrl = () => {
3 | if (typeof window !== 'undefined') {
4 | // Client-side
5 | return window.location.origin;
6 | }
7 | // Server-side
8 | if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
9 | if (process.env.NEXT_PUBLIC_APP_URL) return process.env.NEXT_PUBLIC_APP_URL;
10 | return 'http://localhost:3000';
11 | };
12 | interface Config {
13 | opencageApiKey: string;
14 |
15 | }
16 |
17 |
--------------------------------------------------------------------------------