import { DateTime } from 'luxon'; import { Modal } from '../Modal'; import { ChevronRight, type LucideIcon, X } from 'lucide-react'; import { useState, type ReactNode, type SVGProps } from 'react'; import { GoogleCalendarIcon } from '../ReactIcons/GoogleCalendarIcon'; import { OutlookCalendarIcon } from '../ReactIcons/OutlookCalendarIcon'; import { AppleCalendarIcon } from '../ReactIcons/AppleCalendarIcon'; import { FileIcon } from '../ReactIcons/FileIcon'; function generateRoadmapIcsFile( title: string, details: string, location: string, startDate: Date, endDate: Date, ) { const ics = ` BEGIN:VCALENDAR VERSION:2.0 BEGIN:VEVENT SUMMARY:${title} DESCRIPTION:${details} LOCATION:${location} DTSTART:${startDate.toISOString().replace(/-|:|\.\d+/g, '')} DTEND:${endDate.toISOString().replace(/-|:|\.\d+/g, '')} RRULE:FREQ=DAILY BEGIN:VALARM TRIGGER:-PT30M ACTION:DISPLAY DESCRIPTION:Reminder: ${title} starts in 30 minutes END:VALARM BEGIN:VALARM TRIGGER:-PT15M ACTION:DISPLAY DESCRIPTION:Reminder: ${title} starts in 15 minutes END:VALARM END:VEVENT END:VCALENDAR `.trim(); return new Blob([ics], { type: 'text/calendar' }); } type ScheduleEventModalProps = { roadmapId: string; onClose: () => void; }; export function ScheduleEventModal(props: ScheduleEventModalProps) { const { onClose, roadmapId } = props; let roadmapTitle = ''; if (roadmapId === 'devops') { roadmapTitle = 'DevOps'; } else if (roadmapId === 'ios') { roadmapTitle = 'iOS'; } else if (roadmapId === 'postgresql-dba') { roadmapTitle = 'PostgreSQL'; } else if (roadmapId === 'devrel') { roadmapTitle = 'DevRel'; } else if (roadmapId === 'qa') { roadmapTitle = 'QA'; } else if (roadmapId === 'api-design') { roadmapTitle = 'API Design'; } else if (roadmapId === 'ai-data-scientist') { roadmapTitle = 'AI/Data Scientist'; } else if (roadmapId === 'technical-writer') { } else if (roadmapId === 'software-architect') { roadmapTitle = 'Software Architecture'; } else if (roadmapId === 'ai-engineer') { roadmapTitle = 'AI Engineer'; } else { roadmapTitle = roadmapId .split('-') .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } const [selectedCalendar, setSelectedCalendar] = useState< 'apple' | 'outlook' | null >(null); const [isLoading, setIsLoading] = useState(false); const location = `https://roadmap.sh/${roadmapId}`; const title = `Learn from ${roadmapTitle} Roadmap - roadmap.sh`; const details = ` Learn from the ${roadmapTitle} roadmap on roadmap.sh Visit the roadmap at https://roadmap.sh/${roadmapId} `.trim(); const handleDownloadICS = () => { setIsLoading(true); const startDate = DateTime.now().minus({ minutes: DateTime.now().minute % 30, }); const endDate = startDate.plus({ hours: 1 }); const blob = generateRoadmapIcsFile( title, details, location, startDate.toJSDate(), endDate.toJSDate(), ); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${roadmapTitle}.ics`; a.click(); setIsLoading(false); URL.revokeObjectURL(url); }; const handleGoogleCalendar = () => { setIsLoading(true); const baseURL = 'https://calendar.google.com/calendar/render?action=TEMPLATE'; const startDate = DateTime.now().minus({ minutes: DateTime.now().minute % 30, }); const endDate = startDate.plus({ hours: 1 }); const eventDetails = new URLSearchParams({ text: title, dates: `${startDate.toISO().replace(/-|:|\.\d+/g, '')}/${endDate.toISO().replace(/-|:|\.\d+/g, '')}`, details, location, recur: 'RRULE:FREQ=DAILY', }).toString(); setIsLoading(false); window.open(`${baseURL}&${eventDetails}`, '_blank'); }; const stepDetails = { apple: { title: 'Add to Apple Calendar', steps: [ 'Download the iCS File', 'Open the downloaded file, and it will automatically open your default calendar app.', <> If Apple Calendar is not your default calendar app, open Apple Calendar, go to File > Import, and choose the downloaded file. , ], }, outlook: { title: 'Add to Outlook Calendar', steps: [ 'Download the iCS File', <> Open Outlook and go to{' '} File > Open & Export > Import/Export. , <> In the Import and Export Wizard select{' '} Import an iCalendar (.ics) or vCalendar file (.vcs). You can then choose to keep it a separate calendar or make it a new calendar. , ], }, }; return (
{selectedCalendar && ( { setSelectedCalendar(null); }} isLoading={isLoading} /> )} {!selectedCalendar && ( <>

Schedule Learning Time

Block some time on your calendar to stay consistent

{ setSelectedCalendar('apple'); }} /> { setSelectedCalendar('outlook'); }} />
or download the iCS file and import it to your calendar app
)}
); } type SVGIcon = (props: SVGProps) => ReactNode; type CalendarButtonProps = { icon: LucideIcon | SVGIcon; label: string; isLoading?: boolean; onClick: () => void; }; function CalendarButton(props: CalendarButtonProps) { const { icon: Icon, label, isLoading, onClick } = props; return ( ); } type CalendarStepsProps = { title: string; steps: (string | ReactNode)[]; onDownloadICS: () => void; isLoading?: boolean; onCancel: () => void; }; export function CalendarSteps(props: CalendarStepsProps) { const { steps, onDownloadICS, onCancel, title, isLoading } = props; return (

{title}

{steps.map((step, index) => (
{index + 1}

{step}

))}
); }