feat: share icon event

feat/new-user
Arik Chakma 2 days ago
parent 68597d58d2
commit 6decf1eb70
  1. 20
      src/components/ReactIcons/HackerNewsIcon.tsx
  2. 20
      src/components/ReactIcons/RedditIcon.tsx
  3. 2
      src/components/ReactIcons/TwitterIcon.tsx
  4. 34
      src/components/ShareIcons/ShareIcons.astro
  5. 99
      src/components/ShareIcons/ShareIcons.tsx
  6. 32
      src/components/ShareIcons/sharer.js
  7. 51
      src/pages/[roadmapId]/courses.astro
  8. 3
      src/pages/[roadmapId]/index.astro
  9. 3
      src/pages/best-practices/[bestPracticeId]/index.astro

@ -0,0 +1,20 @@
import { cn } from '../../lib/classname';
interface HackerNewsIconProps {
className?: string;
}
export function HackerNewsIcon(props: HackerNewsIconProps) {
const { className } = props;
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
fill="currentColor"
className={cn('h-[26px] w-[26px]', className)}
>
<path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zM21.2 229.2H21c.1-.1.2-.3.3-.4 0 .1 0 .3-.1.4zm218 53.9V384h-31.4V281.3L128 128h37.3c52.5 98.3 49.2 101.2 59.3 125.6 12.3-27 5.8-24.4 60.6-125.6H320l-80.8 155.1z" />
</svg>
);
}

@ -0,0 +1,20 @@
import { cn } from '../../lib/classname';
interface RedditIconProps {
className?: string;
}
export function RedditIcon(props: RedditIconProps) {
const { className } = props;
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
fill="currentColor"
className={cn('h-[26px] w-[26px]', className)}
>
<path d="M283.2 345.5c2.7 2.7 2.7 6.8 0 9.2-24.5 24.5-93.8 24.6-118.4 0-2.7-2.4-2.7-6.5 0-9.2 2.4-2.4 6.5-2.4 8.9 0 18.7 19.2 81 19.6 100.5 0 2.4-2.3 6.6-2.3 9 0zm-91.3-53.8c0-14.9-11.9-26.8-26.5-26.8a26.67 26.67 0 0 0-26.8 26.8c0 14.6 11.9 26.5 26.8 26.5 14.6 0 26.5-11.9 26.5-26.5zm90.7-26.8c-14.6 0-26.5 11.9-26.5 26.8 0 14.6 11.9 26.5 26.5 26.5 14.9 0 26.8-11.9 26.8-26.5a26.67 26.67 0 0 0-26.8-26.8zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-99.7 140.6c-10.1 0-19 4.2-25.6 10.7-24.1-16.7-56.5-27.4-92.5-28.6l18.7-84.2 59.5 13.4c0 14.6 11.9 26.5 26.5 26.5 14.9 0 26.8-12.2 26.8-26.8s-11.9-26.8-26.8-26.8c-10.4 0-19.3 6.2-23.8 14.9l-65.7-14.6c-3.3-.9-6.5 1.5-7.4 4.8l-20.5 92.8c-35.7 1.5-67.8 12.2-91.9 28.9-6.5-6.8-15.8-11-25.9-11-37.5 0-49.8 50.4-15.5 67.5-1.2 5.4-1.8 11-1.8 16.7 0 56.5 63.7 102.3 141.9 102.3 78.5 0 142.2-45.8 142.2-102.3 0-5.7-.6-11.6-2.1-17 33.6-17.2 21.2-67.2-16.1-67.2z" />
</svg>
);
}

@ -18,7 +18,7 @@ export function TwitterIcon(props: TwitterIconProps) {
<rect width="23" height="23" rx="3" fill={boxColor} /> <rect width="23" height="23" rx="3" fill={boxColor} />
<path <path
d="M12.9285 10.3522L18.5135 4H17.1905L12.339 9.5144L8.467 4H4L9.8565 12.3395L4 19H5.323L10.443 13.1754L14.533 19H19M5.8005 4.97619H7.833L17.1895 18.0718H15.1565" d="M12.9285 10.3522L18.5135 4H17.1905L12.339 9.5144L8.467 4H4L9.8565 12.3395L4 19H5.323L10.443 13.1754L14.533 19H19M5.8005 4.97619H7.833L17.1895 18.0718H15.1565"
fill='currentColor' fill="currentColor"
/> />
</svg> </svg>
); );

@ -1,34 +0,0 @@
---
import Icon from '../AstroIcon.astro';
export interface Props {
pageUrl: string;
description: string;
}
const { pageUrl, description } = Astro.props;
const twitterUrl = `https://twitter.com/intent/tweet?text=${description}&url=${pageUrl}`;
const fbUrl = `https://www.facebook.com/sharer/sharer.php?quote=${description}&u=${pageUrl}`;
const hnUrl = `https://news.ycombinator.com/submitlink?t=${description}&u=${pageUrl}`;
const redditUrl = `https://www.reddit.com/submit?title=${description}&url=${pageUrl}`;
---
<div class='absolute left-[-18px] top-[110px] h-full hidden' id='page-share-icons'>
<div class='flex sticky top-[100px] flex-col gap-1.5 items-center'>
<a href={twitterUrl} target='_blank' class='text-gray-500 hover:text-gray-700 mb-0.5'>
<Icon icon='twitter' />
</a>
<a href={fbUrl} target='_blank' class='text-gray-500 hover:text-gray-700'>
<Icon icon='facebook' />
</a>
<a href={hnUrl} target='_blank' class='text-gray-500 hover:text-gray-700'>
<Icon icon='hackernews' />
</a>
<a href={redditUrl} target='_blank' class='text-gray-500 hover:text-gray-700'>
<Icon icon='reddit' />
</a>
</div>
</div>
<script src='./sharer.js'></script>

@ -0,0 +1,99 @@
import { useEffect, useRef } from 'react';
import { cn } from '../../lib/classname';
import { FacebookIcon } from '../ReactIcons/FacebookIcon';
import { HackerNewsIcon } from '../ReactIcons/HackerNewsIcon';
import { RedditIcon } from '../ReactIcons/RedditIcon';
import { TwitterIcon } from '../ReactIcons/TwitterIcon';
type ShareIconsProps = {
pageUrl: string;
description: string;
};
export function ShareIcons(props: ShareIconsProps) {
const { pageUrl, description } = props;
const shareIconsRef = useRef<HTMLDivElement>(null);
const twitterUrl = `https://twitter.com/intent/tweet?text=${description}&url=${pageUrl}`;
const fbUrl = `https://www.facebook.com/sharer/sharer.php?quote=${description}&u=${pageUrl}`;
const hnUrl = `https://news.ycombinator.com/submitlink?t=${description}&u=${pageUrl}`;
const redditUrl = `https://www.reddit.com/submit?title=${description}&url=${pageUrl}`;
const icons = [
{
url: twitterUrl,
icon: (
<TwitterIcon
className="size-[24px] [&>path]:fill-[#E5E5E5]"
boxColor="currentColor"
/>
),
},
{
url: fbUrl,
icon: <FacebookIcon className="size-[26px]" />,
},
{
url: hnUrl,
icon: <HackerNewsIcon className="size-[26px]" />,
},
{
url: redditUrl,
icon: <RedditIcon className="size-[26px]" />,
},
];
useEffect(() => {
const shareIcons = shareIconsRef.current;
if (!shareIcons) {
return;
}
const onScroll = () => {
if (window.scrollY < 100 || window.innerWidth < 1050) {
shareIcons.classList.add('hidden');
return null;
}
shareIcons.classList.remove('hidden');
};
onScroll();
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
};
}, []);
return (
<div
className="absolute left-[-18px] top-[110px] hidden h-full"
ref={shareIconsRef}
>
<div className="sticky top-[100px] flex flex-col items-center gap-1.5">
{icons.map((icon, index) => (
<a
key={index}
href={icon.url}
target="_blank"
className={cn(
'text-gray-500 hover:text-gray-700',
index === 0 && 'mt-0.5',
)}
onClick={() => {
window.fireEvent({
category: 'RoadmapShareLink',
action: 'Roadmap Share Link Clicked',
label: icon.url,
});
}}
>
{icon.icon}
</a>
))}
</div>
</div>
);
}

@ -1,32 +0,0 @@
export class Sharer {
constructor() {
this.init = this.init.bind(this);
this.onScroll = this.onScroll.bind(this);
this.shareIconsId = 'page-share-icons';
}
get shareIconsEl() {
return document.getElementById(this.shareIconsId);
}
onScroll() {
if (window.scrollY < 100 || window.innerWidth < 1050) {
this.shareIconsEl.classList.add('hidden');
return null;
}
this.shareIconsEl.classList.remove('hidden');
}
init() {
if (!this.shareIconsEl) {
return;
}
window.addEventListener('scroll', this.onScroll, { passive: true });
}
}
const sharer = new Sharer();
sharer.init();

@ -1,25 +1,8 @@
--- ---
import { EditorRoadmap } from '../../components/EditorRoadmap/EditorRoadmap';
import FAQs, { type FAQType } from '../../components/FAQs/FAQs.astro';
import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro';
import RelatedRoadmaps from '../../components/RelatedRoadmaps.astro';
import RoadmapHeader from '../../components/RoadmapHeader.astro'; import RoadmapHeader from '../../components/RoadmapHeader.astro';
import { FolderKanbanIcon } from 'lucide-react';
import { EmptyProjects } from '../../components/Projects/EmptyProjects';
import ShareIcons from '../../components/ShareIcons/ShareIcons.astro';
import { UserProgressModal } from '../../components/UserProgress/UserProgressModal';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { getProjectsByRoadmapId } from '../../lib/project';
import {
generateArticleSchema,
generateFAQSchema,
} from '../../lib/jsonld-schema';
import { getOpenGraphImageUrl } from '../../lib/open-graph'; import { getOpenGraphImageUrl } from '../../lib/open-graph';
import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap'; import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
import RoadmapNote from '../../components/RoadmapNote.astro';
import { RoadmapTitleQuestion } from '../../components/RoadmapTitleQuestion';
import ResourceProgressStats from '../../components/ResourceProgressStats.astro';
import AstroIcon from '../../components/AstroIcon.astro';
import CourseStep from '../../components/courses/CourseStep.astro'; import CourseStep from '../../components/courses/CourseStep.astro';
import Milestone from '../../components/courses/Milestone.astro'; import Milestone from '../../components/courses/Milestone.astro';
@ -95,24 +78,42 @@ const seoDescription = `Seeking ${nounTitle.toLowerCase()} courses to enhance yo
<div class='relative my-3 rounded-lg border bg-white px-12 py-8'> <div class='relative my-3 rounded-lg border bg-white px-12 py-8'>
<span class='absolute inset-y-0 left-[26.3px] w-[1px] bg-black'></span> <span class='absolute inset-y-0 left-[26.3px] w-[1px] bg-black'></span>
<div class='mb-8 flex flex-col gap-4 text-sm text-gray-500 leading-normal'> <div
class='mb-8 flex flex-col gap-4 text-sm leading-normal text-gray-500'
>
<p> <p>
Frontend development is a vast field with a lot of tools and Frontend development is a vast field with a lot of tools and
technologies. We have the <a class="font-medium underline underline-offset-2 text-black" href="/frontend">frontend roadmap</a> technologies. We have the <a
which is filled with a lot of <span class="font-medium text-black">free and good</span> resources to help you learn. But sometimes it helps to have a minimalistic list of courses class='font-medium text-black underline underline-offset-2'
and project recommendations to help you get started. href='/frontend'>frontend roadmap</a
>
which is filled with a lot of <span class='font-medium text-black'
>free and good</span
> resources to help you learn. But sometimes it helps to have a minimalistic
list of courses and project recommendations to help you get started.
</p> </p>
<p class="bg-yellow-100 text-yellow-900 rounded-md p-2"> <p class='rounded-md bg-yellow-100 p-2 text-yellow-900'>
Below are some of the best courses (paid) and projects to help you learn frontend development. These are handpicked and are a great way to get started. Below are some of the best courses (paid) and projects to help you
learn frontend development. These are handpicked and are a great way
to get started.
</p> </p>
<p> <p>
Please note that these are paid courses curated from external platforms. We earn a small commission if you purchase the course using the links below. This helps us maintain the website and keep it free for everyone. Please note that these are paid courses curated from external
platforms. We earn a small commission if you purchase the course
using the links below. This helps us maintain the website and keep
it free for everyone.
</p> </p>
<p> <p>
If you are looking for free resources, you can check out the <a class="font-medium underline underline-offset-2 text-black" href="/frontend">frontend roadmap</a>. Also, we have a <a class="font-medium underline underline-offset-2 text-black" href="/frontend/projects">list of projects</a> that you can work on to enhance your skills. If you are looking for free resources, you can check out the <a
class='font-medium text-black underline underline-offset-2'
href='/frontend'>frontend roadmap</a
>. Also, we have a <a
class='font-medium text-black underline underline-offset-2'
href='/frontend/projects'>list of projects</a
> that you can work on to enhance your skills.
</p> </p>
</div> </div>

@ -4,7 +4,7 @@ import FAQs, { type FAQType } from '../../components/FAQs/FAQs.astro';
import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro'; import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro';
import RelatedRoadmaps from '../../components/RelatedRoadmaps.astro'; import RelatedRoadmaps from '../../components/RelatedRoadmaps.astro';
import RoadmapHeader from '../../components/RoadmapHeader.astro'; import RoadmapHeader from '../../components/RoadmapHeader.astro';
import ShareIcons from '../../components/ShareIcons/ShareIcons.astro'; import { ShareIcons } from '../../components/ShareIcons/ShareIcons';
import { TopicDetail } from '../../components/TopicDetail/TopicDetail'; import { TopicDetail } from '../../components/TopicDetail/TopicDetail';
import { UserProgressModal } from '../../components/UserProgress/UserProgressModal'; import { UserProgressModal } from '../../components/UserProgress/UserProgressModal';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
@ -136,6 +136,7 @@ const projects = await getProjectsByRoadmapId(roadmapId);
<ShareIcons <ShareIcons
description={roadmapData.briefDescription} description={roadmapData.briefDescription}
pageUrl={`https://roadmap.sh/${roadmapId}`} pageUrl={`https://roadmap.sh/${roadmapId}`}
client:load
/> />
{ {

@ -2,7 +2,7 @@
import BestPracticeHeader from '../../../components/BestPracticeHeader.astro'; import BestPracticeHeader from '../../../components/BestPracticeHeader.astro';
import FrameRenderer from '../../../components/FrameRenderer/FrameRenderer.astro'; import FrameRenderer from '../../../components/FrameRenderer/FrameRenderer.astro';
import MarkdownFile from '../../../components/MarkdownFile.astro'; import MarkdownFile from '../../../components/MarkdownFile.astro';
import ShareIcons from '../../../components/ShareIcons/ShareIcons.astro'; import { ShareIcons } from '../../../components/ShareIcons/ShareIcons';
import { TopicDetail } from '../../../components/TopicDetail/TopicDetail'; import { TopicDetail } from '../../../components/TopicDetail/TopicDetail';
import UpcomingForm from '../../../components/UpcomingForm.astro'; import UpcomingForm from '../../../components/UpcomingForm.astro';
import BaseLayout from '../../../layouts/BaseLayout.astro'; import BaseLayout from '../../../layouts/BaseLayout.astro';
@ -96,6 +96,7 @@ const ogImageUrl = getOpenGraphImageUrl({
<ShareIcons <ShareIcons
description={bestPracticeData.briefDescription} description={bestPracticeData.briefDescription}
pageUrl={`https://roadmap.sh/best-practices/${bestPracticeId}`} pageUrl={`https://roadmap.sh/best-practices/${bestPracticeId}`}
client:load
/> />
<TopicDetail <TopicDetail

Loading…
Cancel
Save