mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-19 15:43:49 +02:00
Implement Social Share options (#4569)
* Implement social share options * Minor fix
This commit is contained in:
@@ -15,6 +15,7 @@ import { useToast } from '../../hooks/use-toast';
|
|||||||
export type TeamResourceConfig = {
|
export type TeamResourceConfig = {
|
||||||
isCustomResource: boolean;
|
isCustomResource: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
|
description?: string;
|
||||||
visibility?: AllowedRoadmapVisibility;
|
visibility?: AllowedRoadmapVisibility;
|
||||||
resourceId: string;
|
resourceId: string;
|
||||||
resourceType: string;
|
resourceType: string;
|
||||||
|
@@ -60,6 +60,7 @@ export function PersonalRoadmapList(props: PersonalRoadmapListType) {
|
|||||||
|
|
||||||
const shareSettingsModal = selectedRoadmap && (
|
const shareSettingsModal = selectedRoadmap && (
|
||||||
<ShareOptionsModal
|
<ShareOptionsModal
|
||||||
|
description={selectedRoadmap.description}
|
||||||
visibility={selectedRoadmap.visibility}
|
visibility={selectedRoadmap.visibility}
|
||||||
sharedFriendIds={selectedRoadmap.sharedFriendIds}
|
sharedFriendIds={selectedRoadmap.sharedFriendIds}
|
||||||
sharedTeamMemberIds={selectedRoadmap.sharedTeamMemberIds}
|
sharedTeamMemberIds={selectedRoadmap.sharedTeamMemberIds}
|
||||||
|
@@ -24,6 +24,7 @@ export function ResourceProgressStats(props: ResourceProgressStatsProps) {
|
|||||||
<>
|
<>
|
||||||
{isSharing && $canManageCurrentRoadmap && $currentRoadmap && (
|
{isSharing && $canManageCurrentRoadmap && $currentRoadmap && (
|
||||||
<ShareOptionsModal
|
<ShareOptionsModal
|
||||||
|
description={$currentRoadmap?.description}
|
||||||
visibility={$currentRoadmap?.visibility}
|
visibility={$currentRoadmap?.visibility}
|
||||||
teamId={$currentRoadmap?.teamId}
|
teamId={$currentRoadmap?.teamId}
|
||||||
roadmapId={$currentRoadmap?._id!}
|
roadmapId={$currentRoadmap?._id!}
|
||||||
|
@@ -121,6 +121,7 @@ export function RoadmapHeader(props: RoadmapHeaderProps) {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isSharing && $currentRoadmap && (
|
{isSharing && $currentRoadmap && (
|
||||||
<ShareOptionsModal
|
<ShareOptionsModal
|
||||||
|
description={$currentRoadmap?.description}
|
||||||
visibility={$currentRoadmap?.visibility}
|
visibility={$currentRoadmap?.visibility}
|
||||||
teamId={$currentRoadmap?.teamId}
|
teamId={$currentRoadmap?.teamId}
|
||||||
roadmapId={$currentRoadmap?._id!}
|
roadmapId={$currentRoadmap?._id!}
|
||||||
@@ -143,7 +144,7 @@ export function RoadmapHeader(props: RoadmapHeaderProps) {
|
|||||||
$currentRoadmap?._id
|
$currentRoadmap?._id
|
||||||
}`}
|
}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="inline-flex items-center justify-center rounded-md bg-white py-1.5 pl-2 pr-2 text-xs font-medium text-black hover:bg-gray-300 hover:border-gray-300 sm:px-3 sm:text-sm border border-gray-300"
|
className="inline-flex items-center justify-center rounded-md border border-gray-300 bg-white py-1.5 pl-2 pr-2 text-xs font-medium text-black hover:border-gray-300 hover:bg-gray-300 sm:px-3 sm:text-sm"
|
||||||
>
|
>
|
||||||
<Shapes className="mr-1.5 h-4 w-4 stroke-[2.5]" />
|
<Shapes className="mr-1.5 h-4 w-4 stroke-[2.5]" />
|
||||||
<span className="hidden sm:inline-block">Edit Roadmap</span>
|
<span className="hidden sm:inline-block">Edit Roadmap</span>
|
||||||
@@ -151,7 +152,7 @@ export function RoadmapHeader(props: RoadmapHeaderProps) {
|
|||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsSharing(true)}
|
onClick={() => setIsSharing(true)}
|
||||||
className="inline-flex items-center justify-center rounded-md bg-white py-1.5 pl-2 pr-2 text-xs font-medium text-black hover:bg-gray-300 hover:border-gray-300 sm:px-3 sm:text-sm border border-gray-300"
|
className="inline-flex items-center justify-center rounded-md border border-gray-300 bg-white py-1.5 pl-2 pr-2 text-xs font-medium text-black hover:border-gray-300 hover:bg-gray-300 sm:px-3 sm:text-sm"
|
||||||
>
|
>
|
||||||
<Lock className="mr-1.5 h-4 w-4 stroke-[2.5]" />
|
<Lock className="mr-1.5 h-4 w-4 stroke-[2.5]" />
|
||||||
Sharing
|
Sharing
|
||||||
|
@@ -1,66 +0,0 @@
|
|||||||
import { CheckCircle, Copy } from 'lucide-react';
|
|
||||||
import { useCopyText } from '../../hooks/use-copy-text';
|
|
||||||
import { cn } from '../../lib/classname';
|
|
||||||
|
|
||||||
type CopyRoadmapLinkProps = {
|
|
||||||
roadmapId: string;
|
|
||||||
onClose: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function CopyRoadmapLink(props: CopyRoadmapLinkProps) {
|
|
||||||
const { roadmapId, onClose } = props;
|
|
||||||
|
|
||||||
const baseUrl = import.meta.env.DEV
|
|
||||||
? 'http://localhost:3000'
|
|
||||||
: 'https://roadmap.sh';
|
|
||||||
const shareLink = `${baseUrl}/r?id=${roadmapId}`;
|
|
||||||
|
|
||||||
const { copyText, isCopied } = useCopyText();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex grow flex-col justify-center">
|
|
||||||
<div className="mt-5 flex grow flex-col items-center justify-center gap-1.5">
|
|
||||||
<CheckCircle className="h-14 w-14 text-green-500" />
|
|
||||||
<h3 className="text-xl font-medium">Sharing Settings Updated</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="mt-6 w-full rounded-md border bg-gray-50 p-2 px-2.5 text-gray-700 focus:outline-none"
|
|
||||||
value={shareLink}
|
|
||||||
readOnly
|
|
||||||
onClick={(e) => {
|
|
||||||
e.currentTarget.select();
|
|
||||||
copyText(shareLink);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<p className="mt-1 text-sm text-gray-400">
|
|
||||||
You can share the above link with anyone who has access
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mt-4 flex flex-col items-center justify-end gap-2">
|
|
||||||
<button
|
|
||||||
className={cn(
|
|
||||||
'flex w-full items-center justify-center gap-1.5 rounded bg-black px-4 py-2.5 text-sm font-medium text-white hover:opacity-80',
|
|
||||||
isCopied && 'bg-green-300 text-green-800'
|
|
||||||
)}
|
|
||||||
disabled={isCopied}
|
|
||||||
onClick={() => {
|
|
||||||
copyText(shareLink);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Copy className="h-3.5 w-3.5 stroke-[2.5]" />
|
|
||||||
{isCopied ? 'Copied' : 'Copy'}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={cn(
|
|
||||||
'flex w-full items-center justify-center gap-1.5 rounded border border-black px-4 py-2 text-sm font-medium hover:bg-gray-100'
|
|
||||||
)}
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -7,7 +7,7 @@ import {
|
|||||||
ShareTeamMemberList,
|
ShareTeamMemberList,
|
||||||
type TeamMemberList,
|
type TeamMemberList,
|
||||||
} from './ShareTeamMemberList';
|
} from './ShareTeamMemberList';
|
||||||
import { CopyRoadmapLink } from './CopyRoadmapLink';
|
import { ShareSuccess } from './ShareSuccess';
|
||||||
import { useToast } from '../../hooks/use-toast';
|
import { useToast } from '../../hooks/use-toast';
|
||||||
import type { AllowedRoadmapVisibility } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
|
import type { AllowedRoadmapVisibility } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
|
||||||
import { httpPatch } from '../../lib/http';
|
import { httpPatch } from '../../lib/http';
|
||||||
@@ -28,6 +28,7 @@ type ShareOptionsModalProps = {
|
|||||||
sharedTeamMemberIds?: string[];
|
sharedTeamMemberIds?: string[];
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
roadmapId?: string;
|
roadmapId?: string;
|
||||||
|
description?: string;
|
||||||
|
|
||||||
onShareSettingsUpdate: OnShareSettingsUpdate;
|
onShareSettingsUpdate: OnShareSettingsUpdate;
|
||||||
};
|
};
|
||||||
@@ -41,6 +42,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
|
|||||||
sharedFriendIds: defaultSharedFriendIds = [],
|
sharedFriendIds: defaultSharedFriendIds = [],
|
||||||
teamId,
|
teamId,
|
||||||
onShareSettingsUpdate,
|
onShareSettingsUpdate,
|
||||||
|
description,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@@ -156,7 +158,12 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
|
|||||||
wrapperClassName="max-w-lg"
|
wrapperClassName="max-w-lg"
|
||||||
bodyClassName="p-4 flex flex-col"
|
bodyClassName="p-4 flex flex-col"
|
||||||
>
|
>
|
||||||
<CopyRoadmapLink roadmapId={roadmapId!} onClose={onClose} />
|
<ShareSuccess
|
||||||
|
visibility={visibility}
|
||||||
|
roadmapId={roadmapId!}
|
||||||
|
description={description}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
115
src/components/ShareOptions/ShareSuccess.tsx
Normal file
115
src/components/ShareOptions/ShareSuccess.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { CheckCircle, Copy, Facebook, Linkedin, Twitter } from 'lucide-react';
|
||||||
|
import { useCopyText } from '../../hooks/use-copy-text';
|
||||||
|
import { cn } from '../../lib/classname';
|
||||||
|
import type { AllowedRoadmapVisibility } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
|
||||||
|
|
||||||
|
type ShareSuccessProps = {
|
||||||
|
roadmapId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
visibility: AllowedRoadmapVisibility;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ShareSuccess(props: ShareSuccessProps) {
|
||||||
|
const { roadmapId, onClose, description, visibility } = props;
|
||||||
|
|
||||||
|
const baseUrl = import.meta.env.DEV
|
||||||
|
? 'http://localhost:3000'
|
||||||
|
: 'https://roadmap.sh';
|
||||||
|
const shareLink = `${baseUrl}/r?id=${roadmapId}`;
|
||||||
|
|
||||||
|
const { copyText, isCopied } = useCopyText();
|
||||||
|
|
||||||
|
const socialShareLinks = [
|
||||||
|
{
|
||||||
|
title: 'Twitter',
|
||||||
|
href: `https://twitter.com/intent/tweet?text=${description}&url=${shareLink}`,
|
||||||
|
icon: Twitter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Facebook',
|
||||||
|
href: `https://www.facebook.com/sharer/sharer.php?quote=${description}&u=${shareLink}`,
|
||||||
|
icon: Facebook,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Linkedin',
|
||||||
|
href: `https://www.linkedin.com/sharing/share-offsite/?url=${shareLink}`,
|
||||||
|
icon: Linkedin,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex grow flex-col justify-center">
|
||||||
|
<div className="mt-5 flex grow flex-col items-center justify-center gap-1.5">
|
||||||
|
<CheckCircle className="h-14 w-14 text-green-500" />
|
||||||
|
<h3 className="text-xl font-medium">Sharing Settings Updated</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="mt-6 w-full rounded-md border bg-gray-50 p-2 px-2.5 text-gray-700 focus:outline-none"
|
||||||
|
value={shareLink}
|
||||||
|
readOnly
|
||||||
|
onClick={(e) => {
|
||||||
|
e.currentTarget.select();
|
||||||
|
copyText(shareLink);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-sm text-gray-400">
|
||||||
|
You can share the above link with anyone who has access
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{visibility === 'public' && (
|
||||||
|
<>
|
||||||
|
<div className="-mx-4 mt-4 flex items-center gap-1.5">
|
||||||
|
<span className="h-px grow bg-gray-300" />
|
||||||
|
<span className="text-sm uppercase text-gray-600">Or</span>
|
||||||
|
<span className="h-px grow bg-gray-300" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 flex items-center gap-2">
|
||||||
|
<span className="text-sm text-gray-600">Share on</span>
|
||||||
|
<ul className="flex items-center gap-1.5">
|
||||||
|
{socialShareLinks.map((socialShareLink) => (
|
||||||
|
<li key={socialShareLink.title}>
|
||||||
|
<a
|
||||||
|
href={socialShareLink.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex h-8 w-8 items-center justify-center gap-1.5 rounded-md border bg-gray-50 text-sm text-gray-700 hover:bg-gray-100 focus:outline-none"
|
||||||
|
>
|
||||||
|
<socialShareLink.icon className="h-4 w-4" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-4 flex flex-col items-center justify-end gap-2">
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'flex w-full items-center justify-center gap-1.5 rounded bg-black px-4 py-2.5 text-sm font-medium text-white hover:opacity-80',
|
||||||
|
isCopied && 'bg-green-300 text-green-800'
|
||||||
|
)}
|
||||||
|
disabled={isCopied}
|
||||||
|
onClick={() => {
|
||||||
|
copyText(shareLink);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Copy className="h-3.5 w-3.5 stroke-[2.5]" />
|
||||||
|
{isCopied ? 'Copied' : 'Copy'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'flex w-full items-center justify-center gap-1.5 rounded border border-black px-4 py-2 text-sm font-medium hover:bg-gray-100'
|
||||||
|
)}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -311,6 +311,7 @@ export function TeamRoadmaps() {
|
|||||||
|
|
||||||
const shareSettingsModal = selectedResource && (
|
const shareSettingsModal = selectedResource && (
|
||||||
<ShareOptionsModal
|
<ShareOptionsModal
|
||||||
|
description={selectedResource.description!}
|
||||||
visibility={selectedResource.visibility!}
|
visibility={selectedResource.visibility!}
|
||||||
sharedTeamMemberIds={selectedResource.sharedTeamMemberIds!}
|
sharedTeamMemberIds={selectedResource.sharedTeamMemberIds!}
|
||||||
sharedFriendIds={selectedResource.sharedFriendIds!}
|
sharedFriendIds={selectedResource.sharedFriendIds!}
|
||||||
|
Reference in New Issue
Block a user