diff --git a/src/components/Activity/ActivityPage.tsx b/src/components/Activity/ActivityPage.tsx index da8da197d..e76ffa4d7 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) {