// ============================================================================ // Sun Systems Catalogue — Main App // ============================================================================ const { useState, useEffect, useMemo, useRef, useCallback } = React; // Brand config — edit me const BRAND = { name: 'Sun Systems', tag: 'Pre-owned business laptops', phone: '7013608439', phoneDisplay: '+91 70136 08439', }; // Shown as a banner when a customer filters by a single brand const BRAND_TAGLINES = { Dell: 'Built to endure — Latitude & Precision, the workhorses of business computing.', HP: 'Sleek and dependable — EliteBook & ProBook for professional everyday work.', Lenovo: 'Legendary ThinkPad reliability — the keyboard typists swear by.', Apple: 'Premium power, beautifully built — MacBook Pro for creative work.', Microsoft: 'Surface — premium 2-in-1 versatility for work on the move.', }; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "theme": "light", "density": 4, "cardVariant": "minimal" }/*EDITMODE-END*/; const SORTS = { 'featured': { label: 'Featured first', fn: (a,b) => (b.featured?1:0) - (a.featured?1:0) || (a.stock==='sold-out'?1:0) - (b.stock==='sold-out'?1:0) }, 'price-asc': { label: 'Price: low → high', fn: (a,b) => a.priceMin - b.priceMin }, 'price-desc': { label: 'Price: high → low', fn: (a,b) => b.priceMin - a.priceMin }, 'newest': { label: 'Newest generation', fn: (a,b) => (b.generation||0) - (a.generation||0) }, 'ram': { label: 'Most RAM', fn: (a,b) => b.ram - a.ram }, }; // ============================================================================ // DATA SOURCE — paste your published Google Sheet CSV link between the quotes. // Empty string ('') = use the offline list in products.js. // Setup steps are in GOOGLE-SHEET-SETUP.txt // ============================================================================ const GOOGLE_SHEET_CSV_URL = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQFQr_EWfGedSwGiLn7I6V4OvIPl2q0dKCX37r0T1iNmgQDN-0Plhvg2rJBBG93VH8leSKN5lptsy9l/pub?gid=1093569637&single=true&output=csv'; // ---------- Filter chip set helper ---------- function uniqSorted(arr, cmp) { return [...new Set(arr)].filter(x => x !== null && x !== undefined && x !== '').sort(cmp); } // ---------- Tiny CSV parser (handles quotes, commas, newlines) ---------- function parseCsv(text) { const rows = []; let cur = [], val = '', inQ = false; for (let i = 0; i < text.length; i++) { const c = text[i]; if (inQ) { if (c === '"' && text[i+1] === '"') { val += '"'; i++; } else if (c === '"') { inQ = false; } else { val += c; } } else { if (c === '"') inQ = true; else if (c === ',') { cur.push(val); val = ''; } else if (c === '\n') { cur.push(val); rows.push(cur); cur = []; val = ''; } else if (c === '\r') { /* skip */ } else { val += c; } } } if (val.length || cur.length) { cur.push(val); rows.push(cur); } if (!rows.length) return []; const headers = rows[0].map(h => h.trim().toLowerCase().replace(/[^a-z0-9]/g, '')); return rows.slice(1).filter(r => r.some(v => v.trim() !== '')).map(r => { const o = {}; headers.forEach((h, i) => o[h] = r[i] !== undefined ? r[i].trim() : ''); return o; }); } // ---------- Infer CPU tier & generation from a processor string ---------- function inferCpuTier(p = '') { const s = p.toLowerCase(); if (s.includes('i9')) return 'i9'; if (s.includes('i7')) return 'i7'; if (s.includes('i5')) return 'i5'; if (s.includes('i3')) return 'i3'; if (s.includes('xeon')) return 'Xeon'; if (s.includes('ryzen 7')) return 'Ryzen 7'; if (s.includes('ryzen 5')) return 'Ryzen 5'; if (s.includes('amd')) return 'AMD'; return 'Other'; } function inferGen(p = '') { const m = String(p).match(/(\d+)\s*(st|nd|rd|th)?\s*gen/i); return m ? parseInt(m[1]) : null; } // ---------- Normalize one raw row (from products.js OR the Google Sheet) ---------- function normalizeProduct(raw, i) { // Accept several spellings. Sheet rows arrive lowercased+stripped (parseCsv); // products.js rows use camelCase keys — so check BOTH forms for each key. const g = (...keys) => { for (const k of keys) { const stripped = k.toLowerCase().replace(/[^a-z0-9]/g, ''); for (const kk of [k, stripped]) { if (raw[kk] !== undefined && raw[kk] !== '' && raw[kk] !== null) return raw[kk]; } } return ''; }; const num = (x) => { const n = parseInt(String(x).replace(/[^0-9.]/g, '')); return isNaN(n) ? null : n; }; const truthy = (x) => /^(1|true|yes|y)$/i.test(String(x).trim()); const model = String(g('model', 'name') || `Item ${i+1}`).trim(); const processor = String(g('processor', 'cpu')).trim(); const baseRam = num(g('baseRam', 'ram')) || 8; const baseStorage = num(g('baseStorage', 'storage', 'ssd')) || 256; const basePrice = num(g('basePrice', 'price')) || 0; const addRam = num(g('addRam', 'ramUpgrade', 'plusRam')); const addSsd = num(g('addSsd', 'ssdUpgrade', 'plusSsd', 'addStorage')); const display = parseFloat(String(g('display', 'screen', 'screensize')).replace(/[^0-9.]/g, '')) || 0; const displayNote = String(g('displayNote', 'screennote')).trim(); const gfx = String(g('graphics', 'gpu')).trim(); const brand = String(g('brand') || 'Other').trim(); const series = String(g('series') || 'Other').trim(); const stockRaw = String(g('stock', 'availability')).toLowerCase(); const images = [g('image1', 'image', 'img1'), g('image2', 'img2'), g('image3', 'img3')] .map(x => String(x).trim()).filter(Boolean); const v = variantsOf({ baseRam, baseStorage, basePrice, addRam, addSsd }); return { id: String(g('id') || ('p' + String(i+1).padStart(3, '0'))), model, brand, series, processor, cpuTier: String(g('cputier') || inferCpuTier(processor)), generation: num(g('generation', 'gen')) ?? inferGen(processor), baseRam, baseStorage, basePrice, addRam, addSsd, ramOptions: v.ramOptions, storageOptions: v.storageOptions, ram: baseRam, storage: baseStorage, ramLabel: `${baseRam}GB`, storageLabel: `${baseStorage}GB SSD`, display, displayNote, displayLabel: display ? `${display} in${displayNote ? ` (${displayNote})` : ''}` : '—', graphics: gfx && gfx !== '-' ? gfx : null, hasDedicatedGpu: !!(gfx && gfx !== '-'), priceMin: v.priceMin, priceMax: v.priceMax, images, image: images[0] || null, stock: stockRaw.includes('sold') || stockRaw.includes('out') ? 'sold-out' : 'in-stock', featured: truthy(g('featured')), notes: String(g('notes', 'note')).trim(), }; } function normalizeAll(rows) { return (rows || []).map(normalizeProduct); } // ---------- Persistent state hook ---------- function useLocalState(key, initial) { const [v, setV] = useState(() => { try { const raw = localStorage.getItem(key); if (raw !== null) return JSON.parse(raw); } catch (e) {} return initial; }); useEffect(() => { try { localStorage.setItem(key, JSON.stringify(v)); } catch (e) {} }, [key, v]); return [v, setV]; } // ---------- Persistent tweaks (stays across reloads) ---------- function usePersistentTweaks(defaults) { const [vals, setVals] = useLocalState('sun-systems-tweaks', defaults); useEffect(() => { setVals(v => ({ ...defaults, ...v })); /* eslint-disable-next-line */ }, []); const setTweak = useCallback((keyOrEdits, val) => { setVals(v => { if (typeof keyOrEdits === 'object') return { ...v, ...keyOrEdits }; return { ...v, [keyOrEdits]: val }; }); }, [setVals]); return [vals, setTweak]; } function App() { const [tweaks, setTweak] = usePersistentTweaks(TWEAK_DEFAULTS); // Start with the bundled fallback list, then (if configured) swap in live sheet data const [products, setProducts] = useState(() => normalizeAll(window.PRODUCTS || [])); const [search, setSearch] = useState(''); const [sort, setSort] = useState('featured'); const [showFilters, setShowFilters] = useState(false); const [filters, setFilters] = useState({ brand: [], cpuTier: [], generation: [], ram: [], priceMin: '', priceMax: '', inStockOnly: false, }); const [openProduct, setOpenProduct] = useState(null); const [compareIds, setCompareIds] = useState([]); const [showCompare, setShowCompare] = useState(false); const [toast, setToast] = useState(''); // Track viewport width so we can hide desktop-only options on mobile (< 768px) const [isMobile, setIsMobile] = useState(() => typeof window !== 'undefined' && window.innerWidth < 768); useEffect(() => { const onResize = () => setIsMobile(window.innerWidth < 768); window.addEventListener('resize', onResize); return () => window.removeEventListener('resize', onResize); }, []); // Apply theme useEffect(() => { document.documentElement.dataset.theme = tweaks.theme; }, [tweaks.theme]); // Load live data from the Google Sheet (falls back to products.js on any error) useEffect(() => { if (!GOOGLE_SHEET_CSV_URL) return; let cancelled = false; fetch(GOOGLE_SHEET_CSV_URL) .then(r => r.text()) .then(txt => { const rows = normalizeAll(parseCsv(txt)); if (!cancelled && rows.length) setProducts(rows); }) .catch(() => { /* keep the bundled fallback list */ }); return () => { cancelled = true; }; }, []); // Toast helper const showToast = useCallback((msg) => { setToast(msg); setTimeout(() => setToast(''), 2200); }, []); // ---------- Filter options ---------- const opts = useMemo(() => ({ brands: uniqSorted(products.map(p => p.brand)), cpuTiers: ['i3','i5','i7','i9','Xeon','Ryzen 5','Ryzen 7','AMD','Other'].filter(t => products.some(p => p.cpuTier === t)), generations: uniqSorted(products.map(p => p.generation), (a,b) => b - a), rams: uniqSorted(products.flatMap(p => p.ramOptions || [p.ram]), (a,b) => a - b), }), [products]); // ---------- Filtered + sorted ---------- const visible = useMemo(() => { const q = search.trim().toLowerCase(); let result = products.filter(p => { if (q) { const hay = [p.model, p.brand, p.series, p.processor, p.cpuTier, p.ramLabel, p.storageLabel].join(' ').toLowerCase(); if (!hay.includes(q)) return false; } if (filters.brand.length && !filters.brand.includes(p.brand)) return false; if (filters.cpuTier.length && !filters.cpuTier.includes(p.cpuTier)) return false; if (filters.generation.length && !filters.generation.includes(p.generation)) return false; if (filters.ram.length && !filters.ram.some(r => (p.ramOptions || [p.ram]).includes(r))) return false; if (filters.inStockOnly && p.stock !== 'in-stock') return false; const pmin = parseInt(filters.priceMin) || 0; const pmax = parseInt(filters.priceMax) || Infinity; if (p.priceMax < pmin || p.priceMin > pmax) return false; return true; }); result.sort(SORTS[sort].fn); return result; }, [products, search, filters, sort]); const activeFilterCount = ( filters.brand.length + filters.cpuTier.length + filters.generation.length + filters.ram.length + (filters.priceMin?1:0) + (filters.priceMax?1:0) + (filters.inStockOnly?1:0) ); const compareItems = compareIds.map(id => products.find(p => p.id === id)).filter(Boolean); // ---------- Toggle helpers ---------- const toggleFilter = (key, value) => { setFilters(f => ({ ...f, [key]: f[key].includes(value) ? f[key].filter(v => v !== value) : [...f[key], value] })); }; const clearFilters = () => setFilters({ brand:[], cpuTier:[], generation:[], ram:[], priceMin:'', priceMax:'', inStockOnly:false }); const toggleCompare = (product) => { setCompareIds(ids => { if (ids.includes(product.id)) { return ids.filter(i => i !== product.id); } if (ids.length >= 3) { showToast('You can compare up to 3 laptops at a time'); return ids; } showToast(`Added ${product.model} to compare`); return [...ids, product.id]; }); }; return ( <>
Flypzo Backed by Sun Systems
{BRAND.phoneDisplay}
WhatsApp
Refurbished · Tested · Trusted

A curated catalogue of business-grade laptops, ready to ship.

Flypzo is the online store of Sun Systems — Hyderabad's trusted name in refurbished business laptops. We stock pre-owned Dell, HP, Lenovo and Apple machines, fully tested and professionally cleaned, each covered by a 1-month testing warranty and a 6-month service warranty. Browse, compare specs, and message us on WhatsApp to confirm your model.

{products.length} Models in stock
1-month Testing warranty
6-month Service warranty
All-India Doorstep delivery
setSearch(e.target.value)} />
{showFilters && (
Brand
{opts.brands.map(b => ( ))}
Processor
{opts.cpuTiers.map(t => ( ))}
Intel Generation
{opts.generations.map(g => ( ))}
RAM
{opts.rams.map(r => ( ))}
Price range (₹)
setFilters(f => ({...f, priceMin: e.target.value}))} /> to setFilters(f => ({...f, priceMax: e.target.value}))} />
Availability
{activeFilterCount > 0 && ( )}
)}
{visible.length} {visible.length === 1 ? 'laptop' : 'laptops'} {(search || activeFilterCount > 0) && · matching your filters}
All prices in INR · Inclusive of taxes
{filters.brand.length === 1 && BRAND_TAGLINES[filters.brand[0]] && (
{filters.brand[0]} {BRAND_TAGLINES[filters.brand[0]]}
)} {visible.length === 0 ? (

No laptops match your filters.

Try clearing some filters or adjusting your search.

{activeFilterCount > 0 && }
) : (
{visible.map(p => ( ))}
)}

Have a model in mind? Send us a note.

Drop your details and we'll get back within a few hours with availability, photos and the latest price. For instant replies, message us on WhatsApp.

{BRAND.phoneDisplay} Chat on WhatsApp Hyderabad · All-India shipping
{/* Compare drawer */}
0 ? 'open' : ''}`}>
Compare {compareItems.length}/3
{compareItems.map(p => ( {p.model} ))}
{/* Modals */} {openProduct && setOpenProduct(null)} showPrice={true} waPhone={BRAND.phone} />} {showCompare && compareItems.length >= 2 && setShowCompare(false)} onRemove={toggleCompare} showPrice={true} />} {/* Floating WhatsApp button — desktop/tablet only (hidden on phones) */} Chat with us {/* Mobile sticky sales bar — phones only */}
WhatsApp
{/* Toast */}
{toast}
{/* Floating Tweaks panel — always visible, persistent */} setTweak('theme', v)} /> {/* Cards per row only matters on desktop — mobile is always 1 column */} {!isMobile && ( setTweak('density', parseInt(v))} /> )} setTweak('cardVariant', v)} /> setTweak(TWEAK_DEFAULTS)} secondary /> ); } // ---------- Inquiry form ---------- function InquiryForm({ waPhone }) { const [data, setData] = useState({ name:'', phone:'', interested:'', message:'' }); const [sent, setSent] = useState(false); const submit = (e) => { e.preventDefault(); // Showcase only — open WhatsApp with pre-filled message const msg = `Hello Flypzo!\n\nName: ${data.name}\nPhone: ${data.phone}\nInterested in: ${data.interested}\n\n${data.message}`; window.open(`https://wa.me/91${waPhone}?text=${encodeURIComponent(msg)}`, '_blank'); setSent(true); setTimeout(() => setSent(false), 6000); }; return (
setData(d => ({...d, name: e.target.value}))} placeholder="Full name" />
setData(d => ({...d, phone: e.target.value}))} placeholder="10-digit mobile" />
setData(d => ({...d, interested: e.target.value}))} placeholder="e.g. Latitude 5430, ThinkPad T14" />