|
@ -1,4 +1,3 @@ |
|
|
// components/DuaComponent.tsx
|
|
|
|
|
|
import React, { useEffect, useRef, useState, useCallback } from "react"; |
|
|
import React, { useEffect, useRef, useState, useCallback } from "react"; |
|
|
import Image from "next/image"; |
|
|
import Image from "next/image"; |
|
|
import SettingIcon from "../../../public/assets/images/WhiteSetting.svg"; |
|
|
import SettingIcon from "../../../public/assets/images/WhiteSetting.svg"; |
|
@ -16,8 +15,6 @@ import { useFontSettingsContext } from "@/components/context/font-setting-contex |
|
|
import { useAudio } from "@/components/context/audio-conext"; |
|
|
import { useAudio } from "@/components/context/audio-conext"; |
|
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; |
|
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Define the Dua interface
|
|
|
// Define the Dua interface
|
|
|
interface Dua { |
|
|
interface Dua { |
|
|
id: number; |
|
|
id: number; |
|
@ -30,14 +27,14 @@ interface Dua { |
|
|
// Define the structure for audio synchronization data
|
|
|
// Define the structure for audio synchronization data
|
|
|
interface AudioSyncData { |
|
|
interface AudioSyncData { |
|
|
id: number; |
|
|
id: number; |
|
|
duration: [number, number][]; |
|
|
|
|
|
|
|
|
duration: [number, number][]; // Start and end time in seconds
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Define the Audio interface
|
|
|
|
|
|
|
|
|
|
|
|
// Define the API response structure
|
|
|
|
|
|
|
|
|
// Define the API response structure for Dua parts
|
|
|
interface DuaPartsResponse { |
|
|
interface DuaPartsResponse { |
|
|
results: Dua[]; |
|
|
results: Dua[]; |
|
|
|
|
|
next: string; |
|
|
|
|
|
count: number; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
|
const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
@ -46,53 +43,66 @@ const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
|
}) => { |
|
|
}) => { |
|
|
const params = useParams(); |
|
|
const params = useParams(); |
|
|
const slug = (params?.slug as string) ?? SelectedDua; |
|
|
const slug = (params?.slug as string) ?? SelectedDua; |
|
|
console.log(SelectedDua); |
|
|
|
|
|
const router = useRouter(); |
|
|
const router = useRouter(); |
|
|
const { openSetting, openAudio } = useUI(); |
|
|
const { openSetting, openAudio } = useUI(); |
|
|
const { audioRef, partRefs, getAudio, audio, setLastPart } = useAudio(); |
|
|
const { audioRef, partRefs, getAudio, audio, setLastPart } = useAudio(); |
|
|
console.log("Audio Ref:", audioRef.current); |
|
|
|
|
|
|
|
|
const [scrollPosition, setScrollPosition] = useState(0); |
|
|
|
|
|
const [fetching, setFetching] = useState(false); // Track if data is being fetched
|
|
|
|
|
|
|
|
|
// Use the shared context for font settings
|
|
|
// Use the shared context for font settings
|
|
|
const { fontSettings } = useFontSettingsContext(); |
|
|
const { fontSettings } = useFontSettingsContext(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// State hooks
|
|
|
// State hooks
|
|
|
const [duaParts, setDuaParts] = useState<Dua[]>([]); |
|
|
const [duaParts, setDuaParts] = useState<Dua[]>([]); |
|
|
const [recitingPart, setRecitingPart] = useState<Dua | null>(null); |
|
|
const [recitingPart, setRecitingPart] = useState<Dua | null>(null); |
|
|
|
|
|
const [loading, setLoading] = useState(false); |
|
|
|
|
|
|
|
|
// Audio reference
|
|
|
|
|
|
|
|
|
// Fetch Dua parts and audio
|
|
|
|
|
|
// Use useCallback to memoize fetchData so it doesn’t change between renders
|
|
|
|
|
|
const fetchData = useCallback(async (nextPage) => { |
|
|
|
|
|
if (!slug || fetching) return; // Prevent fetching if data is already being fetched
|
|
|
|
|
|
|
|
|
// Refs to track each part
|
|
|
|
|
|
|
|
|
const id = slug.split("-").pop(); |
|
|
|
|
|
if (!id) return; |
|
|
|
|
|
|
|
|
// Fetch Dua parts and audio
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
if (!slug) return; |
|
|
|
|
|
|
|
|
|
|
|
const fetchData = async () => { |
|
|
|
|
|
const id = slug.split("-").pop(); |
|
|
|
|
|
if (!id) return; |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
// Fetching dua parts
|
|
|
|
|
|
const duaResponse = await http.get<DuaPartsResponse>( |
|
|
|
|
|
`web/mafatih-duas/${id}/parts/` |
|
|
|
|
|
); |
|
|
|
|
|
setDuaParts(duaResponse.data.results); |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
console.error("Error fetching Dua parts:", error); |
|
|
|
|
|
// Optionally, set an error state here
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
setFetching(true); // Set fetching to true when data starts fetching
|
|
|
|
|
|
|
|
|
getAudio(id); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// Reset the duaParts state when the slug changes
|
|
|
|
|
|
const offset = nextPage ? duaParts.length : 0; |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
setLoading(true); |
|
|
|
|
|
|
|
|
|
|
|
// Fetch the data
|
|
|
|
|
|
const duaResponse = await http.get<DuaPartsResponse>( |
|
|
|
|
|
`web/mafatih-duas/${id}/parts/?offset=${offset}` |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// Append the new results to the existing duaParts
|
|
|
|
|
|
setDuaParts((prev) => [...prev, ...duaResponse.data.results]); |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
console.error("Error fetching Dua parts:", error); |
|
|
|
|
|
} finally { |
|
|
|
|
|
setLoading(false); |
|
|
|
|
|
setFetching(false); // Reset fetching state after fetching is done
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
getAudio(id); |
|
|
|
|
|
}, [slug, fetching, duaParts, getAudio]); // Dependencies for fetchData
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Use the memoized fetchData in the effect hook
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
setDuaParts([]); // Reset Dua parts when slug changes
|
|
|
|
|
|
fetchData(false); |
|
|
|
|
|
}, [slug]); // Only slug changes should trigger this effect
|
|
|
|
|
|
|
|
|
fetchData(); |
|
|
|
|
|
}, [slug]); |
|
|
|
|
|
|
|
|
|
|
|
// Play audio from a specific part
|
|
|
// Play audio from a specific part
|
|
|
const playAudio = useCallback( |
|
|
const playAudio = useCallback( |
|
|
(part: Dua) => { |
|
|
(part: Dua) => { |
|
|
if (!Object.keys(audio).length) return; |
|
|
if (!Object.keys(audio).length) return; |
|
|
console.log(part); |
|
|
|
|
|
|
|
|
|
|
|
const selectedAudio = audio.audio_sync_data.find( |
|
|
const selectedAudio = audio.audio_sync_data.find( |
|
|
(item) => item.id === part.id |
|
|
(item) => item.id === part.id |
|
@ -105,13 +115,15 @@ const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
|
audioRef.current.play().catch((error) => { |
|
|
audioRef.current.play().catch((error) => { |
|
|
console.error("Error playing audio:", error); |
|
|
console.error("Error playing audio:", error); |
|
|
}); |
|
|
}); |
|
|
|
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
openAudio(); |
|
|
|
|
|
audioRef.current.play().catch((error) => { |
|
|
|
|
|
console.error("Error playing audio:", error); |
|
|
|
|
|
}); |
|
|
}, |
|
|
}, |
|
|
[audio] |
|
|
[audio] |
|
|
); |
|
|
); |
|
|
console.log(audio); |
|
|
|
|
|
|
|
|
|
|
|
console.log(fontSettings.arabicRange); |
|
|
|
|
|
|
|
|
|
|
|
// Handle audio end to scroll to the next part
|
|
|
// Handle audio end to scroll to the next part
|
|
|
const handleAudioEnd = useCallback(() => { |
|
|
const handleAudioEnd = useCallback(() => { |
|
@ -145,6 +157,7 @@ const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
|
setRecitingPart(currentRecitingPart); |
|
|
setRecitingPart(currentRecitingPart); |
|
|
}, [audio, duaParts]); |
|
|
}, [audio, duaParts]); |
|
|
|
|
|
|
|
|
|
|
|
// Process the slug into a more readable format
|
|
|
function processSlug(slug: string): string { |
|
|
function processSlug(slug: string): string { |
|
|
if (!slug) return ""; |
|
|
if (!slug) return ""; |
|
|
|
|
|
|
|
@ -205,14 +218,24 @@ const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
|
} |
|
|
} |
|
|
}, [recitingPart, duaParts]); |
|
|
}, [recitingPart, duaParts]); |
|
|
|
|
|
|
|
|
|
|
|
const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => { |
|
|
|
|
|
const target = e.currentTarget; |
|
|
|
|
|
const isBottom = target.scrollHeight - target.scrollTop === target.clientHeight; |
|
|
|
|
|
|
|
|
|
|
|
if (isBottom && !fetching) { // Only fetch when not already fetching
|
|
|
|
|
|
fetchData(true); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
setScrollPosition(target.scrollTop); // Update scroll position
|
|
|
|
|
|
}, [fetching]); |
|
|
|
|
|
|
|
|
if (!slug) { |
|
|
if (!slug) { |
|
|
return null; // Handling the case where slug is not available
|
|
|
return null; // Handling the case where slug is not available
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
console.log(audioRef.current); |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<div |
|
|
<div |
|
|
|
|
|
onScroll={handleScroll} // Add the onScroll event listener here
|
|
|
className={`rounded-3xl overflow-y-auto flex-grow h-[calc(100vh-130px)] lg:bg-[#F5F5F5] lg:p-6 lg:rounded-3xl ${ |
|
|
className={`rounded-3xl overflow-y-auto flex-grow h-[calc(100vh-130px)] lg:bg-[#F5F5F5] lg:p-6 lg:rounded-3xl ${ |
|
|
!slug && "hidden lg:flex" |
|
|
!slug && "hidden lg:flex" |
|
|
}`}
|
|
|
}`}
|
|
@ -255,9 +278,7 @@ const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
|
<div className="p-3 bg-white rounded-3xl"> |
|
|
<div className="p-3 bg-white rounded-3xl"> |
|
|
{item.text && ( |
|
|
{item.text && ( |
|
|
<div |
|
|
<div |
|
|
className={`mb-4 text-right ${ |
|
|
|
|
|
!fontSettings.arabic && "hidden" |
|
|
|
|
|
}`}
|
|
|
|
|
|
|
|
|
className={`mb-4 text-right ${!fontSettings.arabic && "hidden"}`} |
|
|
style={{ |
|
|
style={{ |
|
|
fontSize: `${25 * (fontSettings.arabicRange / 100)}px`, |
|
|
fontSize: `${25 * (fontSettings.arabicRange / 100)}px`, |
|
|
}} |
|
|
}} |
|
@ -267,13 +288,9 @@ const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
|
)} |
|
|
)} |
|
|
{item.local_alpha && ( |
|
|
{item.local_alpha && ( |
|
|
<p |
|
|
<p |
|
|
className={`text-sm font-normal mb-4 ${ |
|
|
|
|
|
!fontSettings.transliteration && "hidden" |
|
|
|
|
|
}`}
|
|
|
|
|
|
|
|
|
className={`text-sm font-normal mb-4 ${!fontSettings.transliteration && "hidden"}`} |
|
|
style={{ |
|
|
style={{ |
|
|
fontSize: `${ |
|
|
|
|
|
14 * (fontSettings.transliterationRange / 100) |
|
|
|
|
|
}px`,
|
|
|
|
|
|
|
|
|
fontSize: `${14 * (fontSettings.transliterationRange / 100)}px`, |
|
|
}} |
|
|
}} |
|
|
> |
|
|
> |
|
|
{item.local_alpha} |
|
|
{item.local_alpha} |
|
@ -281,9 +298,7 @@ const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
|
)} |
|
|
)} |
|
|
{item.translation && ( |
|
|
{item.translation && ( |
|
|
<p |
|
|
<p |
|
|
className={`text-sm font-normal border-t pt-4 ${ |
|
|
|
|
|
!fontSettings.translation && "hidden" |
|
|
|
|
|
}`}
|
|
|
|
|
|
|
|
|
className={`text-sm font-normal border-t pt-4 ${!fontSettings.translation && "hidden"}`} |
|
|
style={{ |
|
|
style={{ |
|
|
fontSize: `${14 * (fontSettings.translationRange / 100)}px`, |
|
|
fontSize: `${14 * (fontSettings.translationRange / 100)}px`, |
|
|
}} |
|
|
}} |
|
@ -297,7 +312,6 @@ const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
|
</p> |
|
|
</p> |
|
|
)} |
|
|
)} |
|
|
|
|
|
|
|
|
{/* Play button to start audio from specific time */} |
|
|
|
|
|
{audio && !!Object.keys(audio)?.length && ( |
|
|
{audio && !!Object.keys(audio)?.length && ( |
|
|
<button |
|
|
<button |
|
|
onClick={() => playAudio(item)} |
|
|
onClick={() => playAudio(item)} |
|
@ -324,10 +338,11 @@ const DuaComponent: React.FC<DuaComponentProps> = ({ |
|
|
); |
|
|
); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Fetching server-side translations for multi-language support
|
|
|
export async function getServerSideProps({ locale }: { locale: string }) { |
|
|
export async function getServerSideProps({ locale }: { locale: string }) { |
|
|
return { |
|
|
return { |
|
|
props: { |
|
|
props: { |
|
|
...(await serverSideTranslations(locale, ['common'])), // Load translations for 'common' namespace
|
|
|
|
|
|
|
|
|
...(await serverSideTranslations(locale, ["common"])), // Load translations for 'common' namespace
|
|
|
}, |
|
|
}, |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|