|
|
|
@ -21,6 +21,7 @@ type RoadmapSearchProps = { |
|
|
|
|
onLoadTerm: (topic: string) => void; |
|
|
|
|
limit: number; |
|
|
|
|
limitUsed: number; |
|
|
|
|
isKeyOnly: boolean; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export function RoadmapSearch(props: RoadmapSearchProps) { |
|
|
|
@ -32,16 +33,18 @@ export function RoadmapSearch(props: RoadmapSearchProps) { |
|
|
|
|
limitUsed = 0, |
|
|
|
|
onLoadTerm, |
|
|
|
|
loadAIRoadmapLimit, |
|
|
|
|
isKeyOnly, |
|
|
|
|
} = props; |
|
|
|
|
|
|
|
|
|
const canGenerateMore = limitUsed < limit; |
|
|
|
|
const [isConfiguring, setIsConfiguring] = useState(false); |
|
|
|
|
const openAPIKey = getOpenAIKey(); |
|
|
|
|
const isAuthenticatedUser = isLoggedIn(); |
|
|
|
|
|
|
|
|
|
const randomTerms = ['OAuth', 'APIs', 'UX Design', 'gRPC']; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div className="flex flex-grow flex-col items-center sm:justify-center px-4 py-6 sm:px-6"> |
|
|
|
|
<div className="flex flex-grow flex-col items-center px-4 py-6 sm:px-6"> |
|
|
|
|
{isConfiguring && ( |
|
|
|
|
<OpenAISettings |
|
|
|
|
onClose={() => { |
|
|
|
@ -50,7 +53,7 @@ export function RoadmapSearch(props: RoadmapSearchProps) { |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
)} |
|
|
|
|
<div className="flex flex-col gap-0 text-center sm:gap-2"> |
|
|
|
|
<div className="flex flex-col gap-0 text-center sm:gap-2 md:mt-24 lg:mt-32"> |
|
|
|
|
<h1 className="relative text-2xl font-medium sm:text-3xl"> |
|
|
|
|
<span className="hidden sm:inline">Generate roadmaps with AI</span> |
|
|
|
|
<span className="inline sm:hidden">AI Roadmap Generator</span> |
|
|
|
@ -90,8 +93,28 @@ export function RoadmapSearch(props: RoadmapSearchProps) { |
|
|
|
|
'flex min-w-[154px] flex-shrink-0 items-center justify-center gap-2 rounded-md bg-black px-4 py-2 text-white', |
|
|
|
|
'disabled:cursor-not-allowed disabled:opacity-50', |
|
|
|
|
)} |
|
|
|
|
disabled={!limit || !roadmapTerm || limitUsed >= limit} |
|
|
|
|
onClick={(e) => { |
|
|
|
|
if (!isAuthenticatedUser) { |
|
|
|
|
e.preventDefault(); |
|
|
|
|
showLoginPopup(); |
|
|
|
|
} |
|
|
|
|
}} |
|
|
|
|
disabled={ |
|
|
|
|
isAuthenticatedUser && |
|
|
|
|
(!limit || |
|
|
|
|
!roadmapTerm || |
|
|
|
|
limitUsed >= limit || |
|
|
|
|
(isKeyOnly && !openAPIKey)) |
|
|
|
|
} |
|
|
|
|
> |
|
|
|
|
{!isAuthenticatedUser && ( |
|
|
|
|
<> |
|
|
|
|
<Wand size={20} /> |
|
|
|
|
Generate |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
{isAuthenticatedUser && ( |
|
|
|
|
<> |
|
|
|
|
{(!limit || canGenerateMore) && ( |
|
|
|
|
<> |
|
|
|
|
<Wand size={20} /> |
|
|
|
@ -105,15 +128,22 @@ export function RoadmapSearch(props: RoadmapSearchProps) { |
|
|
|
|
Limit reached |
|
|
|
|
</span> |
|
|
|
|
)} |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
</button> |
|
|
|
|
</form> |
|
|
|
|
<div className="flex flex-row items-center justify-center gap-2 flex-wrap"> |
|
|
|
|
<div className="flex flex-row flex-wrap items-center justify-center gap-2"> |
|
|
|
|
{randomTerms.map((term) => ( |
|
|
|
|
<button |
|
|
|
|
key={term} |
|
|
|
|
disabled={!limit || !canGenerateMore} |
|
|
|
|
disabled={isAuthenticatedUser && (!limit || !canGenerateMore)} |
|
|
|
|
type="button" |
|
|
|
|
onClick={() => { |
|
|
|
|
if (!isAuthenticatedUser) { |
|
|
|
|
showLoginPopup(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
onLoadTerm(term); |
|
|
|
|
}} |
|
|
|
|
className="flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-sm transition-colors hover:border-black hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50" |
|
|
|
@ -129,38 +159,94 @@ export function RoadmapSearch(props: RoadmapSearchProps) { |
|
|
|
|
</a> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
<div className="mt-12 flex flex-col items-center gap-4"> |
|
|
|
|
<p className="text-gray-500 text-center"> |
|
|
|
|
You have generated{' '} |
|
|
|
|
<span |
|
|
|
|
className={cn( |
|
|
|
|
'inline-block min-w-[50px] rounded-xl border px-1.5 text-center text-sm tabular-nums text-gray-800', |
|
|
|
|
{ |
|
|
|
|
'animate-pulse border-zinc-300 bg-zinc-300 text-zinc-300': |
|
|
|
|
!limit, |
|
|
|
|
}, |
|
|
|
|
{!isAuthenticatedUser && ( |
|
|
|
|
<div className="mt-8 flex max-w-[500px] flex-col items-center gap-3 rounded-xl border border-gray-400 px-4 pb-4 pt-3"> |
|
|
|
|
<p className={'text-center text-gray-500'}> |
|
|
|
|
<button |
|
|
|
|
onClick={showLoginPopup} |
|
|
|
|
className="font-medium text-purple-600 underline underline-offset-2 hover:text-purple-800" |
|
|
|
|
> |
|
|
|
|
Sign up (free and takes 2s) or login |
|
|
|
|
</button>{' '} |
|
|
|
|
to start generating roadmaps. Or explore the ones made by the |
|
|
|
|
community. |
|
|
|
|
</p> |
|
|
|
|
<p className="flex flex-col gap-2 text-center text-gray-500 sm:flex-row"> |
|
|
|
|
<a |
|
|
|
|
href="/ai/explore" |
|
|
|
|
className="flex items-center gap-1.5 rounded-full border border-purple-600 px-2.5 py-0.5 text-sm text-purple-600 transition-colors hover:bg-purple-600 hover:text-white" |
|
|
|
|
> |
|
|
|
|
Explore AI Generated Roadmaps <Telescope size={15} /> |
|
|
|
|
</a> |
|
|
|
|
<a |
|
|
|
|
href="/roadmaps" |
|
|
|
|
className="flex items-center gap-1.5 rounded-full border border-purple-600 px-2.5 py-0.5 text-sm text-purple-600 transition-colors hover:bg-purple-600 hover:text-white" |
|
|
|
|
> |
|
|
|
|
Visit Official Roadmaps <ArrowUpRight size={15} /> |
|
|
|
|
</a> |
|
|
|
|
</p> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
{isKeyOnly && isAuthenticatedUser && ( |
|
|
|
|
<div className="mx-auto mt-12 flex max-w-[450px] flex-col items-center gap-4"> |
|
|
|
|
{!openAPIKey && ( |
|
|
|
|
<> |
|
|
|
|
<p className={'text-center text-red-500'}> |
|
|
|
|
We have hit the limit for AI roadmap generation. Please try |
|
|
|
|
again later or{' '} |
|
|
|
|
<button |
|
|
|
|
onClick={() => setIsConfiguring(true)} |
|
|
|
|
className="font-semibold text-purple-600 underline underline-offset-2" |
|
|
|
|
> |
|
|
|
|
{limitUsed} of {limit} |
|
|
|
|
</span>{' '} |
|
|
|
|
roadmaps. |
|
|
|
|
add your own OpenAI API key. |
|
|
|
|
</button> |
|
|
|
|
</p> |
|
|
|
|
<p className="flex min-h-[46px] sm:min-h-[26px] items-center text-sm"> |
|
|
|
|
{limit > 0 && !isLoggedIn() && ( |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
{openAPIKey && ( |
|
|
|
|
<p className={'text-center text-gray-500'}> |
|
|
|
|
You have added your own OpenAI API key.{' '} |
|
|
|
|
<button |
|
|
|
|
onClick={showLoginPopup} |
|
|
|
|
className="rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white" |
|
|
|
|
onClick={() => setIsConfiguring(true)} |
|
|
|
|
className="font-semibold text-purple-600 underline underline-offset-2" |
|
|
|
|
> |
|
|
|
|
Generate more by{' '} |
|
|
|
|
<span className="font-semibold"> |
|
|
|
|
signing up (free and takes 2 seconds) |
|
|
|
|
</span>{' '} |
|
|
|
|
or <span className="font-semibold">logging in</span> |
|
|
|
|
Configure it here if you want. |
|
|
|
|
</button> |
|
|
|
|
</p> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
<p className="flex flex-col gap-2 text-center text-gray-500 sm:flex-row"> |
|
|
|
|
<a |
|
|
|
|
href="/ai/explore" |
|
|
|
|
className="flex items-center gap-1.5 rounded-full border border-purple-600 px-2.5 py-0.5 text-sm text-purple-600 transition-colors hover:bg-purple-600 hover:text-white" |
|
|
|
|
> |
|
|
|
|
Explore AI Roadmaps <Telescope size={15} /> |
|
|
|
|
</a> |
|
|
|
|
<a |
|
|
|
|
href="/roadmaps" |
|
|
|
|
className="flex items-center gap-1.5 rounded-full border border-purple-600 px-2.5 py-0.5 text-sm text-purple-600 transition-colors hover:bg-purple-600 hover:text-white" |
|
|
|
|
> |
|
|
|
|
Visit Official Roadmaps <ArrowUpRight size={15} /> |
|
|
|
|
</a> |
|
|
|
|
</p> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
{!isKeyOnly && limit > 0 && isAuthenticatedUser && ( |
|
|
|
|
<div className="mt-12 flex flex-col items-center gap-4"> |
|
|
|
|
<p className="text-center text-gray-500"> |
|
|
|
|
You have generated{' '} |
|
|
|
|
<span |
|
|
|
|
className={ |
|
|
|
|
'inline-block min-w-[50px] rounded-xl border px-1.5 text-center text-sm tabular-nums text-gray-800' |
|
|
|
|
} |
|
|
|
|
> |
|
|
|
|
{limitUsed} of {limit} |
|
|
|
|
</span>{' '} |
|
|
|
|
roadmaps. |
|
|
|
|
</p> |
|
|
|
|
<p className="-mt-[75px] sm:-mt-[46px] flex min-h-[46px] sm:min-h-[26px] items-center text-sm"> |
|
|
|
|
{limit > 0 && isLoggedIn() && !openAPIKey && ( |
|
|
|
|
{isAuthenticatedUser && ( |
|
|
|
|
<p className="flex items-center text-sm"> |
|
|
|
|
{!openAPIKey && ( |
|
|
|
|
<button |
|
|
|
|
onClick={() => setIsConfiguring(true)} |
|
|
|
|
className="rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white" |
|
|
|
@ -172,7 +258,7 @@ export function RoadmapSearch(props: RoadmapSearchProps) { |
|
|
|
|
</button> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
{limit > 0 && isLoggedIn() && openAPIKey && ( |
|
|
|
|
{openAPIKey && ( |
|
|
|
|
<button |
|
|
|
|
onClick={() => setIsConfiguring(true)} |
|
|
|
|
className="flex flex-row items-center gap-1 rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white" |
|
|
|
@ -182,7 +268,9 @@ export function RoadmapSearch(props: RoadmapSearchProps) { |
|
|
|
|
</button> |
|
|
|
|
)} |
|
|
|
|
</p> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|