diff --git a/src/components/Activity/ActivityPage.tsx b/src/components/Activity/ActivityPage.tsx
index da8da197d..2bc809bfd 100644
--- a/src/components/Activity/ActivityPage.tsx
+++ b/src/components/Activity/ActivityPage.tsx
@@ -14,6 +14,7 @@ type ProgressResponse = {
done: number;
total: number;
isCustomResource: boolean;
+ roadmapSlug?: string;
};
export type ActivityResponse = {
@@ -52,7 +53,7 @@ export function ActivityPage() {
async function loadActivity() {
const { error, response } = await httpGet(
- `${import.meta.env.PUBLIC_API_URL}/v1-get-user-stats`
+ `${import.meta.env.PUBLIC_API_URL}/v1-get-user-stats`,
);
if (!response || error) {
@@ -107,6 +108,7 @@ export function ActivityPage() {
.map((roadmap) => (
(
void;
showClearButton?: boolean;
isCustomResource: boolean;
+ roadmapSlug?: string;
};
export function ResourceProgress(props: ResourceProgressType) {
@@ -37,6 +38,7 @@ export function ResourceProgress(props: ResourceProgressType) {
doneCount,
skippedCount,
onCleared,
+ roadmapSlug,
} = props;
async function clearProgress() {
@@ -46,7 +48,7 @@ export function ResourceProgress(props: ResourceProgressType) {
{
resourceId,
resourceType,
- }
+ },
);
if (error || !response) {
@@ -72,7 +74,7 @@ export function ResourceProgress(props: ResourceProgressType) {
: `/best-practices/${resourceId}`;
if (isCustomResource) {
- url = `/r?id=${resourceId}`;
+ url = `/r/${roadmapSlug}`;
}
const totalMarked = doneCount + skippedCount;
diff --git a/src/components/CreateTeam/RoadmapSelector.tsx b/src/components/CreateTeam/RoadmapSelector.tsx
index b3d160276..c7462361f 100644
--- a/src/components/CreateTeam/RoadmapSelector.tsx
+++ b/src/components/CreateTeam/RoadmapSelector.tsx
@@ -14,6 +14,7 @@ import { useToast } from '../../hooks/use-toast';
export type TeamResourceConfig = {
isCustomResource: boolean;
+ roadmapSlug?: string;
title: string;
description?: string;
visibility?: AllowedRoadmapVisibility;
@@ -80,7 +81,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
{
resourceId: roadmapId,
resourceType: 'roadmap',
- }
+ },
);
if (error || !response) {
@@ -114,7 +115,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
resourceId: roadmapId,
resourceType: 'roadmap',
removed: [],
- }
+ },
);
if (error || !response) {
@@ -312,7 +313,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
`${
import.meta.env.PUBLIC_EDITOR_APP_URL
}/${resourceId}`,
- '_blank'
+ '_blank',
);
return;
}
@@ -335,7 +336,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) {
)}
);
- }
+ },
)}
)}
diff --git a/src/components/CreateVersion/CreateVersion.tsx b/src/components/CreateVersion/CreateVersion.tsx
index 8fcf50554..075886182 100644
--- a/src/components/CreateVersion/CreateVersion.tsx
+++ b/src/components/CreateVersion/CreateVersion.tsx
@@ -82,7 +82,7 @@ export function CreateVersion(props: CreateVersionProps) {
return (
diff --git a/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx b/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx
index b7cf3ea05..00be8ac5d 100644
--- a/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx
+++ b/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx
@@ -27,6 +27,7 @@ export interface RoadmapDocument {
_id?: string;
title: string;
description?: string;
+ slug?: string;
creatorId: string;
teamId?: string;
isDiscoverable: boolean;
@@ -145,7 +146,7 @@ export function CreateRoadmapModal(props: CreateRoadmapModalProps) {
name="title"
id="title"
required
- className="block text-black w-full rounded-md border border-gray-300 px-2.5 py-2 outline-none focus:border-black sm:text-sm"
+ className="block w-full rounded-md border border-gray-300 px-2.5 py-2 text-black outline-none focus:border-black sm:text-sm"
placeholder="Enter Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
@@ -165,8 +166,8 @@ export function CreateRoadmapModal(props: CreateRoadmapModalProps) {
name="description"
required
className={cn(
- 'block text-black h-24 w-full resize-none rounded-md border border-gray-300 px-2.5 py-2 outline-none focus:border-black sm:text-sm',
- isInvalidDescription && 'border-red-300 bg-red-100'
+ 'block h-24 w-full resize-none rounded-md border border-gray-300 px-2.5 py-2 text-black outline-none focus:border-black sm:text-sm',
+ isInvalidDescription && 'border-red-300 bg-red-100',
)}
placeholder="Enter Description"
value={description}
diff --git a/src/components/CustomRoadmap/CustomRoadmap.tsx b/src/components/CustomRoadmap/CustomRoadmap.tsx
index bd823c9a6..790c64fcf 100644
--- a/src/components/CustomRoadmap/CustomRoadmap.tsx
+++ b/src/components/CustomRoadmap/CustomRoadmap.tsx
@@ -62,10 +62,11 @@ export function hideRoadmapLoader() {
type CustomRoadmapProps = {
isEmbed?: boolean;
+ slug?: string;
};
export function CustomRoadmap(props: CustomRoadmapProps) {
- const { isEmbed = false } = props;
+ const { isEmbed = false, slug } = props;
const { id, secret } = getUrlParams() as { id: string; secret: string };
@@ -76,9 +77,11 @@ export function CustomRoadmap(props: CustomRoadmapProps) {
async function getRoadmap() {
setIsLoading(true);
- const roadmapUrl = new URL(
- `${import.meta.env.PUBLIC_API_URL}/v1-get-roadmap/${id}`,
- );
+ const roadmapUrl = slug
+ ? new URL(
+ `${import.meta.env.PUBLIC_API_URL}/v1-get-roadmap-by-slug/${slug}`,
+ )
+ : new URL(`${import.meta.env.PUBLIC_API_URL}/v1-get-roadmap/${id}`);
if (secret) {
roadmapUrl.searchParams.set('secret', secret);
@@ -102,12 +105,12 @@ export function CustomRoadmap(props: CustomRoadmapProps) {
}
async function trackVisit() {
- if (!isLoggedIn() || isEmbed) {
+ if (!isLoggedIn() || isEmbed || !roadmap) {
return;
}
await httpPost(`${import.meta.env.PUBLIC_API_URL}/v1-visit`, {
- resourceId: id,
+ resourceId: roadmap?._id,
resourceType: 'roadmap',
});
}
@@ -116,9 +119,16 @@ export function CustomRoadmap(props: CustomRoadmapProps) {
getRoadmap().finally(() => {
hideRoadmapLoader();
});
- trackVisit().then();
}, []);
+ useEffect(() => {
+ if (!roadmap) {
+ return;
+ }
+
+ trackVisit().then();
+ }, [roadmap]);
+
if (isLoading) {
return null;
}
diff --git a/src/components/CustomRoadmap/PersonalRoadmapList.tsx b/src/components/CustomRoadmap/PersonalRoadmapList.tsx
index 7e863abbb..c50b780de 100644
--- a/src/components/CustomRoadmap/PersonalRoadmapList.tsx
+++ b/src/components/CustomRoadmap/PersonalRoadmapList.tsx
@@ -18,7 +18,7 @@ import { PersonalRoadmapActionDropdown } from './PersonalRoadmapActionDropdown';
import type { GetRoadmapListResponse } from './RoadmapListPage';
import { useState, type Dispatch, type SetStateAction } from 'react';
import { ShareOptionsModal } from '../ShareOptions/ShareOptionsModal';
-import {RoadmapIcon} from "../ReactIcons/RoadmapIcon.tsx";
+import { RoadmapIcon } from '../ReactIcons/RoadmapIcon.tsx';
type PersonalRoadmapListType = {
roadmaps: GetRoadmapListResponse['personalRoadmaps'];
@@ -37,7 +37,7 @@ export function PersonalRoadmapList(props: PersonalRoadmapListType) {
async function deleteRoadmap(roadmapId: string) {
const { response, error } = await httpDelete(
- `${import.meta.env.PUBLIC_API_URL}/v1-delete-roadmap/${roadmapId}`
+ `${import.meta.env.PUBLIC_API_URL}/v1-delete-roadmap/${roadmapId}`,
);
if (error || !response) {
@@ -61,6 +61,7 @@ export function PersonalRoadmapList(props: PersonalRoadmapListType) {
const shareSettingsModal = selectedRoadmap && (
Promise;
setSelectedRoadmap: (
- roadmap: GetRoadmapListResponse['personalRoadmaps'][number] | null
+ roadmap: GetRoadmapListResponse['personalRoadmaps'][number] | null,
) => void;
};
@@ -183,9 +184,9 @@ function CustomRoadmapItem(props: CustomRoadmapItemProps) {
Edit
diff --git a/src/components/CustomRoadmap/ResourceProgressStats.tsx b/src/components/CustomRoadmap/ResourceProgressStats.tsx
index f9a4bfa43..9ebe21a6b 100644
--- a/src/components/CustomRoadmap/ResourceProgressStats.tsx
+++ b/src/components/CustomRoadmap/ResourceProgressStats.tsx
@@ -24,6 +24,7 @@ export function ResourceProgressStats(props: ResourceProgressStatsProps) {
<>
{isSharing && $canManageCurrentRoadmap && $currentRoadmap && (
setIsSharingWithOthers(false)}
@@ -132,7 +134,7 @@ export function RoadmapHeader(props: RoadmapHeaderProps) {
@@ -141,6 +143,7 @@ export function RoadmapHeader(props: RoadmapHeaderProps) {
<>
{isSharing && $currentRoadmap && (
diff --git a/src/components/HeroSection/FavoriteRoadmaps.tsx b/src/components/HeroSection/FavoriteRoadmaps.tsx
index 73dced17b..a639be626 100644
--- a/src/components/HeroSection/FavoriteRoadmaps.tsx
+++ b/src/components/HeroSection/FavoriteRoadmaps.tsx
@@ -16,6 +16,7 @@ export type UserProgressResponse = {
total: number;
updatedAt: Date;
isCustomResource: boolean;
+ roadmapSlug?: string;
team?: {
name: string;
id: string;
@@ -41,7 +42,7 @@ function renderProgress(progressList: UserProgressResponse) {
resourceType: progress.resourceType,
isFavorite: progress.isFavorite,
},
- })
+ }),
);
const totalDone = progress.done + progress.skipped;
@@ -89,7 +90,7 @@ export function FavoriteRoadmaps() {
setIsLoading(true);
const { response: progressList, error } = await httpGet(
- `${import.meta.env.PUBLIC_API_URL}/v1-get-hero-roadmaps`
+ `${import.meta.env.PUBLIC_API_URL}/v1-get-hero-roadmaps`,
);
if (error || !progressList) {
@@ -121,7 +122,7 @@ export function FavoriteRoadmaps() {
const hasProgress = progress?.length > 0;
const customRoadmaps = progress?.filter(
- (p) => p.isCustomResource && !p.team?.name
+ (p) => p.isCustomResource && !p.team?.name,
);
const defaultRoadmaps = progress?.filter((p) => !p.isCustomResource);
const teamRoadmaps: HeroTeamRoadmaps = progress
diff --git a/src/components/HeroSection/HeroRoadmaps.tsx b/src/components/HeroSection/HeroRoadmaps.tsx
index 382c12e8f..4b99e3fc5 100644
--- a/src/components/HeroSection/HeroRoadmaps.tsx
+++ b/src/components/HeroSection/HeroRoadmaps.tsx
@@ -172,7 +172,7 @@ export function HeroRoadmaps(props: ProgressListProps) {
customRoadmap.total) *
100
}
- url={`/r?id=${customRoadmap.resourceId}`}
+ url={`/r/${customRoadmap?.roadmapSlug}`}
allowFavorite={false}
/>
);
@@ -187,7 +187,7 @@ export function HeroRoadmaps(props: ProgressListProps) {
const currentTeam: UserProgressResponse[0]['team'] =
teamRoadmaps?.[teamName]?.[0]?.team;
const roadmapsList = teamRoadmaps[teamName].filter(
- (roadmap) => !!roadmap.resourceTitle
+ (roadmap) => !!roadmap.resourceTitle,
);
const canManageTeam = ['admin', 'manager'].includes(currentTeam?.role!);
@@ -242,7 +242,7 @@ export function HeroRoadmaps(props: ProgressListProps) {
customRoadmap.total) *
100
}
- url={`/r?id=${customRoadmap.resourceId}`}
+ url={`/r/${customRoadmap?.roadmapSlug}`}
allowFavorite={false}
/>
);
diff --git a/src/components/ShareOptions/ShareOptionsModal.tsx b/src/components/ShareOptions/ShareOptionsModal.tsx
index 3c257a4d8..8962f98f2 100644
--- a/src/components/ShareOptions/ShareOptionsModal.tsx
+++ b/src/components/ShareOptions/ShareOptionsModal.tsx
@@ -1,10 +1,4 @@
-import {
- type ReactNode,
- useCallback,
- useState,
- useMemo,
- useEffect,
-} from 'react';
+import { type ReactNode, useCallback, useState, useMemo } from 'react';
import { Globe2, Loader2, Lock } from 'lucide-react';
import { type ListFriendsResponse, ShareFriendList } from './ShareFriendList';
import { TransferToTeamList } from './TransferToTeamList';
@@ -37,6 +31,7 @@ type ShareOptionsModalProps = {
teamId?: string;
roadmapId?: string;
description?: string;
+ roadmapSlug?: string;
onShareSettingsUpdate: OnShareSettingsUpdate;
};
@@ -44,6 +39,7 @@ type ShareOptionsModalProps = {
export function ShareOptionsModal(props: ShareOptionsModalProps) {
const {
roadmapId,
+ roadmapSlug,
onClose,
isDiscoverable: defaultIsDiscoverable = false,
visibility: defaultVisibility,
@@ -68,10 +64,10 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
const [visibility, setVisibility] = useState(defaultVisibility);
const [isDiscoverable, setIsDiscoverable] = useState(defaultIsDiscoverable);
const [sharedTeamMemberIds, setSharedTeamMemberIds] = useState(
- defaultSharedMemberIds
+ defaultSharedMemberIds,
);
const [sharedFriendIds, setSharedFriendIds] = useState(
- defaultSharedFriendIds
+ defaultSharedFriendIds,
);
const [selectedTeamId, setSelectedTeamId] = useState(null);
@@ -120,7 +116,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
sharedFriendIds,
sharedTeamMemberIds,
isDiscoverable,
- }
+ },
);
if (error) {
@@ -151,7 +147,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
teamId,
sharedTeamMemberIds,
isDiscoverable,
- }
+ },
);
if (error) {
@@ -162,7 +158,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
window.location.reload();
},
- [roadmapId]
+ [roadmapId],
);
if (isSettingsUpdated) {
@@ -173,6 +169,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
bodyClassName="p-4 flex flex-col"
>
0 ? defaultSharedFriendIds : []
+ defaultSharedFriendIds.length > 0 ? defaultSharedFriendIds : [],
);
} else if (visibility === 'team' && teamId) {
setSharedTeamMemberIds(
- defaultSharedMemberIds?.length > 0 ? defaultSharedMemberIds : []
+ defaultSharedMemberIds?.length > 0 ? defaultSharedMemberIds : [],
);
setSharedFriendIds([]);
} else {
@@ -329,7 +326,7 @@ export function ShareOptionsModal(props: ShareOptionsModalProps) {
}
onClick={() => {
handleTransferToTeam(selectedTeamId!, sharedTeamMemberIds).then(
- () => null
+ () => null,
);
}}
>
@@ -374,7 +371,7 @@ function UpdateAction(props: {
className={cn(
'flex min-w-[120px] items-center justify-center gap-1.5 rounded-md border border-gray-900 bg-gray-900 px-4 py-2 text-white hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-75',
disabled && 'border-gray-700 bg-gray-700 text-white hover:bg-gray-700',
- className
+ className,
)}
disabled={disabled}
onClick={onClick}
diff --git a/src/components/ShareOptions/ShareSuccess.tsx b/src/components/ShareOptions/ShareSuccess.tsx
index de0c2bb39..1785bfbdb 100644
--- a/src/components/ShareOptions/ShareSuccess.tsx
+++ b/src/components/ShareOptions/ShareSuccess.tsx
@@ -4,6 +4,7 @@ import { cn } from '../../lib/classname';
import type { AllowedRoadmapVisibility } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
type ShareSuccessProps = {
+ roadmapSlug?: string;
roadmapId: string;
onClose: () => void;
visibility: AllowedRoadmapVisibility;
@@ -13,6 +14,7 @@ type ShareSuccessProps = {
export function ShareSuccess(props: ShareSuccessProps) {
const {
+ roadmapSlug,
roadmapId,
onClose,
description,
@@ -23,7 +25,9 @@ export function ShareSuccess(props: ShareSuccessProps) {
const baseUrl = import.meta.env.DEV
? 'http://localhost:3000'
: 'https://roadmap.sh';
- const shareLink = `${baseUrl}/r?id=${roadmapId}`;
+ const shareLink = roadmapSlug
+ ? `${baseUrl}/r/${roadmapSlug}`
+ : `${baseUrl}/r?id=${roadmapId}`;
const { copyText, isCopied } = useCopyText();
@@ -84,13 +88,13 @@ export function ShareSuccess(props: ShareSuccessProps) {
{
- e.currentTarget.select();
- copyText(embedHtml);
- }}
- readOnly={true}
- className="w-full resize-none rounded-md border bg-gray-50 p-2 text-sm"
- value={embedHtml}
+ onClick={(e) => {
+ e.currentTarget.select();
+ copyText(embedHtml);
+ }}
+ readOnly={true}
+ className="w-full resize-none rounded-md border bg-gray-50 p-2 text-sm"
+ value={embedHtml}
/>
@@ -127,7 +131,7 @@ export function ShareSuccess(props: ShareSuccessProps) {