Refactor topic popup

feat/topic-chat
Kamran Ahmed 1 week ago
parent fb6ce28104
commit 7b9e6ed079
  1. 44
      src/components/TopicDetail/TopicDetail.tsx
  2. 48
      src/components/TopicDetail/TopicDetailLink.tsx
  3. 23
      src/components/TopicDetail/TopicDetailsTabs.tsx
  4. 13
      src/components/TopicDetail/TopicProgressButton.tsx
  5. 2
      src/data/roadmaps/frontend/content/how-does-the-internet-work@yCnn-NfSxIybUQ2iTuUGq.md
  6. 2
      src/data/roadmaps/frontend/content/internet@VlNNwIEDWqQXtqkHWJYzC.md

@ -21,7 +21,7 @@ import type {
RoadmapContentDocument, RoadmapContentDocument,
} from '../CustomRoadmap/CustomRoadmap'; } from '../CustomRoadmap/CustomRoadmap';
import { markdownToHtml, sanitizeMarkdown } from '../../lib/markdown'; import { markdownToHtml, sanitizeMarkdown } from '../../lib/markdown';
import { Ban, Coins, FileText, HeartHandshake, Star, X } from 'lucide-react'; import { Ban, FileText, HeartHandshake, Star, X } from 'lucide-react';
import { getUrlParams, parseUrl } from '../../lib/browser'; import { getUrlParams, parseUrl } from '../../lib/browser';
import { Spinner } from '../ReactIcons/Spinner'; import { Spinner } from '../ReactIcons/Spinner';
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx'; import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
@ -396,29 +396,28 @@ export function TopicDetail(props: TopicDetailProps) {
'flex flex-col': activeTab === 'ai', 'flex flex-col': activeTab === 'ai',
})} })}
> >
<div className={cn('mb-6 -mx-6 -mt-6 border-b p-2 flex items-center justify-between')}> <div
className={cn(
'-mt-4 mb-2 flex items-center justify-between py-1.5',
)}
>
<div> <div>
{!isEmbed && ( <a
<TopicProgressButton href={contributionUrl}
topicId={ target={'_blank'}
topicId.indexOf('@') !== -1 className="flex w-full items-center justify-center rounded-md px-2 py-1 text-sm text-xs text-black transition-colors hover:bg-black hover:text-white disabled:bg-green-200 disabled:text-black"
? topicId.split('@')[1] >
: topicId <GitHubIcon className="mr-1.5 inline-block size-[12px] text-current" />
} Edit this content
resourceId={resourceId} </a>
resourceType={resourceType}
onClose={handleClose}
/>
)}
</div> </div>
<button <button
type="button" type="button"
id="close-topic" id="close-topic"
className="flex gap-2 items-center rounded-lg bg-transparent p-1.5 text-xs uppercase tracking-wider text-gray-400 hover:bg-gray-200 hover:text-gray-900" className="flex items-center gap-2 rounded-lg bg-transparent px-1.5 py-1 text-xs tracking-wider text-gray-400 uppercase hover:bg-gray-200 hover:text-gray-900"
onClick={handleClose} onClick={handleClose}
> >
<X className="size-4" /> <X className="size-4" />
Close
</button> </button>
</div> </div>
@ -560,19 +559,6 @@ export function TopicDetail(props: TopicDetailProps) {
})} })}
</ul> </ul>
{hasPaidScrimbaLinks && (
<div className="relative mt-4 -mb-1 ml-3 rounded-md border border-yellow-300 bg-yellow-100 px-2.5 py-2 text-sm text-yellow-800">
<div className="flex items-center gap-2">
<Coins className="h-4 w-4 text-yellow-700" />
<span>
Scrimba is offering{' '}
<span className={'font-semibold'}>20% off</span>{' '}
on all courses for roadmap.sh users.
</span>
</div>
</div>
)}
{showPaidResourceDisclaimer && ( {showPaidResourceDisclaimer && (
<PaidResourceDisclaimer <PaidResourceDisclaimer
onClose={() => { onClose={() => {

@ -1,7 +1,7 @@
import { cn } from '../../lib/classname.ts'; import { cn } from '../../lib/classname.ts';
import type { AllowedLinkTypes } from '../CustomRoadmap/CustomRoadmap.tsx'; import type { AllowedLinkTypes } from '../CustomRoadmap/CustomRoadmap.tsx';
const linkTypes: Record<AllowedLinkTypes, string> = { const linkTypes: Record<AllowedLinkTypes | string, string> = {
article: 'bg-yellow-300', article: 'bg-yellow-300',
course: 'bg-green-400', course: 'bg-green-400',
opensource: 'bg-black text-white', opensource: 'bg-black text-white',
@ -18,6 +18,34 @@ const paidLinkTypes: Record<string, string> = {
course: 'bg-yellow-300', course: 'bg-yellow-300',
}; };
type TopicLinkBadgeProps = {
isPaid: boolean;
discountText?: string;
type: AllowedLinkTypes | string;
className?: string;
};
function TopicLinkBadge(props: TopicLinkBadgeProps) {
const { isPaid, type, className } = props;
const linkType = type === 'opensource' ? 'OpenSource' : type;
const isDiscount = type.includes('% off');
return (
<span className={cn('mr-2', className)}>
<span
className={cn(
'inline-block rounded-sm px-1.5 py-0.5 text-xs capitalize no-underline',
(isPaid ? paidLinkTypes[type] : linkTypes[type]) || 'bg-gray-200',
isDiscount && 'bg-green-300',
)}
>
{linkType}
</span>
</span>
);
}
type TopicDetailLinkProps = { type TopicDetailLinkProps = {
url: string; url: string;
onClick?: () => void; onClick?: () => void;
@ -29,7 +57,7 @@ type TopicDetailLinkProps = {
export function TopicDetailLink(props: TopicDetailLinkProps) { export function TopicDetailLink(props: TopicDetailLinkProps) {
const { url, onClick, type, title, isPaid = false } = props; const { url, onClick, type, title, isPaid = false } = props;
const linkType = type === 'opensource' ? 'OpenSource' : type; const isScrimbaLink = url.toLowerCase().includes('scrimba.com');
return ( return (
<a <a
@ -38,14 +66,14 @@ export function TopicDetailLink(props: TopicDetailLinkProps) {
className="group font-medium text-gray-800 underline underline-offset-1 hover:text-black" className="group font-medium text-gray-800 underline underline-offset-1 hover:text-black"
onClick={onClick} onClick={onClick}
> >
<span <TopicLinkBadge
className={cn( isPaid={isPaid}
'mr-2 inline-block rounded-sm px-1.5 py-0.5 text-xs capitalize no-underline', type={type}
(isPaid ? paidLinkTypes[type] : linkTypes[type]) || 'bg-gray-200', discountText={isScrimbaLink ? '20% off' : undefined}
)} className={isScrimbaLink ? 'mr-1' : 'mr-2'}
> />
{linkType} {isScrimbaLink && <TopicLinkBadge isPaid={isPaid} type="20% off" />}
</span>
{title} {title}
</a> </a>
); );

@ -1,4 +1,4 @@
import { BookIcon, SparklesIcon, type LucideIcon } from 'lucide-react'; import { Earth, WandSparkles, type LucideIcon } from 'lucide-react';
export type AllowedTopicDetailsTabs = 'content' | 'ai'; export type AllowedTopicDetailsTabs = 'content' | 'ai';
@ -11,17 +11,18 @@ export function TopicDetailsTabs(props: TopicDetailsTabsProps) {
const { activeTab, setActiveTab } = props; const { activeTab, setActiveTab } = props;
return ( return (
<div className="flex w-max items-center gap-1 rounded-lg border border-gray-200 p-0.5"> <div className="flex w-max items-center gap-1.5 p-0.5">
<TopicDetailsTab <TopicDetailsTab
isActive={activeTab === 'content'} isActive={activeTab === 'content'}
icon={BookIcon} icon={Earth}
label="Content" label="Resources"
onClick={() => setActiveTab('content')} onClick={() => setActiveTab('content')}
/> />
<TopicDetailsTab <TopicDetailsTab
isActive={activeTab === 'ai'} isActive={activeTab === 'ai'}
icon={SparklesIcon} icon={WandSparkles}
label="Learn with AI" label="AI Tutor"
isNew={true}
onClick={() => setActiveTab('ai')} onClick={() => setActiveTab('ai')}
/> />
</div> </div>
@ -32,15 +33,16 @@ type TopicDetailsTabProps = {
isActive: boolean; isActive: boolean;
icon: LucideIcon; icon: LucideIcon;
label: string; label: string;
isNew?: boolean;
onClick: () => void; onClick: () => void;
}; };
function TopicDetailsTab(props: TopicDetailsTabProps) { function TopicDetailsTab(props: TopicDetailsTabProps) {
const { isActive, icon: Icon, label, onClick } = props; const { isActive, icon: Icon, label, isNew, onClick } = props;
return ( return (
<button <button
className="flex h-7 items-center gap-2 rounded-md px-2 py-0.5 text-sm text-gray-500 data-[state=active]:bg-black data-[state=active]:text-white" className="flex hover:bg-gray-100 items-center gap-2 px-2 py-1 rounded-lg text-sm text-gray-500 data-[state=active]:bg-black data-[state=active]:text-white"
data-state={isActive ? 'active' : 'inactive'} data-state={isActive ? 'active' : 'inactive'}
onClick={onClick} onClick={onClick}
disabled={isActive} disabled={isActive}
@ -48,6 +50,11 @@ function TopicDetailsTab(props: TopicDetailsTabProps) {
> >
<Icon className="h-4 w-4" /> <Icon className="h-4 w-4" />
{label} {label}
{isNew && (
<span className="text-xs bg-yellow-400 text-black rounded-sm px-1">
New
</span>
)}
</button> </button>
); );
} }

@ -204,6 +204,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
console.error(err); console.error(err);
}) })
.finally(() => { .finally(() => {
setShowChangeStatus(false);
setIsUpdatingProgress(false); setIsUpdatingProgress(false);
}); });
}; };
@ -223,9 +224,9 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
if (isUpdatingProgress) { if (isUpdatingProgress) {
return ( return (
<button className="inline-flex cursor-default items-center rounded-md bg-white p-1 px-2 text-sm text-black"> <button className="inline-flex cursor-default items-center rounded-md border border-gray-300 bg-white p-1 px-2 text-sm text-black">
<Spinner isDualRing={false} className="h-4 w-4" /> <Spinner isDualRing={false} className="h-4 w-4" />
<span className="ml-2">Updating Status..</span> <span className="ml-2">Please wait..</span>
</button> </button>
); );
} }
@ -233,23 +234,23 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
return ( return (
<div className="relative inline-flex"> <div className="relative inline-flex">
<button <button
className="inline-flex cursor-default cursor-pointer items-center rounded-md border border-gray-300 p-1 px-2 text-sm text-black hover:bg-gray-100"
onClick={() => setShowChangeStatus(true)} onClick={() => setShowChangeStatus(true)}
className="inline-flex cursor-pointer items-center gap-2 rounded-md p-1 px-2 text-sm text-black hover:bg-gray-100"
> >
<span className="flex h-2 w-2"> <span className="flex h-2 w-2">
<span <span
className={`relative inline-flex h-2 w-2 rounded-full ${statusColors[progress]}`} className={`relative inline-flex h-2 w-2 rounded-full ${statusColors[progress]}`}
></span> ></span>
</span> </span>
<span className="capitalize"> <span className="ml-2 capitalize">
{progress === 'learning' ? 'In Progress' : progress} {progress === 'learning' ? 'In Progress' : progress}
</span> </span>
<ChevronDown className="h-4 w-4" /> <ChevronDown className="ml-2 h-4 w-4" />
</button> </button>
{showChangeStatus && ( {showChangeStatus && (
<div <div
className="absolute top-full left-0 z-50 mt-1 flex min-w-[160px] flex-col divide-y rounded-md border border-gray-200 bg-white shadow-md [&>button:first-child]:rounded-t-md [&>button:last-child]:rounded-b-md" className="absolute bg-white z-50 top-full right-0 mt-1 flex min-w-[160px] flex-col divide-y rounded-md border border-gray-200 bg-white shadow-md [&>button:first-child]:rounded-t-md [&>button:last-child]:rounded-b-md"
ref={changeStatusRef!} ref={changeStatusRef!}
> >
{allowMarkingDone && ( {allowMarkingDone && (

@ -4,7 +4,7 @@ The internet is a global network that connects computers and devices so they can
Visit the following resources to learn more: Visit the following resources to learn more:
- [@roadmap@Introduction to Internet](https://roadmap.sh/guides/what-is-internet) - [@article@Introduction to Internet](https://roadmap.sh/guides/what-is-internet)
- [@article@How does the Internet Work?](https://cs.fyi/guide/how-does-internet-work) - [@article@How does the Internet Work?](https://cs.fyi/guide/how-does-internet-work)
- [@article@How Does the Internet Work? MDN Docs](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/How_does_the_Internet_work) - [@article@How Does the Internet Work? MDN Docs](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/How_does_the_Internet_work)
- [@video@How the Internet Works in 5 Minutes](https://www.youtube.com/watch?v=7_LPdttKXPc) - [@video@How the Internet Works in 5 Minutes](https://www.youtube.com/watch?v=7_LPdttKXPc)

@ -4,5 +4,5 @@ The Internet is a global network of interconnected computer networks that use th
Visit the following resources to learn more: Visit the following resources to learn more:
- [@roadmap@Introduction to Internet](https://roadmap.sh/guides/what-is-internet) - [@article@Introduction to Internet](https://roadmap.sh/guides/what-is-internet)
- [@article@The Internet](https://en.wikipedia.org/wiki/Internet) - [@article@The Internet](https://en.wikipedia.org/wiki/Internet)

Loading…
Cancel
Save