/* @component-map * App — Main container, routes between setup and main app views * SetupScreen — Initial setup flow for user identity and reference date * MainApp — Primary app view with seat display, actions, holidays * Header — App title and theme button * BirthdayBanner — Shows birthday celebration banner on Sept 18 * SeatCard — Displays who has front seat today with streak and swap info * ActionButtons — Swap and dispute buttons * BirthdayCountdown — Mini card showing days until birthday * HolidaySection — Upcoming holidays list with add custom holiday * ThemeModal — Theme picker modal * DisputeModal — Dispute declaration modal with logic explanation * AddHolidayModal — Modal to add custom holidays * @end-component-map */ import { useCollection } from '@deplixo/sdk'; const THEMES = [ { id: 'theme-pastel', label: '🌸 Blossom', bg: '#fff0f6', accent: '#ff6b9d', text: '#2d1b2e' }, { id: 'theme-retro', label: '👾 Retro', bg: '#0a0a1a', accent: '#00ff9f', text: '#ffffff' }, { id: 'theme-luxury', label: '✨ Luxe', bg: '#1a1408', accent: '#d4af37', text: '#f5f0e8' }, { id: 'theme-nature', label: '🌿 Nature', bg: '#e8f5e9', accent: '#2e7d32', text: '#1b3a1c' }, { id: 'theme-neon', label: '⚡ Neon', bg: '#0d0d0d', accent: '#ff073a', text: '#ffffff' }, { id: 'theme-lemon', label: '🍋 Lemon', bg: '#fffde7', accent: '#f9a825', text: '#3e2a00' }, { id: 'theme-sky', label: '🩵 Sky', bg: '#e3f2fd', accent: '#1976d2', text: '#0d1b2a' }, { id: 'theme-peach', label: '🍑 Peach', bg: '#fff3e0', accent: '#e64a19', text: '#3e1000' }, { id: 'theme-lavender', label: '💜 Lavender', bg: '#f3e5f5', accent: '#7b1fa2', text: '#1a0030' }, { id: 'theme-mint', label: '🌿 Mint', bg: '#e0f7fa', accent: '#00838f', text: '#00252a' }, ]; const US_HOLIDAYS_STATIC = [ { name: "New Year's Day", month: 1, day: 1 }, { name: "Valentine's Day", month: 2, day: 14 }, { name: "St. Patrick's Day", month: 3, day: 17 }, { name: "Easter", dynamic: true, fnName: 'getEaster' }, { name: "Mother's Day", dynamic: true, fnName: 'getMothersDay' }, { name: "Memorial Day", dynamic: true, fnName: 'getMemorialDay' }, { name: "Father's Day", dynamic: true, fnName: 'getFathersDay' }, { name: "Independence Day", month: 7, day: 4 }, { name: "Labor Day", dynamic: true, fnName: 'getLaborDay' }, { name: "Halloween", month: 10, day: 31 }, { name: "Thanksgiving", dynamic: true, fnName: 'getThanksgiving' }, { name: "Christmas", month: 12, day: 25 }, { name: "New Year's Eve", month: 12, day: 31 }, ]; function getEaster(year) { const a = year % 19, b = Math.floor(year / 100), c = year % 100; const d = Math.floor(b / 4), e = b % 4, f = Math.floor((b + 8) / 25); const g = Math.floor((b - f + 1) / 3), h = (19 * a + b - d - g + 15) % 30; const i = Math.floor(c / 4), k = c % 4, l = (32 + 2 * e + 2 * i - h - k) % 7; const m = Math.floor((a + 11 * h + 22 * l) / 451); const month = Math.floor((h + l - 7 * m + 114) / 31); const day = ((h + l - 7 * m + 114) % 31) + 1; return new Date(year, month - 1, day); } function getNthWeekday(year, month, weekday, n) { const d = new Date(year, month - 1, 1); let count = 0; while (true) { if (d.getDay() === weekday) { count++; if (count === n) return new Date(d); } d.setDate(d.getDate() + 1); if (d.getMonth() !== month - 1) break; } return null; } function getLastWeekday(year, month, weekday) { const d = new Date(year, month, 0); while (d.getDay() !== weekday) d.setDate(d.getDate() - 1); return d; } function getMothersDay(y) { return getNthWeekday(y, 5, 0, 2); } function getMemorialDay(y) { return getLastWeekday(y, 5, 1); } function getFathersDay(y) { return getNthWeekday(y, 6, 0, 3); } function getLaborDay(y) { return getNthWeekday(y, 9, 1, 1); } function getThanksgiving(y) { return getNthWeekday(y, 11, 4, 4); } const dynamicFns = { getEaster, getMothersDay, getMemorialDay, getFathersDay, getLaborDay, getThanksgiving }; function getToday() { const d = new Date(); return new Date(d.getFullYear(), d.getMonth(), d.getDate()); } function dateToStr(d) { return d.toISOString().split('T')[0]; } function daysBetween(a, b) { return Math.round((b - a) / (1000 * 60 * 60 * 24)); } function whoHasSeatOnDate(refDate, refPerson, date) { const ref = new Date(refDate); const diff = daysBetween(ref, date); const isEven = diff % 2 === 0; if (isEven) return refPerson; return refPerson === 'Madelyn' ? 'Sydney' : 'Madelyn'; } function getEffectiveSeat(refDate, refPerson, swapStartDate, date) { const base = whoHasSeatOnDate(refDate, refPerson, date); if (swapStartDate && date >= new Date(swapStartDate)) { return base === 'Madelyn' ? 'Sydney' : 'Madelyn'; } return base; } function getBirthdayCountdown() { const t = getToday(); let bday = new Date(t.getFullYear(), 8, 18); if (bday <= t) bday = new Date(t.getFullYear() + 1, 8, 18); return daysBetween(t, bday); } function getAllHolidays(customHolidays) { const t = getToday(); const year = t.getFullYear(); const holidays = []; for (const h of US_HOLIDAYS_STATIC) { for (let y = year; y <= year + 1; y++) { let d = h.dynamic ? dynamicFns[h.fnName](y) : new Date(y, h.month - 1, h.day); if (d && d >= t) { holidays.push({ name: h.name, date: d }); break; } } } for (const ch of (customHolidays || [])) { const val = ch.value || ch; let d = new Date(val.date + 'T00:00:00'); if (val.repeat === 'annual') { let candidate = new Date(year, d.getMonth(), d.getDate()); if (candidate < t) candidate = new Date(year + 1, d.getMonth(), d.getDate()); holidays.push({ name: val.name, date: candidate, custom: true, id: ch.id }); } else { if (d >= t) holidays.push({ name: val.name, date: d, custom: true, id: ch.id }); } } holidays.sort((a, b) => a.date - b.date); return holidays; } function SetupScreen({ onFinish }) { const [setupUser, setSetupUser] = React.useState(null); const [setupRefPerson, setSetupRefPerson] = React.useState(null); const [setupDate, setSetupDate] = React.useState(''); const handleFinish = () => { if (!setupUser || !setupRefPerson || !setupDate) { alert('Please fill everything in!'); return; } onFinish({ user: setupUser, refDate: setupDate, refPerson: setupRefPerson, streakStart: dateToStr(getToday()), swapStartDate: null, theme: 'theme-pastel', }); }; return (
Let's get you set up
Today is YOUR day — both of you!
🎊This will reset your streak to 0. The math behind today's seat assignment is shown below so you can settle it together.
After declaring, ask the other person to tap Dispute on their phone too to reset their streak.
Loading...