UI Changes for calendar scheduling

feat/calendar
Kamran Ahmed 2 weeks ago
parent d946eeecf8
commit 56f63aa481
  1. 2
      .astro/settings.json
  2. 115
      src/components/FrameRenderer/ProgressNudge.tsx
  3. 31
      src/components/ReactIcons/AppleCalendarIcon.tsx
  4. 22
      src/components/RoadmapHeader.astro
  5. 7
      src/components/Schedule/ScheduleButton.tsx
  6. 184
      src/components/Schedule/ScheduleEventModal.tsx
  7. 2
      src/components/ShareRoadmapButton.tsx
  8. 2
      src/data/roadmaps/ai-data-scientist/ai-data-scientist.md
  9. 2
      src/data/roadmaps/ai-engineer/ai-engineer.md
  10. 2
      src/data/roadmaps/datastructures-and-algorithms/datastructures-and-algorithms.md

@ -3,6 +3,6 @@
"enabled": false
},
"_variables": {
"lastUpdateCheck": 1729612578122
"lastUpdateCheck": 1731065649795
}
}

@ -1,6 +1,10 @@
import { cn } from '../../lib/classname.ts';
import { roadmapProgress, totalRoadmapNodes } from '../../stores/roadmap.ts';
import { useStore } from '@nanostores/react';
import { Calendar, Info, X } from 'lucide-react';
import { Tooltip } from '../Tooltip.tsx';
import { useState } from 'react';
import { ScheduleEventModal } from '../Schedule/ScheduleEventModal.tsx';
type ProgressNudgeProps = {
resourceType: 'roadmap' | 'best-practice';
@ -8,6 +12,11 @@ type ProgressNudgeProps = {
};
export function ProgressNudge(props: ProgressNudgeProps) {
const { resourceId, resourceType } = props;
const [isNudgeHidden, setIsNudgeHidden] = useState(false);
const [isScheduleModalOpen, setIsScheduleModalOpen] = useState(false);
const $totalRoadmapNodes = useStore(totalRoadmapNodes);
const $roadmapProgress = useStore(roadmapProgress);
@ -17,52 +26,80 @@ export function ProgressNudge(props: ProgressNudgeProps) {
const hasProgress = done > 0;
if (!$totalRoadmapNodes) {
if (!$totalRoadmapNodes || isNudgeHidden) {
return null;
}
return (
<div
className={
'fixed bottom-5 left-1/2 z-30 hidden -translate-x-1/2 transform animate-fade-slide-up overflow-hidden rounded-full bg-stone-900 px-4 py-2 text-center text-white shadow-2xl transition-all duration-300 sm:block'
}
>
<span
className={cn('block', {
hidden: hasProgress,
})}
<>
{isScheduleModalOpen && (
<ScheduleEventModal
onClose={() => {
setIsScheduleModalOpen(false);
}}
roadmapId={resourceId}
/>
)}
<div
className={
'fixed bottom-5 left-1/2 z-30 hidden -translate-x-1/2 transform animate-fade-slide-up flex-row gap-1.5 transition-all duration-300 lg:flex'
}
>
<span className="mr-2 text-sm font-semibold uppercase text-yellow-400">
Tip
</span>
<span className="text-sm text-gray-200">
Right-click on a topic to mark it as done.{' '}
<div
className={
'relative overflow-hidden rounded-full bg-stone-900 px-4 py-2 text-center text-white shadow-2xl'
}
>
<span
className={cn('flex items-center', {
hidden: hasProgress,
})}
>
<span className="mr-2 text-sm font-semibold uppercase text-yellow-400">
Tip
</span>
<span className="text-sm text-gray-200">
Right-click a topic to mark it as done &nbsp;
</span>
</span>
<span
className={cn('relative z-20 block text-sm', {
hidden: !hasProgress,
})}
>
<span className="relative -top-[0.45px] mr-2 text-xs font-medium uppercase text-yellow-400">
Progress
</span>
<span>{done > $totalRoadmapNodes ? $totalRoadmapNodes : done}</span>{' '}
of <span>{$totalRoadmapNodes}</span> Done
</span>
<span
className="absolute bottom-0 left-0 top-0 z-10 bg-stone-700"
style={{
width: `${(done / $totalRoadmapNodes) * 100}%`,
}}
></span>
</div>
{resourceType === 'roadmap' && (
<button
data-popup="progress-help"
className="cursor-pointer font-semibold text-yellow-500 underline"
onClick={() => {
setIsScheduleModalOpen(true);
}}
className="group relative flex items-center gap-2 rounded-full bg-stone-900 px-3 text-sm text-yellow-400"
>
Learn more.
<Calendar className="h-4 w-4 flex-shrink-0" strokeWidth={2.5} />
</button>
</span>
</span>
<span
className={cn('relative z-20 block text-sm', {
hidden: !hasProgress,
})}
>
<span className="relative -top-[0.45px] mr-2 text-xs font-medium uppercase text-yellow-400">
Progress
</span>
<span>{done > $totalRoadmapNodes ? $totalRoadmapNodes : done}</span> of{' '}
<span>{$totalRoadmapNodes}</span> Done
</span>
<span
className="absolute bottom-0 left-0 top-0 z-10 bg-stone-700"
style={{
width: `${(done / $totalRoadmapNodes) * 100}%`,
}}
></span>
</div>
)}
<button
onClick={() => {
setIsNudgeHidden(true);
}}
className="group relative flex items-center gap-2 rounded-full bg-stone-900 px-3 text-sm text-yellow-400"
>
<X className="h-4 w-4 flex-shrink-0" strokeWidth={2.5} />
</button>
</div>
</>
);
}

@ -3,37 +3,34 @@ import type { SVGProps } from 'react';
export function AppleCalendarIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
width="1736"
height="1693"
viewBox="0 0 1736 1693"
fill="none"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
width={100}
height={100}
viewBox="0 0 48 48"
{...props}
>
<rect width="1736" height="1693" fill="#ECEFF1" />
<rect x="1" width="1734" height="526" fill="#FF3D00" />
<path
fill="#eceff1"
d="M15.556,43h16.889C38.274,43,43,38.274,43,32.444V15.556C43,9.726,38.274,5,32.444,5H15.556 C9.726,5,5,9.726,5,15.556v16.889C5,38.274,9.726,43,15.556,43z"
d="M724.689 300.13L750.665 128H805.4L756.691 401.947H701.224L669.269 240.501L637.587 401.947H581.892L533 128H588.101L613.894 299.947L646.032 128H692.505L724.689 300.13Z"
fill="white"
/>
<path
fill="#ff3d00"
d="M20.868,13.77L21.437,10h1.199l-1.067,6h-1.215l-0.7-3.536L18.96,16h-1.22l-1.071-6h1.207 l0.565,3.766L19.145,10h1.018L20.868,13.77z"
d="M976.776 283.419H890.632V356.061H992.617V401.947H835.303V128H992.206V174.069H890.632V238.812H976.776V283.419Z"
fill="white"
/>
<path
fill="#ff3d00"
d="M26.39,13.404h-1.887v1.591h2.234V16h-3.446v-6h3.437v1.009h-2.225v1.418h1.887V13.404z"
/>
<path
fill="#ff3d00"
d="M27.433,16v-6h1.587c0.7,0,1.259,0.223,1.675,0.667c0.416,0.445,0.628,1.055,0.636,1.83v0.973 c0,0.788-0.208,1.407-0.625,1.857C30.291,15.775,29.717,16,28.986,16H27.433z M28.645,11.009v3.985h0.363 c0.404,0,0.688-0.106,0.853-0.319c0.165-0.213,0.252-0.58,0.26-1.102V12.53c0-0.56-0.079-0.951-0.235-1.173 c-0.157-0.221-0.423-0.337-0.8-0.348H28.645z"
d="M1024.39 401.947V128H1096.84C1128.79 128 1154.31 138.182 1173.3 158.454C1192.29 178.771 1201.97 206.623 1202.34 242.008V286.433C1202.34 322.411 1192.84 350.673 1173.8 371.219C1154.86 391.674 1128.66 401.947 1095.28 401.947H1024.39ZM1079.72 174.069V356.015H1096.29C1114.73 356.015 1127.7 351.175 1135.23 341.45C1142.76 331.725 1146.73 314.969 1147.1 291.135V243.514C1147.1 217.946 1143.49 200.094 1136.37 189.958C1129.2 179.867 1117.06 174.571 1099.85 174.069H1079.72Z"
fill="white"
/>
<path
d="M831.353 1451.15H380.138V1345.95L587.348 1082.46C613.643 1045.98 632.999 1013.97 645.462 986.442C657.925 958.91 664.133 932.52 664.133 907.271C664.133 873.256 658.29 846.592 646.512 827.324C634.78 808.056 617.843 798.423 595.748 798.423C571.553 798.423 552.379 809.654 538.182 832.072C523.984 854.536 516.863 886.086 516.863 926.767H367.492C367.492 879.785 377.216 836.821 396.663 797.875C416.111 758.929 443.456 728.703 478.698 707.153C513.941 685.556 553.84 674.781 598.35 674.781C666.735 674.781 719.736 693.638 757.444 731.351C795.152 769.065 814.006 822.621 814.006 892.067C814.006 935.168 803.552 978.954 782.735 1023.29C761.872 1067.67 724.073 1122.27 669.383 1187.11L571.051 1327.55H831.353V1451.15Z"
fill="#424242"
d="M23.211,36.004h-9.884V33.7l4.539-5.771c0.576-0.799,1-1.5,1.273-2.103 c0.273-0.603,0.409-1.181,0.409-1.734c0-0.745-0.128-1.329-0.386-1.751c-0.257-0.422-0.628-0.633-1.112-0.633 c-0.53,0-0.95,0.246-1.261,0.737c-0.311,0.492-0.467,1.183-0.467,2.074H13.05c0-1.029,0.213-1.97,0.639-2.823 c0.426-0.853,1.025-1.515,1.797-1.987C16.258,19.236,17.132,19,18.107,19c1.498,0,2.659,0.413,3.485,1.239 c0.826,0.826,1.239,1.999,1.239,3.52c0,0.944-0.229,1.903-0.685,2.874c-0.457,0.972-1.285,2.168-2.483,3.588l-2.154,3.076h5.702 V36.004z"
/>
<path
d="M1354.1 888.871C1354.1 926.036 1346.21 959.001 1330.41 987.766C1314.62 1016.53 1292.89 1039.5 1265.22 1056.66C1296.77 1074.56 1321.69 1099.17 1339.91 1130.58C1358.12 1161.95 1367.25 1198.89 1367.25 1241.3C1367.25 1309.33 1347.62 1363.07 1308.36 1402.52C1269.1 1441.97 1215.6 1461.69 1147.94 1461.69C1080.29 1461.69 1026.47 1441.97 986.475 1402.52C946.53 1363.07 926.535 1309.33 926.535 1241.3C926.535 1198.89 935.62 1161.9 953.88 1130.35C972.095 1098.81 997.203 1074.24 1029.11 1056.71C1001.04 1039.54 979.171 1016.58 963.376 987.811C947.58 959.047 939.683 926.128 939.683 888.916C939.683 821.936 958.445 769.521 995.971 731.625C1033.45 693.729 1083.8 674.781 1146.89 674.781C1210.71 674.781 1261.2 693.912 1298.36 732.127C1335.52 770.343 1354.1 822.576 1354.1 888.871ZM1147.94 1338.05C1170.36 1338.05 1187.66 1328.46 1199.76 1309.38C1211.85 1290.29 1217.88 1263.72 1217.88 1229.71C1217.88 1195.69 1211.58 1169.07 1198.94 1149.76C1186.29 1130.45 1168.94 1120.81 1146.89 1120.81C1124.8 1120.81 1107.36 1130.45 1094.58 1149.76C1081.79 1169.07 1075.36 1195.69 1075.36 1229.71C1075.36 1263.72 1081.75 1290.29 1094.58 1309.38C1107.36 1328.51 1125.16 1338.05 1147.94 1338.05ZM1205.78 896.724C1205.78 866.909 1200.94 843.076 1191.31 825.224C1181.68 807.326 1166.89 798.377 1146.89 798.377C1127.95 798.377 1113.57 807.052 1103.8 824.402C1093.98 841.752 1089.05 865.859 1089.05 896.724C1089.05 926.904 1093.98 951.148 1103.8 969.594C1113.61 987.994 1128.31 997.217 1147.94 997.217C1167.57 997.217 1182.14 987.994 1191.59 969.594C1201.04 951.194 1205.78 926.904 1205.78 896.724Z"
fill="#424242"
d="M34.662,23.689c0,0.814-0.173,1.536-0.519,2.166c-0.346,0.63-0.822,1.133-1.428,1.509 c0.691,0.392,1.237,0.931,1.636,1.619c0.399,0.687,0.599,1.496,0.599,2.425c0,1.49-0.43,2.667-1.29,3.531 c-0.86,0.864-2.032,1.296-3.514,1.296s-2.661-0.432-3.537-1.296c-0.875-0.864-1.313-2.041-1.313-3.531 c0-0.929,0.199-1.739,0.599-2.43c0.399-0.691,0.949-1.229,1.648-1.613c-0.615-0.376-1.094-0.879-1.44-1.509 c-0.346-0.63-0.519-1.351-0.519-2.166c0-1.467,0.411-2.615,1.233-3.445C27.638,19.415,28.741,19,30.123,19 c1.398,0,2.504,0.419,3.318,1.256C34.255,21.093,34.662,22.237,34.662,23.689z M30.146,33.527c0.491,0,0.87-0.21,1.135-0.628 c0.265-0.418,0.397-1,0.397-1.745s-0.138-1.328-0.415-1.751s-0.657-0.634-1.14-0.634c-0.484,0-0.866,0.211-1.146,0.634 s-0.421,1.006-0.421,1.751s0.14,1.327,0.421,1.745C29.257,33.318,29.647,33.527,30.146,33.527z M31.413,23.861 c0-0.653-0.106-1.175-0.317-1.566c-0.211-0.392-0.535-0.588-0.973-0.588c-0.415,0-0.73,0.19-0.944,0.57 c-0.215,0.38-0.323,0.908-0.323,1.584c0,0.661,0.108,1.192,0.323,1.596c0.215,0.403,0.537,0.605,0.967,0.605 c0.43,0,0.749-0.202,0.956-0.605C31.309,25.054,31.413,24.522,31.413,23.861z"
/>
</svg>
);

@ -7,12 +7,12 @@ import {
} from 'lucide-react';
import { TabLink } from './TabLink';
import LoginPopup from './AuthenticationFlow/LoginPopup.astro';
import { ScheduleButton } from './Schedule/ScheduleButton';
import ProgressHelpPopup from './ProgressHelpPopup.astro';
import { MarkFavorite } from './FeaturedItems/MarkFavorite';
import { type RoadmapFrontmatter } from '../lib/roadmap';
import { ShareRoadmapButton } from './ShareRoadmapButton';
import { DownloadRoadmapButton } from './DownloadRoadmapButton';
import { ScheduleButton } from './Schedule/ScheduleButton';
export interface Props {
title: string;
@ -46,8 +46,6 @@ const roadmapTitle =
roadmapId === 'devops'
? 'DevOps'
: `${roadmapId.charAt(0).toUpperCase()}${roadmapId.slice(1)}`;
const hasTnsBanner = !!tnsBannerLink;
---
<LoginPopup />
@ -96,6 +94,12 @@ const hasTnsBanner = !!tnsBannerLink;
className='relative top-px mr-2 text-gray-500 !opacity-100 hover:text-gray-600 focus:outline-0 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:stroke-gray-400 [&>svg]:stroke-[0.4] hover:[&>svg]:stroke-gray-600 sm:[&>svg]:h-4 sm:[&>svg]:w-4'
client:only='react'
/>
<ScheduleButton
resourceId={roadmapId}
resourceType='roadmap'
resourceTitle={title}
client:load
/>
<DownloadRoadmapButton roadmapId={roadmapId} client:idle />
<ShareRoadmapButton
description={description}
@ -104,11 +108,11 @@ const hasTnsBanner = !!tnsBannerLink;
/>
</div>
</div>
<div class='mb-5 mt-5 sm:mb-8 sm:mt-5'>
<h1 class='mb-0.5 text-2xl font-bold sm:mb-2 sm:text-3xl'>
<div class='mb-5 mt-5 sm:mb-12 sm:mt-12'>
<h1 class='mb-0.5 text-2xl font-bold sm:mb-3.5 sm:text-5xl'>
{title}
</h1>
<p class='text-balance text-sm text-gray-500 sm:text-base'>
<p class='text-balance text-sm text-gray-500 sm:text-lg'>
{description}
</p>
</div>
@ -128,12 +132,6 @@ const hasTnsBanner = !!tnsBannerLink;
isActive={activeTab === 'projects'}
badgeText={projectCount > 0 ? 'new' : 'soon'}
/>
<ScheduleButton
resourceId={roadmapId}
resourceType='roadmap'
resourceTitle={title}
client:load
/>
</div>
<TabLink

@ -23,20 +23,17 @@ export function ScheduleButton(props: ScheduleButtonProps) {
setIsModalOpen(false);
}}
roadmapId={resourceId}
roadmapTitle={resourceTitle}
/>
)}
<button
className={cn(
'group inline-flex items-center gap-1.5 border-b-2 border-b-transparent px-2 pb-2.5 text-sm font-normal text-gray-400 transition-colors hover:text-gray-700',
)}
className="inline-flex items-center justify-center gap-2 rounded-md bg-gray-200 px-3 py-1.5 text-xs font-medium hover:bg-gray-300 sm:text-sm"
onClick={() => {
setIsModalOpen(true);
}}
>
<Calendar className="h-4 w-4 flex-shrink-0" />
<span className="hidden sm:inline">Schedule</span>
<span className="hidden sm:inline">Schedule Learning Time</span>
</button>
</>
);

@ -45,13 +45,40 @@ END:VCALENDAR
}
type ScheduleEventModalProps = {
roadmapTitle: string;
roadmapId: string;
onClose: () => void;
};
export function ScheduleEventModal(props: ScheduleEventModalProps) {
const { onClose, roadmapId, roadmapTitle } = props;
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
@ -59,11 +86,11 @@ export function ScheduleEventModal(props: ScheduleEventModalProps) {
const [isLoading, setIsLoading] = useState(false);
const location = `https://roadmap.sh/${roadmapId}`;
const title = `Learn ${roadmapTitle}`;
const title = `Learn from ${roadmapTitle} Roadmap - roadmap.sh`;
const details = `
Learn ${roadmapTitle} on roadmap.sh
Learn from the ${roadmapTitle} roadmap on roadmap.sh
For more details, visit: https://roadmap.sh/${roadmapId}
Visit the roadmap at https://roadmap.sh/${roadmapId}
`.trim();
const handleDownloadICS = () => {
@ -146,68 +173,75 @@ For more details, visit: https://roadmap.sh/${roadmapId}
};
return (
<Modal onClose={onClose} wrapperClassName="max-w-lg">
<button
className="absolute right-4 top-4 text-gray-400 hover:text-black"
onClick={onClose}
>
<X className="h-4 w-4 stroke-[2.5]" />
</button>
<div className="flex flex-col items-center p-4 py-6 text-center">
{selectedCalendar && (
<CalendarSteps
title={stepDetails[selectedCalendar].title}
steps={stepDetails[selectedCalendar].steps}
onDownloadICS={handleDownloadICS}
onCancel={() => {
setSelectedCalendar(null);
}}
isLoading={isLoading}
/>
)}
{!selectedCalendar && (
<>
<h2 className="text-2xl font-medium tracking-wide">
Add to Your Calendar
</h2>
<p className="mt-1 text-balance text-sm text-gray-600">
Export the event to your calendar of choice. Future changes to
either the original or the copy will not be reflected in the
other.
</p>
<div className="mt-6 flex w-full flex-col gap-1">
<CalendarButton
icon={GoogleCalendarIcon}
label="Google Calendar"
onClick={handleGoogleCalendar}
isLoading={isLoading}
/>
<CalendarButton
icon={AppleCalendarIcon}
label="Apple Calendar"
onClick={() => {
setSelectedCalendar('apple');
}}
/>
<CalendarButton
icon={OutlookCalendarIcon}
label="Outlook Calendar"
onClick={() => {
setSelectedCalendar('outlook');
}}
/>
<CalendarButton
icon={FileIcon}
label="Download File (.ics)"
onClick={handleDownloadICS}
/>
</div>
</>
)}
<Modal
onClose={onClose}
bodyClassName="bg-transparent shadow-none"
wrapperClassName="h-auto max-w-lg"
overlayClassName="items-start md:items-center"
>
<div className="rounded-xl bg-white px-3">
<button
className="absolute right-4 top-4 text-gray-400 hover:text-black"
onClick={onClose}
>
<X className="h-4 w-4 stroke-[2.5]" />
</button>
<div className="flex flex-col items-center p-4 py-6 text-center">
{selectedCalendar && (
<CalendarSteps
title={stepDetails[selectedCalendar].title}
steps={stepDetails[selectedCalendar].steps}
onDownloadICS={handleDownloadICS}
onCancel={() => {
setSelectedCalendar(null);
}}
isLoading={isLoading}
/>
)}
{!selectedCalendar && (
<>
<h2 className="text-3xl font-semibold">Schedule Learning Time</h2>
<p className="mt-1.5 text-balance text-base text-gray-600">
Block some time on your calendar to stay consistent
</p>
<div className="mt-6 flex w-full flex-col gap-1">
<CalendarButton
icon={GoogleCalendarIcon}
label="Google Calendar"
onClick={handleGoogleCalendar}
isLoading={isLoading}
/>
<CalendarButton
icon={AppleCalendarIcon}
label="Apple Calendar"
onClick={() => {
setSelectedCalendar('apple');
}}
/>
<CalendarButton
icon={OutlookCalendarIcon}
label="Outlook Calendar"
onClick={() => {
setSelectedCalendar('outlook');
}}
/>
<div className="mx-auto my-4 text-base text-gray-600">
or download the iCS file and import it to your calendar app
</div>
<CalendarButton
icon={FileIcon}
label="Download File (.ics)"
onClick={handleDownloadICS}
/>
</div>
</>
)}
</div>
</div>
</Modal>
);
@ -233,7 +267,7 @@ function CalendarButton(props: CalendarButtonProps) {
onClick={onClick}
>
<div className="flex items-center gap-2">
<Icon className="h-4 w-4 shrink-0 stroke-[2.5]" />
<Icon className="h-5 w-5 shrink-0 stroke-[2.5]" />
{label}
</div>
<ChevronRight className="h-4 w-4 stroke-[2.5]" />
@ -254,16 +288,16 @@ export function CalendarSteps(props: CalendarStepsProps) {
return (
<div className="flex flex-col">
<h2 className="text-2xl font-medium tracking-wide">{title}</h2>
<h2 className="text-3xl font-semibold">{title}</h2>
<div className="mt-6 flex flex-col gap-2">
{steps.map((step, index) => (
<div key={index} className="flex items-center gap-3">
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-gray-200 text-gray-600">
<div key={index} className="flex items-baseline gap-3">
<div className="flex h-6 w-6 relative top-px text-sm shrink-0 items-center justify-center rounded-full bg-gray-200 text-gray-600">
{index + 1}
</div>
<div className="flex flex-col gap-1">
<p className="text-left text-sm text-gray-800">{step}</p>
<p className="text-left text-base text-gray-800">{step}</p>
</div>
</div>
))}
@ -271,14 +305,14 @@ export function CalendarSteps(props: CalendarStepsProps) {
<div className="mt-6 flex gap-2">
<button
className="flex-1 rounded-lg border border-gray-300 py-2 text-sm text-gray-600 hover:bg-gray-50 disabled:opacity-60 data-[loading='true']:cursor-progress"
className="flex-1 rounded-md border hover:bg-gray-100 border-gray-300 py-2 text-sm text-gray-600 disabled:opacity-60 data-[loading='true']:cursor-progress"
onClick={onCancel}
disabled={isLoading}
>
Cancel
Go back
</button>
<button
className="flex-1 rounded-lg bg-blue-600 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-60 data-[loading='true']:cursor-progress"
className="flex-1 rounded-md bg-black py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-60 data-[loading='true']:cursor-progress"
onClick={onDownloadICS}
disabled={isLoading}
data-loading={isLoading}

@ -33,8 +33,6 @@ export function ShareRoadmapButton(props: ShareRoadmapButtonProps) {
setIsDropdownOpen(false);
});
const embedHtml = `<iframe src="https://roadmap.sh/r/embed?id=${roadmapId}" width="100%" height="500px" frameBorder="0"\n></iframe>`;
return (
<div className="relative" ref={containerRef}>
{isEmbedModalOpen && (

@ -5,7 +5,7 @@ order: 5
renderer: 'editor'
briefTitle: 'AI and Data Scientist'
briefDescription: 'Step by step guide to becoming an AI and Data Scientist in 2024'
title: 'AI and Data Scientist Roadmap'
title: 'AI and Data Scientist'
description: 'Step by step guide to becoming an AI and Data Scientist in 2024'
hasTopics: true
isNew: false

@ -5,7 +5,7 @@ order: 4
renderer: 'editor'
briefTitle: 'AI Engineer'
briefDescription: 'Step by step guide to becoming an AI Engineer in 2024'
title: 'AI Engineer Roadmap'
title: 'AI Engineer'
description: 'Step by step guide to becoming an AI Engineer in 2024'
hasTopics: true
isNew: true

@ -4,7 +4,7 @@ pdfUrl: '/pdfs/roadmaps/datastructures-and-algorithms.pdf'
order: 18
briefTitle: 'Data Structures & Algorithms'
briefDescription: 'Step by step guide to learn Data Structures and Algorithms in 2024'
title: 'Data Structures & Algorithms Roadmap'
title: 'Data Structures & Algorithms'
description: 'Step by step guide to learn Data Structures and Algorithms in 2024'
hasTopics: true
isNew: false

Loading…
Cancel
Save