import { writable } from 'svelte/store'; import { browser } from '$app/environment'; export interface FlowerPot { id: string; name: string; plantType: string; wateringFrequency: number; // days lastWatered: Date; nextWatering: Date; notes?: string; photo?: string; // base64 encoded image price?: number; category?: string; careInstructions?: string[]; growthStages?: { stage: string; description: string; timeline: string }[]; additionalImages?: string[]; } export interface WateringLogEntry { id: string; potId: string; potName: string; wateredAt: Date; notes?: string; } function createFlowerPotsStore() { const initialValue: FlowerPot[] = []; const { subscribe, set, update } = writable(initialValue); // Load from localStorage on client if (browser) { const stored = localStorage.getItem('flower-pots'); if (stored) { try { const parsed = JSON.parse(stored); // Convert date strings back to Date objects const pots = parsed.map((pot: any) => ({ ...pot, lastWatered: new Date(pot.lastWatered), nextWatering: new Date(pot.nextWatering) })); set(pots); } catch (e) { console.error('Failed to parse stored flower pots:', e); } } } return { subscribe, add: (pot: Omit) => { const newPot: FlowerPot = { ...pot, id: crypto.randomUUID(), nextWatering: new Date(pot.lastWatered.getTime() + pot.wateringFrequency * 24 * 60 * 60 * 1000) }; update(pots => { const newPots = [...pots, newPot]; if (browser) { localStorage.setItem('flower-pots', JSON.stringify(newPots)); } return newPots; }); }, remove: (id: string) => { update(pots => { const newPots = pots.filter(pot => pot.id !== id); if (browser) { localStorage.setItem('flower-pots', JSON.stringify(newPots)); } return newPots; }); }, water: (id: string) => { update(pots => { const newPots = pots.map(pot => { if (pot.id === id) { const now = new Date(); // Add entry to watering log wateringLog.add({ potId: pot.id, potName: pot.name, wateredAt: now }); return { ...pot, lastWatered: now, nextWatering: new Date(now.getTime() + pot.wateringFrequency * 24 * 60 * 60 * 1000) }; } return pot; }); if (browser) { localStorage.setItem('flower-pots', JSON.stringify(newPots)); } return newPots; }); }, update: (id: string, updates: Partial>) => { update(pots => { const newPots = pots.map(pot => { if (pot.id === id) { const updated = { ...pot, ...updates }; if (updates.lastWatered || updates.wateringFrequency) { updated.nextWatering = new Date( (updates.lastWatered || pot.lastWatered).getTime() + (updates.wateringFrequency || pot.wateringFrequency) * 24 * 60 * 60 * 1000 ); } return updated; } return pot; }); if (browser) { localStorage.setItem('flower-pots', JSON.stringify(newPots)); } return newPots; }); }, export: () => { let currentPots: FlowerPot[] = []; subscribe(value => currentPots = value)(); return currentPots; }, import: (importedPots: Omit[], merge: boolean = false) => { update(currentPots => { let newPots: FlowerPot[]; if (merge) { // Merge with existing pots, avoiding duplicates by name const existingNames = new Set(currentPots.map(pot => pot.name.toLowerCase())); const potsToAdd = importedPots .filter(pot => !existingNames.has(pot.name.toLowerCase())) .map(pot => ({ ...pot, nextWatering: new Date(pot.lastWatered.getTime() + pot.wateringFrequency * 24 * 60 * 60 * 1000) })); newPots = [...currentPots, ...potsToAdd]; } else { // Replace all pots newPots = importedPots.map(pot => ({ ...pot, nextWatering: new Date(pot.lastWatered.getTime() + pot.wateringFrequency * 24 * 60 * 60 * 1000) })); } if (browser) { localStorage.setItem('flower-pots', JSON.stringify(newPots)); } return newPots; }); } }; } export const flowerPots = createFlowerPotsStore(); function createWateringLogStore() { const initialValue: WateringLogEntry[] = []; const { subscribe, set, update } = writable(initialValue); // Load from localStorage on client if (browser) { const stored = localStorage.getItem('watering-log'); if (stored) { try { const parsed = JSON.parse(stored); // Convert date strings back to Date objects const logs = parsed.map((log: any) => ({ ...log, wateredAt: new Date(log.wateredAt) })); set(logs); } catch (e) { console.error('Failed to parse stored watering log:', e); } } } return { subscribe, add: (entry: Omit) => { const newEntry: WateringLogEntry = { ...entry, id: crypto.randomUUID() }; update(logs => { const newLogs = [...logs, newEntry]; if (browser) { localStorage.setItem('watering-log', JSON.stringify(newLogs)); } return newLogs; }); }, remove: (id: string) => { update(logs => { const newLogs = logs.filter(log => log.id !== id); if (browser) { localStorage.setItem('watering-log', JSON.stringify(newLogs)); } return newLogs; }); }, getRecent: (limit: number = 10) => { let logs: WateringLogEntry[] = []; subscribe(value => logs = value)(); return logs .sort((a, b) => b.wateredAt.getTime() - a.wateredAt.getTime()) .slice(0, limit); } }; } export const wateringLog = createWateringLogStore(); // Computed store for upcoming watering export const upcomingWatering = writable([]); flowerPots.subscribe(pots => { const now = new Date(); const upcoming = pots .filter(pot => pot.nextWatering <= new Date(now.getTime() + 24 * 60 * 60 * 1000)) // Next 24 hours .sort((a, b) => a.nextWatering.getTime() - b.nextWatering.getTime()); upcomingWatering.set(upcoming); }); // Dashboard layout preferences export interface DashboardSection { id: string; type: 'welcome' | 'stats' | 'content-grid' | 'plant-list' | 'quick-actions'; enabled: boolean; } export interface StatsCard { id: string; type: 'total-plants' | 'healthy' | 'need-watering' | 'overdue' | 'total-waterings'; enabled: boolean; } function createDashboardLayoutStore() { const defaultSections: DashboardSection[] = [ { id: 'welcome', type: 'welcome', enabled: false }, { id: 'stats', type: 'stats', enabled: true }, { id: 'content-grid', type: 'content-grid', enabled: true }, { id: 'plant-list', type: 'plant-list', enabled: true }, { id: 'quick-actions', type: 'quick-actions', enabled: true } ]; const defaultStatsCards: StatsCard[] = [ { id: 'total-plants', type: 'total-plants', enabled: true }, { id: 'healthy', type: 'healthy', enabled: true }, { id: 'need-watering', type: 'need-watering', enabled: true }, { id: 'overdue', type: 'overdue', enabled: true }, { id: 'total-waterings', type: 'total-waterings', enabled: true } ]; const { subscribe, set, update } = writable({ sections: defaultSections, statsCards: defaultStatsCards }); // Load from localStorage on client if (browser) { const stored = localStorage.getItem('dashboard-layout'); if (stored) { try { const layout = JSON.parse(stored); set(layout); } catch (e) { console.error('Failed to parse stored dashboard layout:', e); } } } return { subscribe, updateSections: (sections: DashboardSection[]) => { update(layout => { const newLayout = { ...layout, sections }; if (browser) { localStorage.setItem('dashboard-layout', JSON.stringify(newLayout)); } return newLayout; }); }, updateStatsCards: (statsCards: StatsCard[]) => { update(layout => { const newLayout = { ...layout, statsCards }; if (browser) { localStorage.setItem('dashboard-layout', JSON.stringify(newLayout)); } return newLayout; }); }, reset: () => { const newLayout = { sections: defaultSections, statsCards: defaultStatsCards }; set(newLayout); if (browser) { localStorage.setItem('dashboard-layout', JSON.stringify(newLayout)); } } }; } export const dashboardLayout = createDashboardLayoutStore(); // Theme store function createThemeStore() { const { subscribe, set, update } = writable<'light' | 'dark'>('light'); // Load from localStorage on client if (browser) { const stored = localStorage.getItem('theme'); if (stored === 'dark' || stored === 'light') { set(stored); // Apply theme class to document document.documentElement.classList.toggle('dark', stored === 'dark'); } else { // Check system preference const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; const defaultTheme = prefersDark ? 'dark' : 'light'; set(defaultTheme); document.documentElement.classList.toggle('dark', defaultTheme === 'dark'); } } return { subscribe, set: (theme: 'light' | 'dark') => { set(theme); if (browser) { localStorage.setItem('theme', theme); document.documentElement.classList.toggle('dark', theme === 'dark'); } }, toggle: () => { update(current => { const newTheme = current === 'light' ? 'dark' : 'light'; if (browser) { localStorage.setItem('theme', newTheme); document.documentElement.classList.toggle('dark', newTheme === 'dark'); } return newTheme; }); } }; } export const theme = createThemeStore(); // Authentication store export interface User { username: string; password: string; // In production, this would be hashed } function createAuthStore() { const { subscribe, set, update } = writable<{ isAuthenticated: boolean; user: User | null }>({ isAuthenticated: false, user: null }); // Load from localStorage on client if (browser) { const stored = localStorage.getItem('auth-user'); if (stored) { try { const user = JSON.parse(stored); set({ isAuthenticated: true, user }); } catch (e) { console.error('Failed to parse stored auth user:', e); localStorage.removeItem('auth-user'); } } } return { subscribe, login: (username: string, password: string): boolean => { // Simple authentication - in production, this would validate against a backend // For demo purposes, accept any non-empty username/password if (username.trim() && password.trim()) { const user: User = { username: username.trim(), password }; // Don't store password in production set({ isAuthenticated: true, user }); if (browser) { localStorage.setItem('auth-user', JSON.stringify(user)); } return true; } return false; }, logout: () => { set({ isAuthenticated: false, user: null }); if (browser) { localStorage.removeItem('auth-user'); } }, register: (username: string, password: string): boolean => { // Simple registration - in production, this would create user in backend if (username.trim() && password.trim()) { const user: User = { username: username.trim(), password }; set({ isAuthenticated: true, user }); if (browser) { localStorage.setItem('auth-user', JSON.stringify(user)); } return true; } return false; } }; } export const auth = createAuthStore(); // Utility function to generate mock care instructions and growth stages export function generateMockPlantData(plantType: string): { careInstructions: string[]; growthStages: { stage: string; description: string; timeline: string }[]; additionalImages: string[]; } { const careInstructions: Record = { 'Snake Plant': [ 'Water every 2-3 weeks, allowing soil to dry completely between waterings', 'Prefers bright, indirect light but tolerates low light conditions', 'Keep in temperatures between 60-85°F (15-29°C)', 'Use well-draining potting soil mixed with perlite or sand' ], 'Spider Plant': [ 'Water when top inch of soil feels dry', 'Thrives in bright, indirect light', 'Maintain humidity levels above 50%', 'Fertilize monthly during growing season with balanced houseplant fertilizer' ], 'Pothos': [ 'Water every 1-2 weeks when top soil is dry', 'Grows well in low to bright indirect light', 'Prefers temperatures between 65-85°F (18-29°C)', 'Prune regularly to encourage bushier growth' ], 'Peace Lily': [ 'Keep soil consistently moist but not waterlogged', 'Thrives in low to medium indirect light', 'Mist leaves regularly to maintain humidity', 'Use room temperature water and avoid letting plant sit in water' ], 'ZZ Plant': [ 'Water every 2-3 weeks, allowing soil to dry completely', 'Tolerates low light conditions well', 'Very drought tolerant once established', 'Prefers well-draining cactus/succulent soil mix' ] }; const growthStages: Record = { 'Snake Plant': [ { stage: 'Seedling', description: 'Small shoots emerging from soil', timeline: '0-1 month' }, { stage: 'Juvenile', description: 'Leaves developing vertical growth', timeline: '1-3 months' }, { stage: 'Mature', description: 'Tall, upright leaves with yellow edges', timeline: '3-6 months' }, { stage: 'Flowering', description: 'Rare white flowers on tall stalks', timeline: '1-2 years' } ], 'Spider Plant': [ { stage: 'Seedling', description: 'First few narrow leaves appear', timeline: '0-1 month' }, { stage: 'Juvenile', description: 'Developing characteristic white stripes', timeline: '1-2 months' }, { stage: 'Mature', description: 'Full arching leaves with plantlets', timeline: '2-4 months' }, { stage: 'Producing', description: 'Regular production of baby spider plants', timeline: '4+ months' } ], 'Pothos': [ { stage: 'Cutting', description: 'Initial rooting of stem cutting', timeline: '0-2 weeks' }, { stage: 'Establishing', description: 'New leaves developing from nodes', timeline: '2-4 weeks' }, { stage: 'Vining', description: 'Long trailing stems with heart-shaped leaves', timeline: '1-2 months' }, { stage: 'Bushy', description: 'Full, dense growth with multiple stems', timeline: '2-4 months' } ], 'Peace Lily': [ { stage: 'Seedling', description: 'First broad leaves emerging', timeline: '0-1 month' }, { stage: 'Juvenile', description: 'Developing dark green foliage', timeline: '1-2 months' }, { stage: 'Mature', description: 'Large leaves with white flower spikes', timeline: '2-4 months' }, { stage: 'Flowering', description: 'Regular blooming with proper care', timeline: '4+ months' } ], 'ZZ Plant': [ { stage: 'Seedling', description: 'First glossy leaves appear', timeline: '0-1 month' }, { stage: 'Juvenile', description: 'Developing thick, waxy leaves', timeline: '1-3 months' }, { stage: 'Mature', description: 'Full, bushy appearance with multiple stems', timeline: '3-6 months' }, { stage: 'Established', description: 'Very drought tolerant and low maintenance', timeline: '6+ months' } ] }; const additionalImages: Record = { 'Snake Plant': [ 'https://images.unsplash.com/photo-1593691509543-c55fb32d8de5?w=400', 'https://images.unsplash.com/photo-1586093148909-4e1166c7b6c7?w=400', 'https://images.unsplash.com/photo-1593691509543-c55fb32d8de5?w=400' ], 'Spider Plant': [ 'https://images.unsplash.com/photo-1586093148909-4e1166c7b6c7?w=400', 'https://images.unsplash.com/photo-1593691509543-c55fb32d8de5?w=400', 'https://images.unsplash.com/photo-1586093148909-4e1166c7b6c7?w=400' ], 'Pothos': [ 'https://images.unsplash.com/photo-1593691509543-c55fb32d8de5?w=400', 'https://images.unsplash.com/photo-1586093148909-4e1166c7b6c7?w=400', 'https://images.unsplash.com/photo-1593691509543-c55fb32d8de5?w=400' ], 'Peace Lily': [ 'https://images.unsplash.com/photo-1586093148909-4e1166c7b6c7?w=400', 'https://images.unsplash.com/photo-1593691509543-c55fb32d8de5?w=400', 'https://images.unsplash.com/photo-1586093148909-4e1166c7b6c7?w=400' ], 'ZZ Plant': [ 'https://images.unsplash.com/photo-1593691509543-c55fb32d8de5?w=400', 'https://images.unsplash.com/photo-1586093148909-4e1166c7b6c7?w=400', 'https://images.unsplash.com/photo-1593691509543-c55fb32d8de5?w=400' ] }; return { careInstructions: careInstructions[plantType] || [ 'Water when top soil feels dry', 'Provide bright, indirect light', 'Maintain room temperature (65-75°F)', 'Use well-draining potting soil' ], growthStages: growthStages[plantType] || [ { stage: 'Seedling', description: 'Initial growth phase', timeline: '0-1 month' }, { stage: 'Juvenile', description: 'Developing leaves and stems', timeline: '1-2 months' }, { stage: 'Mature', description: 'Full grown plant', timeline: '2-4 months' } ], additionalImages: additionalImages[plantType] || [] }; }