Skip to content

Commit 2a6c1bf

Browse files
feat: implement calendar scheduling (#7574)
* wip * feat: add calendar scheduling * fix: update names * UI Changes for calendar scheduling --------- Co-authored-by: Kamran Ahmed <[email protected]>
1 parent e4c863b commit 2a6c1bf

File tree

15 files changed

+614
-50
lines changed

15 files changed

+614
-50
lines changed

.astro/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"enabled": false
44
},
55
"_variables": {
6-
"lastUpdateCheck": 1729612578122
6+
"lastUpdateCheck": 1731065649795
77
}
88
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@nanostores/react": "^0.8.0",
3838
"@napi-rs/image": "^1.9.2",
3939
"@resvg/resvg-js": "^2.6.2",
40+
"@tanstack/react-query": "^5.59.16",
4041
"@types/react": "^18.3.11",
4142
"@types/react-dom": "^18.3.1",
4243
"astro": "^4.16.1",

pnpm-lock.yaml

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
import { cn } from '../../lib/classname.ts';
22
import { roadmapProgress, totalRoadmapNodes } from '../../stores/roadmap.ts';
33
import { useStore } from '@nanostores/react';
4+
import { Calendar, Info, X } from 'lucide-react';
5+
import { Tooltip } from '../Tooltip.tsx';
6+
import { useState } from 'react';
7+
import { ScheduleEventModal } from '../Schedule/ScheduleEventModal.tsx';
48

59
type ProgressNudgeProps = {
610
resourceType: 'roadmap' | 'best-practice';
711
resourceId: string;
812
};
913

1014
export function ProgressNudge(props: ProgressNudgeProps) {
15+
const { resourceId, resourceType } = props;
16+
17+
const [isNudgeHidden, setIsNudgeHidden] = useState(false);
18+
const [isScheduleModalOpen, setIsScheduleModalOpen] = useState(false);
19+
1120
const $totalRoadmapNodes = useStore(totalRoadmapNodes);
1221
const $roadmapProgress = useStore(roadmapProgress);
1322

@@ -17,52 +26,80 @@ export function ProgressNudge(props: ProgressNudgeProps) {
1726

1827
const hasProgress = done > 0;
1928

20-
if (!$totalRoadmapNodes) {
29+
if (!$totalRoadmapNodes || isNudgeHidden) {
2130
return null;
2231
}
2332

2433
return (
25-
<div
26-
className={
27-
'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'
28-
}
29-
>
30-
<span
31-
className={cn('block', {
32-
hidden: hasProgress,
33-
})}
34+
<>
35+
{isScheduleModalOpen && (
36+
<ScheduleEventModal
37+
onClose={() => {
38+
setIsScheduleModalOpen(false);
39+
}}
40+
roadmapId={resourceId}
41+
/>
42+
)}
43+
<div
44+
className={
45+
'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'
46+
}
3447
>
35-
<span className="mr-2 text-sm font-semibold uppercase text-yellow-400">
36-
Tip
37-
</span>
38-
<span className="text-sm text-gray-200">
39-
Right-click on a topic to mark it as done.{' '}
48+
<div
49+
className={
50+
'relative overflow-hidden rounded-full bg-stone-900 px-4 py-2 text-center text-white shadow-2xl'
51+
}
52+
>
53+
<span
54+
className={cn('flex items-center', {
55+
hidden: hasProgress,
56+
})}
57+
>
58+
<span className="mr-2 text-sm font-semibold uppercase text-yellow-400">
59+
Tip
60+
</span>
61+
<span className="text-sm text-gray-200">
62+
Right-click a topic to mark it as done &nbsp;
63+
</span>
64+
</span>
65+
<span
66+
className={cn('relative z-20 block text-sm', {
67+
hidden: !hasProgress,
68+
})}
69+
>
70+
<span className="relative -top-[0.45px] mr-2 text-xs font-medium uppercase text-yellow-400">
71+
Progress
72+
</span>
73+
<span>{done > $totalRoadmapNodes ? $totalRoadmapNodes : done}</span>{' '}
74+
of <span>{$totalRoadmapNodes}</span> Done
75+
</span>
76+
77+
<span
78+
className="absolute bottom-0 left-0 top-0 z-10 bg-stone-700"
79+
style={{
80+
width: `${(done / $totalRoadmapNodes) * 100}%`,
81+
}}
82+
></span>
83+
</div>
84+
{resourceType === 'roadmap' && (
4085
<button
41-
data-popup="progress-help"
42-
className="cursor-pointer font-semibold text-yellow-500 underline"
86+
onClick={() => {
87+
setIsScheduleModalOpen(true);
88+
}}
89+
className="group relative flex items-center gap-2 rounded-full bg-stone-900 px-3 text-sm text-yellow-400"
4390
>
44-
Learn more.
91+
<Calendar className="h-4 w-4 flex-shrink-0" strokeWidth={2.5} />
4592
</button>
46-
</span>
47-
</span>
48-
<span
49-
className={cn('relative z-20 block text-sm', {
50-
hidden: !hasProgress,
51-
})}
52-
>
53-
<span className="relative -top-[0.45px] mr-2 text-xs font-medium uppercase text-yellow-400">
54-
Progress
55-
</span>
56-
<span>{done > $totalRoadmapNodes ? $totalRoadmapNodes : done}</span> of{' '}
57-
<span>{$totalRoadmapNodes}</span> Done
58-
</span>
59-
60-
<span
61-
className="absolute bottom-0 left-0 top-0 z-10 bg-stone-700"
62-
style={{
63-
width: `${(done / $totalRoadmapNodes) * 100}%`,
64-
}}
65-
></span>
66-
</div>
93+
)}
94+
<button
95+
onClick={() => {
96+
setIsNudgeHidden(true);
97+
}}
98+
className="group relative flex items-center gap-2 rounded-full bg-stone-900 px-3 text-sm text-yellow-400"
99+
>
100+
<X className="h-4 w-4 flex-shrink-0" strokeWidth={2.5} />
101+
</button>
102+
</div>
103+
</>
67104
);
68105
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { SVGProps } from 'react';
2+
3+
export function AppleCalendarIcon(props: SVGProps<SVGSVGElement>) {
4+
return (
5+
<svg
6+
width="1736"
7+
height="1693"
8+
viewBox="0 0 1736 1693"
9+
fill="none"
10+
xmlns="http://www.w3.org/2000/svg"
11+
{...props}
12+
>
13+
<rect width="1736" height="1693" fill="#ECEFF1" />
14+
<rect x="1" width="1734" height="526" fill="#FF3D00" />
15+
<path
16+
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"
17+
fill="white"
18+
/>
19+
<path
20+
d="M976.776 283.419H890.632V356.061H992.617V401.947H835.303V128H992.206V174.069H890.632V238.812H976.776V283.419Z"
21+
fill="white"
22+
/>
23+
<path
24+
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"
25+
fill="white"
26+
/>
27+
<path
28+
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"
29+
fill="#424242"
30+
/>
31+
<path
32+
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"
33+
fill="#424242"
34+
/>
35+
</svg>
36+
);
37+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { SVGProps } from 'react';
2+
3+
export function FileIcon(props: SVGProps<SVGSVGElement>) {
4+
return (
5+
<svg
6+
xmlns="http://www.w3.org/2000/svg"
7+
x="0px"
8+
y="0px"
9+
width={100}
10+
height={100}
11+
viewBox="0 0 48 48"
12+
{...props}
13+
>
14+
<path fill="#90CAF9" d="M40 45L8 45 8 3 30 3 40 13z" />
15+
<path fill="#E1F5FE" d="M38.5 14L29 14 29 4.5z" />
16+
<path
17+
fill="#1976D2"
18+
d="M16 21H33V23H16zM16 25H29V27H16zM16 29H33V31H16zM16 33H29V35H16z"
19+
/>
20+
</svg>
21+
);
22+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { SVGProps } from 'react';
2+
3+
export function GoogleCalendarIcon(props: SVGProps<SVGSVGElement>) {
4+
return (
5+
<svg
6+
xmlns="http://www.w3.org/2000/svg"
7+
x="0px"
8+
y="0px"
9+
width="100"
10+
height="100"
11+
viewBox="0 0 48 48"
12+
{...props}
13+
>
14+
<rect width="22" height="22" x="13" y="13" fill="#fff"></rect>
15+
<polygon
16+
fill="#1e88e5"
17+
points="25.68,20.92 26.688,22.36 28.272,21.208 28.272,29.56 30,29.56 30,18.616 28.56,18.616"
18+
></polygon>
19+
<path
20+
fill="#1e88e5"
21+
d="M22.943,23.745c0.625-0.574,1.013-1.37,1.013-2.249c0-1.747-1.533-3.168-3.417-3.168 c-1.602,0-2.972,1.009-3.33,2.453l1.657,0.421c0.165-0.664,0.868-1.146,1.673-1.146c0.942,0,1.709,0.646,1.709,1.44 c0,0.794-0.767,1.44-1.709,1.44h-0.997v1.728h0.997c1.081,0,1.993,0.751,1.993,1.64c0,0.904-0.866,1.64-1.931,1.64 c-0.962,0-1.784-0.61-1.914-1.418L17,26.802c0.262,1.636,1.81,2.87,3.6,2.87c2.007,0,3.64-1.511,3.64-3.368 C24.24,25.281,23.736,24.363,22.943,23.745z"
22+
></path>
23+
<polygon
24+
fill="#fbc02d"
25+
points="34,42 14,42 13,38 14,34 34,34 35,38"
26+
></polygon>
27+
<polygon
28+
fill="#4caf50"
29+
points="38,35 42,34 42,14 38,13 34,14 34,34"
30+
></polygon>
31+
<path
32+
fill="#1e88e5"
33+
d="M34,14l1-4l-1-4H9C7.343,6,6,7.343,6,9v25l4,1l4-1V14H34z"
34+
></path>
35+
<polygon fill="#e53935" points="34,34 34,42 42,34"></polygon>
36+
<path fill="#1565c0" d="M39,6h-5v8h8V9C42,7.343,40.657,6,39,6z"></path>
37+
<path fill="#1565c0" d="M9,42h5v-8H6v5C6,40.657,7.343,42,9,42z"></path>
38+
</svg>
39+
);
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { SVGProps } from 'react';
2+
3+
export function OutlookCalendarIcon(props: SVGProps<SVGSVGElement>) {
4+
return (
5+
<svg
6+
xmlns="http://www.w3.org/2000/svg"
7+
x="0px"
8+
y="0px"
9+
width={100}
10+
height={100}
11+
viewBox="0 0 48 48"
12+
{...props}
13+
>
14+
<path
15+
fill="#1976d2"
16+
d="M28,13h14.533C43.343,13,44,13.657,44,14.467v19.066C44,34.343,43.343,35,42.533,35H28V13z"
17+
/>
18+
<rect width={14} height="15.542" x={28} y="17.958" fill="#fff" />
19+
<polygon fill="#1976d2" points="27,44 4,39.5 4,8.5 27,4" />
20+
<path
21+
fill="#fff"
22+
d="M15.25,16.5c-3.176,0-5.75,3.358-5.75,7.5s2.574,7.5,5.75,7.5S21,28.142,21,24 S18.426,16.5,15.25,16.5z M15,28.5c-1.657,0-3-2.015-3-4.5s1.343-4.5,3-4.5s3,2.015,3,4.5S16.657,28.5,15,28.5z"
23+
/>
24+
<rect width="2.7" height="2.9" x="28.047" y="29.737" fill="#1976d2" />
25+
<rect width="2.7" height="2.9" x="31.448" y="29.737" fill="#1976d2" />
26+
<rect width="2.7" height="2.9" x="34.849" y="29.737" fill="#1976d2" />
27+
<rect width="2.7" height="2.9" x="28.047" y="26.159" fill="#1976d2" />
28+
<rect width="2.7" height="2.9" x="31.448" y="26.159" fill="#1976d2" />
29+
<rect width="2.7" height="2.9" x="34.849" y="26.159" fill="#1976d2" />
30+
<rect width="2.7" height="2.9" x="38.25" y="26.159" fill="#1976d2" />
31+
<rect width="2.7" height="2.9" x="28.047" y="22.706" fill="#1976d2" />
32+
<rect width="2.7" height="2.9" x="31.448" y="22.706" fill="#1976d2" />
33+
<rect width="2.7" height="2.9" x="34.849" y="22.706" fill="#1976d2" />
34+
<rect width="2.7" height="2.9" x="38.25" y="22.706" fill="#1976d2" />
35+
<rect width="2.7" height="2.9" x="31.448" y="19.112" fill="#1976d2" />
36+
<rect width="2.7" height="2.9" x="34.849" y="19.112" fill="#1976d2" />
37+
<rect width="2.7" height="2.9" x="38.25" y="19.112" fill="#1976d2" />
38+
</svg>
39+
);
40+
}

src/components/RoadmapHeader.astro

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from 'lucide-react';
88
import { TabLink } from './TabLink';
99
import LoginPopup from './AuthenticationFlow/LoginPopup.astro';
10+
import { ScheduleButton } from './Schedule/ScheduleButton';
1011
import ProgressHelpPopup from './ProgressHelpPopup.astro';
1112
import { MarkFavorite } from './FeaturedItems/MarkFavorite';
1213
import { type RoadmapFrontmatter } from '../lib/roadmap';
@@ -45,8 +46,6 @@ const roadmapTitle =
4546
roadmapId === 'devops'
4647
? 'DevOps'
4748
: `${roadmapId.charAt(0).toUpperCase()}${roadmapId.slice(1)}`;
48-
49-
const hasTnsBanner = !!tnsBannerLink;
5049
---
5150

5251
<LoginPopup />
@@ -95,6 +94,12 @@ const hasTnsBanner = !!tnsBannerLink;
9594
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'
9695
client:only='react'
9796
/>
97+
<ScheduleButton
98+
resourceId={roadmapId}
99+
resourceType='roadmap'
100+
resourceTitle={title}
101+
client:load
102+
/>
98103
<DownloadRoadmapButton roadmapId={roadmapId} client:idle />
99104
<ShareRoadmapButton
100105
description={description}
@@ -103,11 +108,11 @@ const hasTnsBanner = !!tnsBannerLink;
103108
/>
104109
</div>
105110
</div>
106-
<div class='mb-5 mt-5 sm:mb-8 sm:mt-5'>
107-
<h1 class='mb-0.5 text-2xl font-bold sm:mb-2 sm:text-3xl'>
111+
<div class='mb-5 mt-5 sm:mb-12 sm:mt-12'>
112+
<h1 class='mb-0.5 text-2xl font-bold sm:mb-3.5 sm:text-5xl'>
108113
{title}
109114
</h1>
110-
<p class='text-balance text-sm text-gray-500 sm:text-base'>
115+
<p class='text-balance text-sm text-gray-500 sm:text-lg'>
111116
{description}
112117
</p>
113118
</div>
@@ -135,6 +140,7 @@ const hasTnsBanner = !!tnsBannerLink;
135140
text='Suggest Changes'
136141
isExternal={true}
137142
hideTextOnMobile={true}
143+
isActive={false}
138144
/>
139145
</div>
140146
</div>

0 commit comments

Comments
 (0)