diff --git a/src/components/tutorial/TutorialWindow.tsx b/src/components/tutorial/TutorialWindow.tsx index e243e83..c595ee4 100644 --- a/src/components/tutorial/TutorialWindow.tsx +++ b/src/components/tutorial/TutorialWindow.tsx @@ -1,6 +1,6 @@ import { Tabs, TabsContent } from "@radix-ui/react-tabs"; import { useNavigate, useRouter, useSearch } from "@tanstack/react-router"; -import { DatabaseZapIcon, XIcon } from "lucide-react"; +import { CheckIcon, DatabaseZapIcon, LinkIcon, XIcon } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import { useCallback, @@ -9,7 +9,8 @@ import { useRef, useState, } from "react"; -import { steps } from "@/data/tutorial"; +import { toast } from "sonner"; +import { steps as articles } from "@/data/tutorial"; import { useScrollShadow } from "@/hooks/use-scroll-shadow"; import { cn } from "@/lib/utils"; import { @@ -22,6 +23,65 @@ import { ScrollArea } from "../ui/scroll-area"; import { ScrollShadow } from "../ui/scroll-shadow"; import { TutorialTableOfContents } from "./TutorialTableOfContents"; +function CopyArticleLinkButton({ activeStep }: { activeStep: string | null }) { + const [copied, setCopied] = useState(false); + + const copyLink = useCallback(() => { + if (!activeStep) return; + + const url = new URL(window.location.href); + url.searchParams.set( + "article", + encodeURIComponent(activeStep.toLowerCase()), + ); + + navigator.clipboard + .writeText(url.toString()) + .then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }) + .catch((_) => { + toast.error("Couldn't copy the url"); + }); + }, [activeStep]); + + return ( + + ); +} + function FloatingWindowHeader({ toggleWindow }: { toggleWindow: () => void }) { return (
@@ -365,6 +425,7 @@ function FloatingWindow({ isResizing && "pointer-events-none", )} > + - {steps.map((step) => ( + {articles.map((step) => ( { - if (activeStepFromSearch && typeof activeStepFromSearch === "string") { - const stepInSearch = decodeURIComponent(activeStepFromSearch); - if (steps.find((step) => step.title === stepInSearch)) { - setActiveStep(stepInSearch); + if ( + activeArticleFromSearch && + typeof activeArticleFromSearch === "string" + ) { + const articleInSearch = decodeURIComponent( + activeArticleFromSearch.toLowerCase(), + ); + + if (articles.find((a) => a.title === articleInSearch)) { + setActiveStep(articleInSearch); } } - }, [activeStepFromSearch]); + }, [activeArticleFromSearch]); const router = useRouter(); diff --git a/src/components/tutorial/index.tsx b/src/components/tutorial/index.tsx index af08fe6..2016499 100644 --- a/src/components/tutorial/index.tsx +++ b/src/components/tutorial/index.tsx @@ -99,7 +99,7 @@ export function LinkToArticle({ articleTitle: string; }) { const encodedTitle = useMemo( - () => encodeURIComponent(articleTitle), + () => encodeURIComponent(articleTitle.toLowerCase()), [articleTitle], ); @@ -107,7 +107,7 @@ export function LinkToArticle({ diff --git a/src/routes/_tutorial.tsx b/src/routes/_tutorial.tsx index 54e27e6..855718f 100644 --- a/src/routes/_tutorial.tsx +++ b/src/routes/_tutorial.tsx @@ -10,7 +10,7 @@ export const Route = createFileRoute("/_tutorial")({ validateSearch: z .object({ temp_db_missing: z.string().optional(), - step: z.string().optional(), + article: z.string().optional(), }) .extend(highlightParamSchema.shape), loader: async () => {