// DEPLOY_CONFIG: {"triggers": [{"name": "notify-members-on-harvest-logged", "on": "collection.add", "collection": "harvests", "actions": [{"type": "notification", "to": "all_members", "subject": "New harvest logged", "body": "A new harvest has been logged. Check the app for details."}]}], "cron": [{"name": "weekly-garden-status-email-digest", "schedule": "0 9 * * 1", "action": "event", "config": {"event_type": "weekly-garden-status-email-digest"}}]} import { useEffect, useMemo, useState } from 'react'; import { useAuth, usePresence, useProxy } from '@deplixo/sdk'; import { GardenGrid } from './components/GardenGrid.jsx'; import { HarvestCalendar } from './components/HarvestCalendar.jsx'; import { MyPlots } from './components/MyPlots.jsx'; import { PlotModal } from './components/PlotModal.jsx'; function PresenceBar({ currentUser }) { const { users, update } = usePresence({ status: 'online', name: currentUser?.name, avatar: currentUser?.avatar, email: currentUser?.email, }); const [presenceState, setPresenceState] = useState('online'); const [lastActivityAt, setLastActivityAt] = useState(Date.now()); useEffect(() => { const setActive = () => { setPresenceState('online'); setLastActivityAt(Date.now()); update?.({ status: 'online', lastSeenAt: Date.now(), }); }; const setIdle = () => { setPresenceState('idle'); update?.({ status: 'idle', lastSeenAt: Date.now(), }); }; const handleVisibilityChange = () => { if (document.hidden) { setIdle(); } else { setActive(); } }; const activityEvents = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll']; const idleTimer = setInterval(() => { if (Date.now() - lastActivityAt > 60 * 1000) { setIdle(); } }, 15000); activityEvents.forEach(eventName => window.addEventListener(eventName, setActive, { passive: true })); document.addEventListener('visibilitychange', handleVisibilityChange); setActive(); return () => { activityEvents.forEach(eventName => window.removeEventListener(eventName, setActive)); document.removeEventListener('visibilitychange', handleVisibilityChange); clearInterval(idleTimer); update?.({ status: 'offline', lastSeenAt: Date.now() }); }; }, [lastActivityAt, update]); const presentUsers = useMemo(() => { return (users || []).filter(user => user?.status !== 'offline'); }, [users]); return (
{presentUsers.length} {presentUsers.length === 1 ? 'member' : 'members'} at the garden
{presentUsers.length === 0 ? ( No one is active right now ) : ( presentUsers.map(user => ( {user.avatar ? ( {user.name ) : ( {user.name?.charAt(0)?.toUpperCase() || 'G'} )} {user.name || 'Member'} {user.status || 'online'} )) )}
); } function App() { const { user, loading, login, logout } = useAuth(); const [activeTab, setActiveTab] = useState('garden'); const [selectedPlot, setSelectedPlot] = useState(null); const tabs = [ { id: 'garden', label: '๐ŸŒฑ Garden Map', icon: '๐Ÿ—บ๏ธ' }, { id: 'myplots', label: '๐Ÿฅ• My Plots', icon: '๐Ÿฅ•' }, { id: 'calendar', label: '๐Ÿ“… Harvests', icon: '๐Ÿ“…' }, ]; if (loading) { return
Signing in...
; } if (!user) { return (

๐ŸŒฟ Community Garden

Sign in with Google to join your members garden.

); } return (

๐ŸŒฟ Community Garden

Grow together, harvest together

{user.avatar ? ( {user.name} ) : (
{user.name?.charAt(0)?.toUpperCase() || 'G'}
)}
{user.name} {user.email}
{activeTab === 'garden' && } {activeTab === 'myplots' && } {activeTab === 'calendar' && }
{selectedPlot !== null && ( setSelectedPlot(null)} /> )}
); } function WeatherAdviceBar() { const { fetch: proxyFetch, loading: proxyLoading, error: proxyError } = useProxy(); const [weather, setWeather] = useState(null); const [advice, setAdvice] = useState([]); const [locationLabel, setLocationLabel] = useState('your area'); useEffect(() => { let cancelled = false; const loadWeather = async () => { try { const forecast = await proxyFetch('https://api.weatherapi.com/v1/forecast.json?key=${WEATHER_KEY}&q=auto:ip&days=3&aqi=no&alerts=no', { method: 'GET' }); if (cancelled || !forecast) return; setWeather(forecast); setLocationLabel(forecast?.location?.name ? `${forecast.location.name}, ${forecast.location.region || forecast.location.country || ''}`.trim().replace(/,$/, '') : 'your area'); } catch (err) { if (!cancelled) { setWeather(null); } } }; loadWeather(); return () => { cancelled = true; }; }, [proxyFetch]); useEffect(() => { const nextAdvice = []; const currentTempC = weather?.current?.temp_c; const chanceOfRain = weather?.forecast?.forecastday?.[0]?.day?.daily_chance_of_rain; const willFreezeNight = weather?.forecast?.forecastday?.some(day => (day?.day?.mintemp_c ?? 99) <= 1) || false; const conditionText = weather?.current?.condition?.text || ''; if (typeof currentTempC === 'number') { if (currentTempC < 5) { nextAdvice.push('Too cold for sensitive seedlings: start indoors or wait for warmer soil.'); } else if (currentTempC <= 12) { nextAdvice.push('Good for cool-season crops like lettuce, peas, spinach, and radishes.'); } else if (currentTempC <= 24) { nextAdvice.push('Great planting weather for most vegetables and herbs.'); } else { nextAdvice.push('Warm conditions: water new transplants well and mulch to conserve moisture.'); } } if (typeof chanceOfRain === 'number') { if (chanceOfRain >= 70) { nextAdvice.push('Rain is likely soon: avoid heavy watering and consider direct sowing hardy seeds.'); } else if (chanceOfRain >= 35) { nextAdvice.push('Moderate rain chance: water lightly and watch for damp-soil fungal issues.'); } else { nextAdvice.push('Low rain chance: plan to water after planting if the topsoil dries out.'); } } if (willFreezeNight) { nextAdvice.push('Frost risk ahead: protect tender plants with covers or delay planting warm-season crops.'); } if (/storm|thunder|snow|sleet|freezing/i.test(conditionText)) { nextAdvice.push('Adverse conditions detected: avoid transplanting today if possible.'); } if (nextAdvice.length === 0) { nextAdvice.push('Weather data is loading. Check back for planting guidance in a moment.'); } setAdvice(nextAdvice); }, [weather]); return (
๐ŸŒฆ๏ธ Planting forecast

{locationLabel}

{proxyLoading && Loading weather...} {!proxyLoading && weather?.current && ( {weather.current.temp_c}ยฐC ยท {weather.current.condition?.text || 'Current conditions'} )}
{proxyError && !weather ? (

Weather preview unavailable right now. Please try again later.

) : ( <> {weather?.forecast?.forecastday?.[0] && (
{weather.forecast.forecastday.slice(0, 3).map(day => ( {day.date} {day.day?.avgtemp_c}ยฐC avg {day.day?.daily_chance_of_rain || 0}% rain ))}
)} )}
); } ReactDOM.createRoot(document.getElementById("root")).render();