import React,{useEffect,useState,useRef,useCallback} from 'react' import {supabase} from '../lib/supabase' import { FiExternalLink,FiGithub,FiClock,FiCode, FiChevronLeft,FiChevronRight,FiPackage,FiAlertCircle,FiRefreshCw } from 'react-icons/fi' import './Projects.css' const BUCKET='Portfolio',FOLDER='projects_image',SLIDE_AT=3 function imgUrl(item){ if(!item?.image_url) return null if(item.image_url.startsWith('http')) return item.image_url return supabase.storage.from(BUCKET).getPublicUrl(`${FOLDER}/${item.image_url}`).data.publicUrl } function parseTools(t){ if(!t) return [] if(Array.isArray(t)) return t try{return JSON.parse(t)}catch{return t.split(',').map(s=>s.trim()).filter(Boolean)} } async function fetchProjects(){ const{data,error}=await supabase.from('projects').select('*').order('id',{ascending:false}) if(error) throw error return data??[] } export default function Projects(){ const[projects,setProjects]=useState([]) const[loading,setLoading]=useState(true) const[fetchErr,setFetchErr]=useState(null) const[filter,setFilter]=useState('All') const[cur,setCur]=useState(0) const[paused,setPaused]=useState(false) const timer=useRef(null) const load=useCallback(()=>{ setLoading(true);setFetchErr(null) fetchProjects() .then(data=>{setProjects(data);setFetchErr(null)}) .catch(err=>setFetchErr(err.message||'Failed to load projects')) .finally(()=>setLoading(false)) },[]) useEffect(()=>{load()},[load]) useEffect(()=>{ const ch=supabase.channel('projects-changes') .on('postgres_changes',{event:'*',schema:'public',table:'projects'},()=>fetchProjects().then(setProjects).catch(console.error)) .subscribe() return()=>{supabase.removeChannel(ch)} },[]) const allTags=['All',...new Set(projects.flatMap(p=>parseTools(p.tools)))] const list=filter==='All'?projects:projects.filter(p=>parseTools(p.tools).includes(filter)) const slide=list.length>SLIDE_AT const next=useCallback(()=>{if(slide)setCur(c=>(c+1)%list.length)},[slide,list.length]) const prev=useCallback(()=>{if(slide)setCur(c=>(c-1+list.length)%list.length)},[slide,list.length]) useEffect(()=>{ if(!slide||paused) return timer.current=setInterval(next,4000) return()=>clearInterval(timer.current) },[slide,paused,next]) useEffect(()=>setCur(0),[filter]) const visible3=()=>{ const v=[] for(let o=-1;o<=1;o++){ const i=(cur+o+list.length)%list.length v.push({p:list[i],pos:o}) } return v } return(
Could not load projects.
{fetchErr}
{projects.length===0?'Projects coming soon!':'No projects match this filter.'}
{p.description}
{tools.length>0&&