Block UI for guest users

pull/5337/head
Kamran Ahmed 9 months ago
parent 5b7e7b9767
commit 64cfe503af
  1. 134
      src/components/GenerateRoadmap/GenerateRoadmap.tsx
  2. 10
      src/components/GenerateRoadmap/OpenAISettings.tsx
  3. 202
      src/components/GenerateRoadmap/RoadmapSearch.tsx
  4. 1
      src/lib/ai.ts

@ -35,6 +35,7 @@ import { cn } from '../../lib/classname.ts';
import { RoadmapTopicDetail } from './RoadmapTopicDetail.tsx';
import { AIRoadmapAlert } from './AIRoadmapAlert.tsx';
import { OpenAISettings } from './OpenAISettings.tsx';
import { IS_KEY_ONLY_ROADMAP_GENERATION } from '../../lib/ai.ts';
export type GetAIRoadmapLimitResponse = {
used: number;
@ -104,6 +105,7 @@ export function GenerateRoadmap() {
const [isConfiguring, setIsConfiguring] = useState(false);
const openAPIKey = getOpenAIKey();
const isKeyOnly = IS_KEY_ONLY_ROADMAP_GENERATION;
const renderRoadmap = async (roadmap: string) => {
const { nodes, edges } = generateAIRoadmapFromText(roadmap);
@ -372,6 +374,7 @@ export function GenerateRoadmap() {
limit={roadmapLimit}
limitUsed={roadmapLimitUsed}
loadAIRoadmapLimit={loadAIRoadmapLimit}
isKeyOnly={isKeyOnly}
onLoadTerm={(term: string) => {
setRoadmapTerm(term);
loadTermRoadmap(term).finally(() => {});
@ -427,63 +430,93 @@ export function GenerateRoadmap() {
</span>
)}
{!isLoading && (
<div className="container flex flex-grow flex-col items-center">
<div className="container flex flex-grow flex-col items-start">
<AIRoadmapAlert />
<div className="mt-2 flex w-full flex-col items-start justify-between gap-2 text-sm sm:flex-row sm:items-center sm:gap-0">
<span>
<span
className={cn(
'mr-0.5 inline-block 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':
!roadmapLimit,
},
)}
>
{roadmapLimitUsed} of {roadmapLimit}
</span>{' '}
roadmaps generated.
</span>
{!isLoggedInUser && (
<button
className="rounded-xl border border-current px-1.5 py-0.5 text-left text-sm font-medium text-blue-500 sm:text-center"
onClick={showLoginPopup}
>
Generate more by{' '}
<span className="font-semibold">
signing up (free, takes 2s)
{isKeyOnly && (
<div className="flex flex-row gap-4">
{!openAPIKey && (
<p className={'text-left text-red-500'}>
We have hit the limit for AI roadmap generation. Please
try again tomorrow or{' '}
<button
onClick={() => setIsConfiguring(true)}
className="font-semibold text-purple-600 underline underline-offset-2"
>
add your own OpenAI API key
</button>
</p>
)}
{openAPIKey && (
<p className={'text-left text-gray-500'}>
You have added your own OpenAI API key.{' '}
<button
onClick={() => setIsConfiguring(true)}
className="font-semibold text-purple-600 underline underline-offset-2"
>
Configure it here if you want.
</button>
</p>
)}
</div>
)}
{!isKeyOnly && (
<div className="mt-2 flex w-full flex-col items-start justify-between gap-2 text-sm sm:flex-row sm:items-center sm:gap-0">
<span>
<span
className={cn(
'mr-0.5 inline-block 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':
!roadmapLimit,
},
)}
>
{roadmapLimitUsed} of {roadmapLimit}
</span>{' '}
or <span className="font-semibold">logging in</span>
</button>
)}
{isLoggedInUser && !openAPIKey && (
<button
onClick={() => setIsConfiguring(true)}
className="text-left rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white"
>
By-pass all limits by{' '}
<span className="font-semibold">
adding your own OpenAI API key
</span>
</button>
)}
roadmaps generated.
</span>
{!isLoggedInUser && (
<button
className="rounded-xl border border-current px-1.5 py-0.5 text-left text-sm font-medium text-blue-500 sm:text-center"
onClick={showLoginPopup}
>
Generate more by{' '}
<span className="font-semibold">
signing up (free, takes 2s)
</span>{' '}
or <span className="font-semibold">logging in</span>
</button>
)}
{isLoggedInUser && !openAPIKey && (
<button
onClick={() => setIsConfiguring(true)}
className="rounded-xl border border-current px-2 py-0.5 text-left text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white"
>
By-pass all limits by{' '}
<span className="font-semibold">
adding your own OpenAI API key
</span>
</button>
)}
{isLoggedInUser && 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"
>
<Cog size={15} />
Configure OpenAI key
</button>
)}
</div>
{isLoggedInUser && 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"
>
<Cog size={15} />
Configure OpenAI key
</button>
)}
</div>
)}
<form
onSubmit={handleSubmit}
className="my-3 flex w-full flex-col gap-2 sm:flex-row sm:items-center sm:justify-center"
>
<input
type="text"
autoFocus
placeholder="e.g. Try searching for Ansible or DevOps"
className="flex-grow rounded-md border border-gray-400 px-3 py-2 transition-colors focus:border-black focus:outline-none"
value={roadmapTerm}
@ -501,7 +534,8 @@ export function GenerateRoadmap() {
!roadmapLimit ||
!roadmapTerm ||
roadmapLimitUsed >= roadmapLimit ||
roadmapTerm === currentRoadmap?.term
roadmapTerm === currentRoadmap?.term ||
(isKeyOnly && !openAPIKey)
}
>
{roadmapLimit > 0 && canGenerateMore && (
@ -514,7 +548,7 @@ export function GenerateRoadmap() {
{roadmapLimit === 0 && <span>Please wait..</span>}
{roadmapLimit > 0 && !canGenerateMore && (
<span className="flex items-center text-sm">
<span className="flex items-center">
<Ban size={15} className="mr-2" />
Limit reached
</span>

@ -1,10 +1,6 @@
import { Modal } from '../Modal.tsx';
import { useEffect, useState } from 'react';
import {
deleteOpenAIKey,
getOpenAIKey,
saveOpenAIKey,
} from '../../lib/jwt.ts';
import { deleteOpenAIKey, getOpenAIKey, saveOpenAIKey } from '../../lib/jwt.ts';
import { cn } from '../../lib/classname.ts';
import { CloseIcon } from '../ReactIcons/CloseIcon.tsx';
import { useToast } from '../../hooks/use-toast.ts';
@ -121,6 +117,10 @@ export function OpenAISettings(props: OpenAISettingsProps) {
</button>
)}
</div>
<p className={'mb-2 mt-1 text-xs text-gray-500'}>
We do not store your API key on our servers.
</p>
{hasError && (
<p className="mt-2 text-sm text-red-500">
Please enter a valid OpenAI API key

@ -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,30 +93,57 @@ 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))
}
>
{(!limit || canGenerateMore) && (
{!isAuthenticatedUser && (
<>
<Wand size={20} />
Generate
</>
)}
{isAuthenticatedUser && (
<>
{(!limit || canGenerateMore) && (
<>
<Wand size={20} />
Generate
</>
)}
{limit > 0 && !canGenerateMore && (
<span className="flex items-center text-base">
<Ban size={15} className="mr-2" />
Limit reached
</span>
{limit > 0 && !canGenerateMore && (
<span className="flex items-center text-base">
<Ban size={15} className="mr-2" />
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,60 +159,118 @@ 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,
},
)}
>
{limitUsed} of {limit}
</span>{' '}
roadmaps.
</p>
<p className="flex min-h-[46px] sm:min-h-[26px] items-center text-sm">
{limit > 0 && !isLoggedIn() && (
{!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="rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white"
className="font-medium text-purple-600 underline underline-offset-2 hover:text-purple-800"
>
Generate more by{' '}
<span className="font-semibold">
signing up (free and takes 2 seconds)
</span>{' '}
or <span className="font-semibold">logging in</span>
</button>
)}
</p>
<p className="-mt-[75px] sm:-mt-[46px] flex min-h-[46px] sm:min-h-[26px] items-center text-sm">
{limit > 0 && isLoggedIn() && !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"
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"
>
By-pass all limits by{' '}
<span className="font-semibold">
adding your own OpenAI API key
</span>
</button>
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"
>
add your own OpenAI API key.
</button>
</p>
</>
)}
{openAPIKey && (
<p className={'text-center text-gray-500'}>
You have added your own OpenAI API key.{' '}
<button
onClick={() => setIsConfiguring(true)}
className="font-semibold text-purple-600 underline underline-offset-2"
>
Configure it here if you want.
</button>
</p>
)}
{limit > 0 && isLoggedIn() && 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"
<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"
>
<Cog size={15} />
Configure OpenAI key
</button>
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>
{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"
>
By-pass all limits by{' '}
<span className="font-semibold">
adding your own OpenAI API key
</span>
</button>
)}
{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"
>
<Cog size={15} />
Configure OpenAI key
</button>
)}
</p>
)}
</p>
</div>
</div>
)}
</div>
);
}

@ -0,0 +1 @@
export const IS_KEY_ONLY_ROADMAP_GENERATION = false;
Loading…
Cancel
Save