feat: create new course (#8605)

* feat: create new course

* seo: update latest roadmap keywords (#8590)

* seo: update roadmap keywords

* Update src/lib/config.ts

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>

* Fix non-logged in user course access

* feat: create new course

* Update UI

---------

Co-authored-by: Vedansh <superuser.ntsystems@outlook.com>
Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
pull/8607/head
Arik Chakma 3 days ago committed by GitHub
parent 30d3a86784
commit b7b96b7d7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 60
      src/components/TopicDetail/CreateCourseModal.tsx
  2. 24
      src/components/TopicDetail/TopicDetail.tsx
  3. 16
      src/components/TopicDetail/TopicDetailAI.tsx

@ -0,0 +1,60 @@
import { WandSparkles } from 'lucide-react';
import { Modal } from '../Modal';
import { useState } from 'react';
type CreateCourseModalProps = {
onClose: () => void;
};
export function CreateCourseModal(props: CreateCourseModalProps) {
const { onClose } = props;
const [subject, setSubject] = useState('');
return (
<Modal
onClose={onClose}
wrapperClassName="h-auto mt-20"
overlayClassName="items-start"
bodyClassName="p-1.5"
>
<form
onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const subject = formData.get('subject');
window.location.href = `/ai/search?term=${subject}&difficulty=beginner&src=topic`;
onClose();
}}
>
<label
className="mb-2.5 ml-1 inline-block text-sm leading-none"
htmlFor="subject"
>
Ask AI to Teach You
</label>
<div className="relative flex items-center gap-2 overflow-hidden">
<input
id="subject"
type="text"
className="w-full bg-white p-2.5 pr-8 text-sm focus:outline-hidden"
placeholder="Enter a topic to learn"
name="subject"
autoFocus={true}
value={subject}
onChange={(e) => setSubject(e.target.value)}
/>
<button
disabled={!subject.trim()}
className="flex h-full disabled:opacity-40 items-center justify-center gap-2 rounded-md bg-black px-3 py-1 text-sm text-white hover:opacity-80"
>
Generate
<WandSparkles className="size-4" />
</button>
</div>
</form>
</Modal>
);
}

@ -42,6 +42,7 @@ import { cn } from '../../lib/classname.ts';
import type { AIChatHistoryType } from '../GenerateCourse/AICourseLessonChat.tsx'; import type { AIChatHistoryType } from '../GenerateCourse/AICourseLessonChat.tsx';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal.tsx'; import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal.tsx';
import { TopicProgressButton } from './TopicProgressButton.tsx'; import { TopicProgressButton } from './TopicProgressButton.tsx';
import { CreateCourseModal } from './CreateCourseModal.tsx';
type TopicDetailProps = { type TopicDetailProps = {
resourceId?: string; resourceId?: string;
@ -119,6 +120,8 @@ export function TopicDetail(props: TopicDetailProps) {
const [showUpgradeModal, setShowUpgradeModal] = useState(false); const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [isCustomResource, setIsCustomResource] = useState(false); const [isCustomResource, setIsCustomResource] = useState(false);
const [showSubjectSearchModal, setShowSubjectSearchModal] = useState(false);
const toast = useToast(); const toast = useToast();
const [showPaidResourceDisclaimer, setShowPaidResourceDisclaimer] = const [showPaidResourceDisclaimer, setShowPaidResourceDisclaimer] =
@ -139,6 +142,7 @@ export function TopicDetail(props: TopicDetailProps) {
setShowUpgradeModal(false); setShowUpgradeModal(false);
setAiChatHistory(defaultChatHistory); setAiChatHistory(defaultChatHistory);
setActiveTab('content'); setActiveTab('content');
setShowSubjectSearchModal(false);
}; };
// Close the topic detail when user clicks outside the topic detail // Close the topic detail when user clicks outside the topic detail
@ -349,10 +353,6 @@ export function TopicDetail(props: TopicDetailProps) {
return null; return null;
} }
const resourceTitleForSearch = resourceTitle
?.toLowerCase()
?.replace(/\s+?roadmap/gi, '');
const tnsLink = const tnsLink =
'https://thenewstack.io/devops/?utm_source=roadmap.sh&utm_medium=Referral&utm_campaign=Topic'; 'https://thenewstack.io/devops/?utm_source=roadmap.sh&utm_medium=Referral&utm_campaign=Topic';
@ -362,10 +362,6 @@ export function TopicDetail(props: TopicDetailProps) {
return resource.topicIds.includes(normalizedTopicId); return resource.topicIds.includes(normalizedTopicId);
}); });
const hasPaidScrimbaLinks = paidResourcesForTopic.some(
(resource) => resource?.url?.toLowerCase().indexOf('scrimba') !== -1,
);
const shouldShowAiTab = !isCustomResource && resourceType === 'roadmap'; const shouldShowAiTab = !isCustomResource && resourceType === 'roadmap';
return ( return (
@ -379,6 +375,10 @@ export function TopicDetail(props: TopicDetailProps) {
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} /> <UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
)} )}
{showSubjectSearchModal && (
<CreateCourseModal onClose={() => setShowSubjectSearchModal(false)} />
)}
{isLoading && ( {isLoading && (
<div className="flex h-full w-full justify-center"> <div className="flex h-full w-full justify-center">
<Spinner <Spinner
@ -448,6 +448,14 @@ export function TopicDetail(props: TopicDetailProps) {
handleClose(); handleClose();
showLoginPopup(); showLoginPopup();
}} }}
onShowSubjectSearchModal={() => {
if (!isLoggedIn()) {
showLoginPopup();
return;
}
setShowSubjectSearchModal(true);
}}
/> />
)} )}

@ -10,9 +10,8 @@ import {
ChevronRightIcon, ChevronRightIcon,
Gift, Gift,
Loader2Icon, Loader2Icon,
LockIcon, LockIcon, SendIcon, Trash2,
SendIcon, WandSparkles
Trash2,
} from 'lucide-react'; } from 'lucide-react';
import { showLoginPopup } from '../../lib/popup'; import { showLoginPopup } from '../../lib/popup';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
@ -42,6 +41,8 @@ type TopicDetailAIProps = {
onUpgrade: () => void; onUpgrade: () => void;
onLogin: () => void; onLogin: () => void;
onShowSubjectSearchModal: () => void;
}; };
export function TopicDetailAI(props: TopicDetailAIProps) { export function TopicDetailAI(props: TopicDetailAIProps) {
@ -53,6 +54,7 @@ export function TopicDetailAI(props: TopicDetailAIProps) {
topicId, topicId,
onUpgrade, onUpgrade,
onLogin, onLogin,
onShowSubjectSearchModal,
} = props; } = props;
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
@ -298,6 +300,14 @@ export function TopicDetailAI(props: TopicDetailAIProps) {
))} ))}
</a> </a>
)} )}
<button
onClick={onShowSubjectSearchModal}
className="flex items-center gap-1.5 rounded-md border border-gray-300 bg-transparent px-2 py-1 hover:bg-gray-200 hover:text-black"
>
<WandSparkles className="h-3 w-3" />
Custom Topic
</button>
</div> </div>
</div> </div>
)} )}

Loading…
Cancel
Save