Browse Source
feat: add logo component, update mobile header, implement sticky components and audio controls
master
feat: add logo component, update mobile header, implement sticky components and audio controls
master
sina_sajjadi
4 weeks ago
19 changed files with 673 additions and 134 deletions
-
3next.config.ts
-
3public/assets/images/Ellipse 441.svg
-
4public/assets/images/GraySetting.svg
-
139src/components/context/audio-conext.tsx
-
34src/components/context/ui.context.tsx
-
16src/components/layout/header.tsx
-
9src/components/layout/mobile-header.tsx
-
108src/components/layout/mobile-navigation.tsx
-
5src/components/mobile-header/hamburger.tsx
-
27src/components/mobile-header/mobile-setting.tsx
-
5src/components/modals/modal-manager.tsx
-
119src/components/modals/reciters.tsx
-
46src/components/modals/setting.tsx
-
151src/components/sticky-components/audio-controls.tsx
-
6src/components/sticky-components/download-app.tsx
-
14src/components/sticky-components/sticky-components.tsx
-
12src/components/ui/logo.tsx
-
39src/pages/_app.tsx
-
67src/pages/duas/[slug].tsx
@ -0,0 +1,3 @@ |
|||
<svg width="62" height="41" viewBox="0 0 62 41" fill="none" xmlns="http://www.w3.org/2000/svg"> |
|||
<path d="M1.49459 40.5095C-0.000395198 35.871 -0.380694 30.9453 0.384556 26.1323C1.14981 21.3192 3.03905 16.7544 5.89895 12.8083C8.75885 8.8622 12.5088 5.64595 16.8446 3.42058C21.1803 1.19522 25.9797 0.0234406 30.8531 0.000346046C35.7265 -0.0227447 40.5368 1.10349 44.8934 3.28767C49.25 5.47184 53.0303 8.65241 55.9275 12.5713C58.8247 16.4901 60.7571 21.0368 61.5679 25.8424C62.3788 30.6479 62.0452 35.577 60.5942 40.2295L58.9371 39.7126C60.3068 35.3207 60.6217 30.6676 59.8563 26.1312C59.0908 21.5947 57.2666 17.3026 54.5317 13.6032C51.7967 9.90378 48.2281 6.90131 44.1154 4.83944C40.0027 2.77757 35.4619 1.7144 30.8613 1.7362C26.2608 1.758 21.7302 2.86416 17.6372 4.96491C13.5443 7.06567 10.0043 10.1018 7.3045 13.827C4.60474 17.5521 2.82129 21.8613 2.0989 26.4049C1.3765 30.9484 1.7355 35.5982 3.14677 39.977L1.49459 40.5095Z" fill="#BCBCBC"/> |
|||
</svg> |
@ -0,0 +1,4 @@ |
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> |
|||
<path d="M9.03339 11.2216C10.2621 11.2216 11.2582 10.2277 11.2582 9.00165C11.2582 7.77563 10.2621 6.78174 9.03339 6.78174C7.80467 6.78174 6.80859 7.77563 6.80859 9.00165C6.80859 10.2277 7.80467 11.2216 9.03339 11.2216Z" stroke="#8B8B8B" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/> |
|||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4076 5.77977L15.5159 4.24225C15.1437 3.60305 14.33 3.38711 13.6894 3.75853L13.2479 4.00903C12.3562 4.51866 11.2482 3.87946 11.2482 2.85156V2.34194C11.2482 1.60773 10.6508 1.01172 9.91501 1.01172H8.13172C7.3959 1.01172 6.7986 1.60773 6.7986 2.34194V2.85156C6.7986 3.87946 5.69051 4.51866 4.79886 4.00903L4.35739 3.75853C3.71679 3.38711 2.90304 3.61169 2.5308 4.24225L1.63915 5.77977C1.26691 6.41897 1.49198 7.23092 2.12393 7.60234L2.56543 7.85284C3.45708 8.36247 3.45708 9.64949 2.56543 10.1591L2.12393 10.4096C1.48333 10.781 1.26691 11.593 1.63915 12.2322L2.5308 13.7697C2.90304 14.4089 3.71679 14.6249 4.35739 14.2534L4.79886 14.0029C5.69051 13.4933 6.7986 14.1325 6.7986 15.1604V15.67C6.7986 16.4042 7.3959 17.0003 8.13172 17.0003H9.91501C10.6508 17.0003 11.2482 16.4042 11.2482 15.67V15.1604C11.2482 14.1325 12.3562 13.4933 13.2479 14.0029L13.6894 14.2534C14.33 14.6249 15.1437 14.4003 15.5159 13.7697L16.4076 12.2322C16.7798 11.593 16.5548 10.781 15.9228 10.4096L15.4813 10.1591C14.5897 9.64949 14.5897 8.36247 15.4813 7.85284L15.9228 7.60234C16.5634 7.23092 16.7798 6.41033 16.4076 5.77977Z" stroke="#8B8B8B" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/> |
|||
</svg> |
@ -0,0 +1,139 @@ |
|||
import http from "@/api/http"; |
|||
import React, { |
|||
createContext, |
|||
useContext, |
|||
useState, |
|||
ReactNode, |
|||
useRef, |
|||
useEffect, |
|||
} from "react"; |
|||
|
|||
interface Dua { |
|||
id: number; |
|||
text: string; |
|||
description: string; |
|||
local_alpha: string; |
|||
translation: string; |
|||
} |
|||
|
|||
interface AudioSyncData { |
|||
id: number; |
|||
duration: [number, number][]; |
|||
} |
|||
|
|||
interface Audio { |
|||
audio: string; |
|||
audio_sync_data: AudioSyncData[]; |
|||
reciter?: { |
|||
id: string; |
|||
name: string; |
|||
}; |
|||
} |
|||
|
|||
interface AudiosResponse { |
|||
results: Audio[]; |
|||
} |
|||
|
|||
interface AudioContextType { |
|||
audioRef: React.RefObject<HTMLAudioElement>; |
|||
audio: Audio | null; // Store a single audio object
|
|||
getAudio: (id: number) => Promise<void>; |
|||
selectedReciter?: string; |
|||
setSelectedReciter: React.Dispatch<React.SetStateAction<string | undefined>>; |
|||
} |
|||
|
|||
const AudioContext = createContext<AudioContextType | undefined>(undefined); |
|||
|
|||
export const AudioProvider: React.FC<{ children: ReactNode }> = ({ |
|||
children, |
|||
}) => { |
|||
const audioRef = useRef<HTMLAudioElement | null>(null); |
|||
const partRefs = useRef<(HTMLDivElement | null)[]>([]); |
|||
|
|||
const [audios, setAudios] = useState<Audio | null>({}); |
|||
const [audio, setAudio] = useState<Audio | null>({}); |
|||
const [selectedReciter, setSelectedReciter] = useState<string | undefined>(); |
|||
const [lastPart , setLastPart] = useState({}) |
|||
|
|||
const getAudio = async (id: number) => { |
|||
try { |
|||
// Fetching audio files
|
|||
const audioResponse = await http.get<AudiosResponse>( |
|||
`web/mafatih/${id}/audios/` |
|||
); |
|||
|
|||
console.log( |
|||
"Audio Response:", |
|||
audioResponse.data.results, |
|||
"selectedReciter:", |
|||
selectedReciter |
|||
); |
|||
setAudios(audioResponse.data.results); |
|||
let selectedAudio: Audio | undefined; |
|||
|
|||
if (selectedReciter) { |
|||
selectedAudio = audioResponse.data.results.find( |
|||
(audio) => audio.reciter?.id === selectedReciter |
|||
); |
|||
} |
|||
|
|||
if (!selectedAudio) { |
|||
selectedAudio = audioResponse.data.results.find( |
|||
(audio) => audio.audio_sync_data.length > 0 |
|||
); |
|||
} |
|||
|
|||
setAudio(selectedAudio || audioResponse.data.results[0] || null); |
|||
} catch (error) { |
|||
console.error("Error fetching audios:", error); |
|||
} |
|||
}; |
|||
|
|||
useEffect(() => { |
|||
if (!audios) return; // Ensure `audios` is not null or undefined
|
|||
|
|||
let selectedAudio: Audio | undefined; |
|||
|
|||
// If a reciter is selected, find the corresponding audio
|
|||
if (selectedReciter) { |
|||
selectedAudio = audios.find( |
|||
(audio) => audio.reciter?.id === selectedReciter |
|||
); |
|||
} |
|||
|
|||
// If no reciter is selected, pick the first audio with sync data or the first audio
|
|||
selectedAudio = selectedAudio || audios[0]; |
|||
|
|||
// Update the current audio
|
|||
setAudio(selectedAudio || null); |
|||
|
|||
// If no reciter is selected, update it with the first audio's reciter ID
|
|||
if (!selectedReciter && audios[0]?.reciter?.id) { |
|||
setSelectedReciter(audios[0].reciter.id); |
|||
} |
|||
}, [selectedReciter, audios]); |
|||
|
|||
|
|||
const value: AudioContextType = { |
|||
audioRef, |
|||
partRefs, |
|||
audio, |
|||
getAudio, |
|||
selectedReciter, |
|||
setSelectedReciter, |
|||
lastPart , setLastPart |
|||
}; |
|||
console.log(audios); |
|||
|
|||
return ( |
|||
<AudioContext.Provider value={value}>{children}</AudioContext.Provider> |
|||
); |
|||
}; |
|||
|
|||
export const useAudio = (): AudioContextType => { |
|||
const context = useContext(AudioContext); |
|||
if (context === undefined) { |
|||
throw new Error("useAudio must be used within an AudioProvider"); |
|||
} |
|||
return context; |
|||
}; |
@ -1,46 +1,80 @@ |
|||
import { useEffect } from 'react'; |
|||
import { motion, AnimatePresence } from 'framer-motion'; |
|||
import { useUI } from '../context/ui.context'; |
|||
import { useEffect } from "react"; |
|||
import { motion, AnimatePresence } from "framer-motion"; |
|||
import { useUI } from "../context/ui.context"; |
|||
import Logo from "../ui/logo"; |
|||
import { IoMdClose } from "react-icons/io"; |
|||
import Link from "next/link"; |
|||
import LanguageSwitcher from "../language-switcher"; |
|||
|
|||
const sidebarVariants = { |
|||
hidden: { x: '100%' }, |
|||
visible: { x: 0 }, |
|||
exit: { x: '100%' }, |
|||
hidden: { x: "-100%" }, // Sidebar starts off-screen to the left
|
|||
visible: { x: 0 }, // Sidebar is fully visible on the screen
|
|||
exit: { x: "-100%" }, // Sidebar exits to the left
|
|||
}; |
|||
|
|||
const MobileNavigation = () => { |
|||
const { sidebarDisplay, closeSidebar } = useUI(); |
|||
const { displaySidebar, closeSidebar } = useUI(); |
|||
|
|||
useEffect(() => { |
|||
if (!sidebarDisplay) { |
|||
document.body.style.overflow = 'hidden'; |
|||
} else { |
|||
document.body.style.overflow = 'auto'; |
|||
} |
|||
}, [sidebarDisplay]); |
|||
console.log("ssssssssssss"); |
|||
useEffect(() => { |
|||
document.body.style.overflow = displaySidebar ? "hidden" : "auto"; |
|||
}, [displaySidebar]); |
|||
|
|||
return ( |
|||
<AnimatePresence> |
|||
{sidebarDisplay && ( |
|||
<motion.div |
|||
className="fixed inset-0 z-50 flex justify-end" |
|||
initial="hidden" |
|||
animate="visible" |
|||
exit="exit" |
|||
variants={sidebarVariants} |
|||
transition={{ type: 'spring', stiffness: 300, damping: 30 }} |
|||
> |
|||
<div className="w-64 bg-white h-full shadow-lg p-4"> |
|||
<button onClick={closeSidebar} className="text-gray-600"> |
|||
Close |
|||
</button> |
|||
{/* Add your sidebar content here */} |
|||
</div> |
|||
</motion.div> |
|||
)} |
|||
</AnimatePresence> |
|||
); |
|||
return ( |
|||
<AnimatePresence> |
|||
{displaySidebar && ( |
|||
<> |
|||
{/* Overlay: Closes sidebar when clicked */} |
|||
<motion.div |
|||
className="fixed inset-0 z-40 bg-black bg-opacity-50" |
|||
onClick={closeSidebar} // Close the sidebar on clicking outside
|
|||
initial={{ opacity: 0 }} |
|||
animate={{ opacity: 1 }} |
|||
exit={{ opacity: 0 }} |
|||
></motion.div> |
|||
|
|||
{/* Sidebar */} |
|||
<motion.div |
|||
className="fixed inset-y-0 left-0 z-50 flex justify-start" |
|||
initial="hidden" |
|||
animate="visible" |
|||
exit="exit" |
|||
variants={sidebarVariants} |
|||
transition={{ type: "spring", stiffness: 250, damping: 30 }} |
|||
> |
|||
<div className="flex flex-col justify-between w-64 bg-white h-full shadow-lg p-8"> |
|||
<div> |
|||
<div className="flex justify-between"> |
|||
<Logo /> |
|||
<button onClick={closeSidebar} className="text-gray-600"> |
|||
<IoMdClose size={18} color="black" /> |
|||
</button> |
|||
</div> |
|||
<div className="mt-14"> |
|||
<ul className="flex flex-col gap-8 font-semibold text-black h-full items-start"> |
|||
<li className="h-full flex items-center border-[#c79389] hover:border-b hover:text-[#EB6E57] "> |
|||
<Link href={"/"}>Home</Link> |
|||
</li> |
|||
<li className="h-full flex items-center border-[#F4846F] hover:border-b hover:text-[#EB6E57] "> |
|||
<Link href={"/about"}>About us</Link> |
|||
</li> |
|||
<li className=""> |
|||
<Link |
|||
href={"/"} |
|||
className="bg-gradient-to-r from-[#F79B59] to-[#EB6E57] text-transparent bg-clip-text font-semibold" |
|||
> |
|||
Donate |
|||
</Link> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
<LanguageSwitcher /> |
|||
</div> |
|||
</motion.div> |
|||
</> |
|||
)} |
|||
</AnimatePresence> |
|||
); |
|||
}; |
|||
|
|||
export default MobileNavigation; |
|||
export default MobileNavigation; |
@ -1,16 +1,27 @@ |
|||
import React from 'react'; |
|||
import Image from 'next/image'; |
|||
import { useRouter } from 'next/router'; // Import useRouter
|
|||
import Setting from "../../../public/assets/images/Setting.svg"; |
|||
import { useUI } from '../context/ui.context'; |
|||
|
|||
|
|||
const MobileSetting: React.FC = () => { |
|||
const {openModal} = useUI() |
|||
return ( |
|||
<button onClick={()=>{openModal("SETTING_VIEW")}} className="p-1 bg-white rounded-[15px] z-50"> |
|||
<Image width={30} src={Setting} alt="Setting" /> |
|||
</button> |
|||
); |
|||
const { openModal } = useUI(); |
|||
const router = useRouter(); // Initialize the router
|
|||
|
|||
// Conditional rendering logic
|
|||
if (router.pathname.includes("duas")) { |
|||
return ( |
|||
<button |
|||
onClick={() => openModal("SETTING_VIEW")} |
|||
className="p-1 bg-white rounded-[15px] z-50" |
|||
> |
|||
<Image width={30} src={Setting} alt="Setting" /> |
|||
</button> |
|||
); |
|||
} |
|||
|
|||
// If the condition is not met, return null
|
|||
return null; |
|||
}; |
|||
|
|||
export default MobileSetting; |
|||
export default MobileSetting; |
@ -0,0 +1,119 @@ |
|||
// components/ui/setting.tsx
|
|||
import React, { useEffect, useRef, useState } from "react"; |
|||
import { useUI } from "../context/ui.context"; |
|||
import { IoMdClose } from "react-icons/io"; |
|||
import http from "@/api/http"; |
|||
import { useParams } from "next/navigation"; |
|||
import Image from "next/image"; |
|||
import { useAudio } from "../context/audio-conext"; |
|||
|
|||
interface ModalProps { |
|||
className?: string; // Made optional with default value
|
|||
} |
|||
|
|||
const RecitersModal: React.FC<ModalProps> = ({ className = "" }) => { |
|||
const { displaySetting, modalView, closeModal } = useUI(); |
|||
const {selectedReciter , setSelectedReciter} = useAudio() |
|||
const modalRef = useRef<HTMLDivElement>(null); |
|||
const closeButtonRef = useRef<HTMLButtonElement>(null); |
|||
const previouslyFocusedElement = useRef<HTMLElement | null>(null); |
|||
const [windowWidth, setWindowWidth] = useState(window.innerWidth); |
|||
const [reciters, setReciters] = useState([]); |
|||
const params = useParams(); |
|||
const slug = params?.slug as string; |
|||
const id = slug.split("-").pop(); |
|||
const modalClasses = windowWidth < 1024 ? "absolute w-full bottom-0" : ""; |
|||
|
|||
console.log(windowWidth); |
|||
|
|||
useEffect(() => { |
|||
http.get(`web/mafatih/${id}/reciters/`).then((res) => { |
|||
console.log("fdasfdasfads", res); |
|||
setReciters(res.data.results); |
|||
}); |
|||
|
|||
const handleResize = () => { |
|||
setWindowWidth(window.innerWidth); |
|||
}; |
|||
|
|||
window.addEventListener("resize", handleResize); |
|||
|
|||
// Cleanup the event listener on component unmount
|
|||
return () => { |
|||
window.removeEventListener("resize", handleResize); |
|||
}; |
|||
}, [id]); |
|||
// Handle modal visibility and accessibility
|
|||
useEffect(() => { |
|||
const handleOutsideClick = (event: MouseEvent) => { |
|||
if ( |
|||
modalRef.current && |
|||
!modalRef.current.contains(event.target as Node) |
|||
) { |
|||
closeModal(); |
|||
} |
|||
}; |
|||
|
|||
const handleKeyDown = (event: KeyboardEvent) => { |
|||
if (event.key === "Escape") { |
|||
closeModal(); |
|||
} |
|||
}; |
|||
|
|||
if (displaySetting) { |
|||
previouslyFocusedElement.current = document.activeElement as HTMLElement; |
|||
document.addEventListener("mousedown", handleOutsideClick); |
|||
document.addEventListener("keydown", handleKeyDown); |
|||
// Prevent background scrolling
|
|||
document.body.style.overflow = "hidden"; |
|||
// Shift focus to close button
|
|||
closeButtonRef.current?.focus(); |
|||
} else { |
|||
document.removeEventListener("mousedown", handleOutsideClick); |
|||
document.removeEventListener("keydown", handleKeyDown); |
|||
document.body.style.overflow = "auto"; |
|||
// Return focus to previously focused element
|
|||
previouslyFocusedElement.current?.focus(); |
|||
} |
|||
|
|||
return () => { |
|||
document.removeEventListener("mousedown", handleOutsideClick); |
|||
document.removeEventListener("keydown", handleKeyDown); |
|||
document.body.style.overflow = "auto"; |
|||
}; |
|||
}, []); |
|||
console.log(reciters); |
|||
reciters.map((item)=>{ |
|||
console.log(item); |
|||
|
|||
}) |
|||
return ( |
|||
<div |
|||
ref={modalRef} |
|||
className={`flex flex-col z-50 bg-white text-black min-h-60 max-h-[739px] rounded-3xl ${modalClasses} ${className}`} |
|||
role="dialog" |
|||
aria-modal="true" |
|||
> |
|||
<div className="flex justify-between items-center p-6 "> |
|||
<div className="text-[#8B8B8B] text-sm font-bold">Reciter</div> |
|||
<div></div> |
|||
<button onClick={closeModal}> |
|||
<IoMdClose size={18} /> |
|||
</button> |
|||
</div> |
|||
<div> |
|||
{reciters.map((reciter) => ( |
|||
<div onClick={(()=>setSelectedReciter(reciter.id))} key={reciter?.id} className="flex py-4 px-6 items-center gap-4 border-b hover:bg-[#EBEBEB] cursor-pointer"> |
|||
<div className={`w-4 h-4 rounded-full border border-black ${selectedReciter === reciter?.id && "bg-[#F4846F] border-0"}`}></div> |
|||
<Image className="rounded-full w-12 h-12" alt={reciter?.name} width={50} height={50} src={reciter?.avatar?.sm}/> |
|||
<p> |
|||
{reciter?.name} |
|||
</p> |
|||
</div> |
|||
))} |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default RecitersModal; |
@ -0,0 +1,151 @@ |
|||
import { useEffect, useState } from "react"; |
|||
import { FaPlay, FaPause } from "react-icons/fa6"; // Fixed import for FaPause
|
|||
import Setting from "../../../public/assets/images/GraySetting.svg"; |
|||
import Close from "../../../public/assets/images/Group 1000005170.svg"; |
|||
import timeLine from "../../../public/assets/images/Ellipse 441.svg"; |
|||
import Image from "next/image"; |
|||
import { useUI } from "../context/ui.context"; |
|||
import { useAudio } from "../context/audio-conext"; |
|||
import { IoPersonSharp } from "react-icons/io5"; |
|||
|
|||
const AudioControls = () => { |
|||
const { openModal, closeAudio } = useUI(); |
|||
const { audioRef, partRefs, audio, lastPart } = useAudio(); |
|||
const [isPlaying, setIsPlaying] = useState(false); |
|||
const [currentTime, setCurrentTime] = useState(0); |
|||
const [duration, setDuration] = useState(1); // Default duration to 1 to prevent division by zero
|
|||
|
|||
const play = () => { |
|||
if (audioRef.current && audio) { |
|||
if (lastPart && audio.audio_sync_data.length > 0) { |
|||
const selectedPart = audio.audio_sync_data.find( |
|||
(item) => item.id === lastPart.id |
|||
); |
|||
|
|||
if (selectedPart) { |
|||
// Set the audio current time to the start time of the lastPart
|
|||
audioRef.current.currentTime = selectedPart.duration[0][0]; |
|||
} |
|||
} |
|||
audioRef.current |
|||
.play() |
|||
.then(() => { |
|||
setIsPlaying(true); |
|||
}) |
|||
.catch((error) => { |
|||
console.error("Error playing audio:", error); |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
const pause = () => { |
|||
if (audioRef.current) { |
|||
audioRef.current.pause(); |
|||
setIsPlaying(false); |
|||
} |
|||
}; |
|||
|
|||
const checkPlayingState = () => { |
|||
if (audioRef.current) { |
|||
if (!audioRef.current.paused) { |
|||
setIsPlaying(true); |
|||
} else { |
|||
setIsPlaying(false); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const onTimeUpdate = () => { |
|||
if (audioRef.current) { |
|||
setCurrentTime(audioRef.current.currentTime); |
|||
setDuration(audioRef.current.duration); |
|||
} |
|||
}; |
|||
|
|||
function convertTo180(numerator, denominator) { |
|||
if (!numerator || !denominator) return -20; // Safeguard for invalid inputs
|
|||
const newNumerator = (numerator * 200) / denominator; |
|||
return newNumerator; // Adjust to start from -20 degrees
|
|||
} |
|||
|
|||
useEffect(() => { |
|||
if (audioRef.current) { |
|||
audioRef.current.addEventListener("timeupdate", onTimeUpdate); |
|||
|
|||
// Cleanup event listener on component unmount
|
|||
return () => { |
|||
audioRef.current.removeEventListener("timeupdate", onTimeUpdate); |
|||
}; |
|||
} |
|||
}, [audioRef]); |
|||
|
|||
useEffect(() => { |
|||
checkPlayingState(); |
|||
}, [audio, lastPart]); |
|||
|
|||
return ( |
|||
<div className="flex fixed bottom-0 left-0 right-0 p-5 justify-between items-center bg-white rounded-t-[45px] z-50"> |
|||
<div className="flex gap-4 items-center"> |
|||
<div> |
|||
<div className="relative flex justify-center rounded-full w-16 h-15 items-center"> |
|||
<Image |
|||
width={58} |
|||
height={58} |
|||
className="absolute right bottom-4" |
|||
src={timeLine} |
|||
alt="Time Line" |
|||
/> |
|||
<div |
|||
className="absolute w-full h-full" |
|||
style={{ |
|||
rotate: `${convertTo180(currentTime, duration)}deg`, |
|||
}} |
|||
> |
|||
<div className="text-[#F4846F] text-3xl font-bold">.</div> |
|||
</div> |
|||
<button |
|||
className="p-4 rounded-full w-12 h-12 flex items-center justify-center bg-[#F4846F]" |
|||
onClick={() => { |
|||
isPlaying ? pause() : play(); |
|||
}} |
|||
> |
|||
{isPlaying ? <FaPause color="white" /> : <FaPlay color="white" />} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
<div> |
|||
<p>{audio?.reciter?.name}</p> |
|||
<div className="flex items-center"> |
|||
{audio?.reciter?.avatar?.sm ? ( |
|||
<Image |
|||
className="rounded-full" |
|||
width={14} |
|||
height={14} |
|||
src={audio?.reciter?.avatar?.sm} |
|||
alt={audio?.reciter?.name} |
|||
/> |
|||
) : ( |
|||
<IoPersonSharp className="rounded-full" /> |
|||
)} |
|||
<p>{audio?.reciter?.name}</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div className="flex gap-4 items-center"> |
|||
<button onClick={() => openModal("RECITERS_VIEW")}> |
|||
<Image src={Setting} alt="Setting" /> |
|||
</button> |
|||
<button |
|||
onClick={() => { |
|||
closeAudio(); |
|||
pause(); |
|||
}} |
|||
> |
|||
<Image src={Close} alt="Setting" /> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default AudioControls; |
@ -0,0 +1,14 @@ |
|||
import { useUI } from "../context/ui.context"; |
|||
import AudioControls from "./audio-controls"; |
|||
import DownloadApp from "./download-app"; |
|||
|
|||
const StickyComponents = () => { |
|||
const { displayDownload , displayAudio } = useUI(); |
|||
return ( |
|||
<div className="sticky-components"> |
|||
{displayDownload && <DownloadApp />} |
|||
{displayAudio && <AudioControls />} |
|||
</div> |
|||
); |
|||
}; |
|||
export default StickyComponents; |
@ -0,0 +1,12 @@ |
|||
import Image from "next/image"; |
|||
import logo from "../../../public/assets/images/Hosseiniye.svg"; |
|||
|
|||
|
|||
const Logo = () => { |
|||
return ( |
|||
<div> |
|||
<Image src={logo} alt="Logo" /> |
|||
</div> |
|||
); |
|||
}; |
|||
export default Logo; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue