import { useEffect, useRef, useState } from 'react'; import { Swiper, SwiperSlide } from 'swiper/react'; import Header from '../components/store/header'; import { Autoplay } from 'swiper/modules'; import { AnimatePresence, motion } from 'framer-motion'; import 'swiper/css'; import 'swiper/css/pagination'; import 'swiper/css/scrollbar'; import 'swiper/css/autoplay'; import SpinnerIcon from '../components/LoadingSpinner'; import localforage from 'localforage'; import { StoreDownloadTheme } from '../../seqta/ui/themes/downloadTheme'; const textVariants = { hidden: { opacity: 0, y: 60 }, visible: { opacity: 1, y: 0, transition: { type: 'spring', bounce: 0, stiffness: 80, damping: 12 } }, }; const containerVariants = { hidden: { y: '100vh', }, visible: { y: 0, transition: { staggerChildren: 0.05, type: 'spring', bounce: 0, stiffness: 400, damping: 50 }, }, }; export type Theme = { name: string; description: string; coverImage: string; marqueeImage: string; id: string; } type ThemesResponse = { themes: Theme[]; } export const DeleteDownloadedTheme = async (themeID: string) => { console.debug('DeleteDownloaded Theme:', themeID) await localforage.removeItem(themeID); const availableThemesList = await localforage.getItem('availableThemes') as string[]; const updatedThemesList = availableThemesList.filter(theme => theme !== themeID); await localforage.setItem('availableThemes', updatedThemesList); } const Store = () => { const [searchTerm, setSearchTerm] = useState(''); const swiperCover = useRef(null); const [gridThemes, setGridThemes] = useState([]); const [filteredThemes, setFilteredThemes] = useState([]); const [coverThemes, setCoverThemes] = useState([]); const [installingThemes, setInstallingThemes] = useState([]); const [currentThemes, setCurrentThemes] = useState([]); const [displayTheme, setDisplayTheme] = useState(null); const [loading, setLoading] = useState(true); const fetchThemes = async () => { try { const response = await fetch(`https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Themes/main/store/themes.json?nocache=${(new Date()).getTime()}`, { cache: 'no-store' }); const data: ThemesResponse = await response.json(); setGridThemes(data.themes); // Select up to 3 random themes to display in coverThemes const shuffled = [...data.themes].sort(() => 0.5 - Math.random()); setCoverThemes(shuffled.slice(0, 3)); setLoading(false); } catch (error) { console.error('Failed to fetch themes', error); // Retry after 5 seconds setTimeout(fetchThemes, 5000); } }; useEffect(() => { (async () => { document.title = 'BetterSEQTA+ Store'; fetchThemes(); const availableThemes = await localforage.getItem('availableThemes') as string[] | null; if (availableThemes) { setCurrentThemes(availableThemes) } })(); }, []); useEffect(() => { setFilteredThemes(gridThemes.filter(theme => theme.name.toLowerCase().includes(searchTerm.toLowerCase()) || theme.description.toLowerCase().includes(searchTerm.toLowerCase()) )); }, [searchTerm, gridThemes]); const downloadTheme = (id: string) => { const themeContent = gridThemes.find(theme => theme.id === id); if (!themeContent) { alert('There was an error, The theme was not found!') return } setInstallingThemes([...installingThemes, id]); StoreDownloadTheme({ themeContent }).then(() => { setInstallingThemes(installingThemes.filter(theme => theme !== id)); setCurrentThemes([...currentThemes, id]); }); }; const removeTheme = async (id: string) => { const themeContent = gridThemes.find(theme => theme.id === id); if (!themeContent) { alert('There was an error, The theme was not found!') return } setInstallingThemes([...installingThemes, id]); DeleteDownloadedTheme(id).then(() => { setInstallingThemes(installingThemes.filter(theme => theme !== id)); setCurrentThemes(currentThemes.filter(theme => theme !== id)); }); } return (
{/* loader */}
{ [...coverThemes, ...coverThemes].map((theme, index) => ( setDisplayTheme(theme)} key={index}> Theme Preview

{theme.name}

{theme.description}

{/* shadow from the bottom of the image */}
)) }
{displayTheme && ( setDisplayTheme(null)} > e.stopPropagation()} className="w-full max-w-xl h-[95%] p-4 bg-white rounded-t-2xl dark:bg-zinc-800 overflow-scroll" exit={{ y: "100vh" }} transition={{ type: 'spring', stiffness: 300, damping: 30 }} variants={containerVariants} initial="hidden" animate="visible" > setDisplayTheme(null)} variants={textVariants} > {'\ued8a'} {displayTheme.name} {displayTheme.description} { currentThemes.includes(displayTheme.id) ? removeTheme(displayTheme.id)} className="flex px-4 py-2 mt-4 ml-auto rounded-full dark:text-white bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600/50 hover:bg-zinc-200 focus:outline-none focus:ring-2 focus:ring-zinc-800 focus:ring-offset-2"> { installingThemes.includes(displayTheme.id) ? <> Removing... : <> Remove } : downloadTheme(displayTheme.id)} className="flex px-4 py-2 mt-4 ml-auto rounded-full dark:text-white bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600/50 hover:bg-zinc-200 focus:outline-none focus:ring-2 focus:ring-zinc-800 focus:ring-offset-2"> { installingThemes.includes(displayTheme.id) ? <> Installing... : <> Install } } Similar Themes
{gridThemes.filter(theme => theme.id !== displayTheme.id).sort((a, b) => a.name.localeCompare(displayTheme.name) - b.name.localeCompare(displayTheme.name)).map((theme, index) => ( { setDisplayTheme(null); setDisplayTheme(theme); }} className='w-full cursor-pointer' variants={textVariants}>
{theme.name}
Theme Preview
))}
)}
{/* pagination */}
{filteredThemes.map((theme, index) => (
setDisplayTheme(theme)} key={index} className='w-full cursor-pointer'>
{theme.name}
Theme Preview
))}
{'\uecb3'}
Got a Theme Idea?

Transform it into a stunning theme!

{filteredThemes.length == 0 && !loading && (

That doesnt exist! 😭😭😭

Sorry, we couldn’t find the theme you’re looking for. Maybe... you could create it?

)}
); }; export default Store;