mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-16 06:04:24 +02:00
Block UI for guest users
This commit is contained in:
@@ -35,6 +35,7 @@ import { cn } from '../../lib/classname.ts';
|
|||||||
import { RoadmapTopicDetail } from './RoadmapTopicDetail.tsx';
|
import { RoadmapTopicDetail } from './RoadmapTopicDetail.tsx';
|
||||||
import { AIRoadmapAlert } from './AIRoadmapAlert.tsx';
|
import { AIRoadmapAlert } from './AIRoadmapAlert.tsx';
|
||||||
import { OpenAISettings } from './OpenAISettings.tsx';
|
import { OpenAISettings } from './OpenAISettings.tsx';
|
||||||
|
import { IS_KEY_ONLY_ROADMAP_GENERATION } from '../../lib/ai.ts';
|
||||||
|
|
||||||
export type GetAIRoadmapLimitResponse = {
|
export type GetAIRoadmapLimitResponse = {
|
||||||
used: number;
|
used: number;
|
||||||
@@ -104,6 +105,7 @@ export function GenerateRoadmap() {
|
|||||||
const [isConfiguring, setIsConfiguring] = useState(false);
|
const [isConfiguring, setIsConfiguring] = useState(false);
|
||||||
|
|
||||||
const openAPIKey = getOpenAIKey();
|
const openAPIKey = getOpenAIKey();
|
||||||
|
const isKeyOnly = IS_KEY_ONLY_ROADMAP_GENERATION;
|
||||||
|
|
||||||
const renderRoadmap = async (roadmap: string) => {
|
const renderRoadmap = async (roadmap: string) => {
|
||||||
const { nodes, edges } = generateAIRoadmapFromText(roadmap);
|
const { nodes, edges } = generateAIRoadmapFromText(roadmap);
|
||||||
@@ -372,6 +374,7 @@ export function GenerateRoadmap() {
|
|||||||
limit={roadmapLimit}
|
limit={roadmapLimit}
|
||||||
limitUsed={roadmapLimitUsed}
|
limitUsed={roadmapLimitUsed}
|
||||||
loadAIRoadmapLimit={loadAIRoadmapLimit}
|
loadAIRoadmapLimit={loadAIRoadmapLimit}
|
||||||
|
isKeyOnly={isKeyOnly}
|
||||||
onLoadTerm={(term: string) => {
|
onLoadTerm={(term: string) => {
|
||||||
setRoadmapTerm(term);
|
setRoadmapTerm(term);
|
||||||
loadTermRoadmap(term).finally(() => {});
|
loadTermRoadmap(term).finally(() => {});
|
||||||
@@ -427,63 +430,93 @@ export function GenerateRoadmap() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{!isLoading && (
|
{!isLoading && (
|
||||||
<div className="container flex flex-grow flex-col items-center">
|
<div className="container flex flex-grow flex-col items-start">
|
||||||
<AIRoadmapAlert />
|
<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">
|
{isKeyOnly && (
|
||||||
<span>
|
<div className="flex flex-row gap-4">
|
||||||
<span
|
{!openAPIKey && (
|
||||||
className={cn(
|
<p className={'text-left text-red-500'}>
|
||||||
'mr-0.5 inline-block rounded-xl border px-1.5 text-center text-sm tabular-nums text-gray-800',
|
We have hit the limit for AI roadmap generation. Please
|
||||||
{
|
try again tomorrow or{' '}
|
||||||
'animate-pulse border-zinc-300 bg-zinc-300 text-zinc-300':
|
<button
|
||||||
!roadmapLimit,
|
onClick={() => setIsConfiguring(true)}
|
||||||
},
|
className="font-semibold text-purple-600 underline underline-offset-2"
|
||||||
)}
|
>
|
||||||
>
|
add your own OpenAI API key
|
||||||
{roadmapLimitUsed} of {roadmapLimit}
|
</button>
|
||||||
</span>{' '}
|
</p>
|
||||||
roadmaps generated.
|
)}
|
||||||
</span>
|
{openAPIKey && (
|
||||||
{!isLoggedInUser && (
|
<p className={'text-left text-gray-500'}>
|
||||||
<button
|
You have added your own OpenAI API key.{' '}
|
||||||
className="rounded-xl border border-current px-1.5 py-0.5 text-left text-sm font-medium text-blue-500 sm:text-center"
|
<button
|
||||||
onClick={showLoginPopup}
|
onClick={() => setIsConfiguring(true)}
|
||||||
>
|
className="font-semibold text-purple-600 underline underline-offset-2"
|
||||||
Generate more by{' '}
|
>
|
||||||
<span className="font-semibold">
|
Configure it here if you want.
|
||||||
signing up (free, takes 2s)
|
</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>{' '}
|
</span>{' '}
|
||||||
or <span className="font-semibold">logging in</span>
|
roadmaps generated.
|
||||||
</button>
|
</span>
|
||||||
)}
|
{!isLoggedInUser && (
|
||||||
{isLoggedInUser && !openAPIKey && (
|
<button
|
||||||
<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={() => setIsConfiguring(true)}
|
onClick={showLoginPopup}
|
||||||
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"
|
>
|
||||||
>
|
Generate more by{' '}
|
||||||
By-pass all limits by{' '}
|
<span className="font-semibold">
|
||||||
<span className="font-semibold">
|
signing up (free, takes 2s)
|
||||||
adding your own OpenAI API key
|
</span>{' '}
|
||||||
</span>
|
or <span className="font-semibold">logging in</span>
|
||||||
</button>
|
</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 && (
|
{isLoggedInUser && openAPIKey && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsConfiguring(true)}
|
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"
|
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} />
|
<Cog size={15} />
|
||||||
Configure OpenAI key
|
Configure OpenAI key
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="my-3 flex w-full flex-col gap-2 sm:flex-row sm:items-center sm:justify-center"
|
className="my-3 flex w-full flex-col gap-2 sm:flex-row sm:items-center sm:justify-center"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
autoFocus
|
||||||
placeholder="e.g. Try searching for Ansible or DevOps"
|
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"
|
className="flex-grow rounded-md border border-gray-400 px-3 py-2 transition-colors focus:border-black focus:outline-none"
|
||||||
value={roadmapTerm}
|
value={roadmapTerm}
|
||||||
@@ -501,7 +534,8 @@ export function GenerateRoadmap() {
|
|||||||
!roadmapLimit ||
|
!roadmapLimit ||
|
||||||
!roadmapTerm ||
|
!roadmapTerm ||
|
||||||
roadmapLimitUsed >= roadmapLimit ||
|
roadmapLimitUsed >= roadmapLimit ||
|
||||||
roadmapTerm === currentRoadmap?.term
|
roadmapTerm === currentRoadmap?.term ||
|
||||||
|
(isKeyOnly && !openAPIKey)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{roadmapLimit > 0 && canGenerateMore && (
|
{roadmapLimit > 0 && canGenerateMore && (
|
||||||
@@ -514,7 +548,7 @@ export function GenerateRoadmap() {
|
|||||||
{roadmapLimit === 0 && <span>Please wait..</span>}
|
{roadmapLimit === 0 && <span>Please wait..</span>}
|
||||||
|
|
||||||
{roadmapLimit > 0 && !canGenerateMore && (
|
{roadmapLimit > 0 && !canGenerateMore && (
|
||||||
<span className="flex items-center text-sm">
|
<span className="flex items-center">
|
||||||
<Ban size={15} className="mr-2" />
|
<Ban size={15} className="mr-2" />
|
||||||
Limit reached
|
Limit reached
|
||||||
</span>
|
</span>
|
||||||
|
@@ -1,10 +1,6 @@
|
|||||||
import { Modal } from '../Modal.tsx';
|
import { Modal } from '../Modal.tsx';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import {
|
import { deleteOpenAIKey, getOpenAIKey, saveOpenAIKey } from '../../lib/jwt.ts';
|
||||||
deleteOpenAIKey,
|
|
||||||
getOpenAIKey,
|
|
||||||
saveOpenAIKey,
|
|
||||||
} from '../../lib/jwt.ts';
|
|
||||||
import { cn } from '../../lib/classname.ts';
|
import { cn } from '../../lib/classname.ts';
|
||||||
import { CloseIcon } from '../ReactIcons/CloseIcon.tsx';
|
import { CloseIcon } from '../ReactIcons/CloseIcon.tsx';
|
||||||
import { useToast } from '../../hooks/use-toast.ts';
|
import { useToast } from '../../hooks/use-toast.ts';
|
||||||
@@ -121,6 +117,10 @@ export function OpenAISettings(props: OpenAISettingsProps) {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<p className={'mb-2 mt-1 text-xs text-gray-500'}>
|
||||||
|
We do not store your API key on our servers.
|
||||||
|
</p>
|
||||||
|
|
||||||
{hasError && (
|
{hasError && (
|
||||||
<p className="mt-2 text-sm text-red-500">
|
<p className="mt-2 text-sm text-red-500">
|
||||||
Please enter a valid OpenAI API key
|
Please enter a valid OpenAI API key
|
||||||
|
@@ -21,6 +21,7 @@ type RoadmapSearchProps = {
|
|||||||
onLoadTerm: (topic: string) => void;
|
onLoadTerm: (topic: string) => void;
|
||||||
limit: number;
|
limit: number;
|
||||||
limitUsed: number;
|
limitUsed: number;
|
||||||
|
isKeyOnly: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RoadmapSearch(props: RoadmapSearchProps) {
|
export function RoadmapSearch(props: RoadmapSearchProps) {
|
||||||
@@ -32,16 +33,18 @@ export function RoadmapSearch(props: RoadmapSearchProps) {
|
|||||||
limitUsed = 0,
|
limitUsed = 0,
|
||||||
onLoadTerm,
|
onLoadTerm,
|
||||||
loadAIRoadmapLimit,
|
loadAIRoadmapLimit,
|
||||||
|
isKeyOnly,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const canGenerateMore = limitUsed < limit;
|
const canGenerateMore = limitUsed < limit;
|
||||||
const [isConfiguring, setIsConfiguring] = useState(false);
|
const [isConfiguring, setIsConfiguring] = useState(false);
|
||||||
const openAPIKey = getOpenAIKey();
|
const openAPIKey = getOpenAIKey();
|
||||||
|
const isAuthenticatedUser = isLoggedIn();
|
||||||
|
|
||||||
const randomTerms = ['OAuth', 'APIs', 'UX Design', 'gRPC'];
|
const randomTerms = ['OAuth', 'APIs', 'UX Design', 'gRPC'];
|
||||||
|
|
||||||
return (
|
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 && (
|
{isConfiguring && (
|
||||||
<OpenAISettings
|
<OpenAISettings
|
||||||
onClose={() => {
|
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">
|
<h1 className="relative text-2xl font-medium sm:text-3xl">
|
||||||
<span className="hidden sm:inline">Generate roadmaps with AI</span>
|
<span className="hidden sm:inline">Generate roadmaps with AI</span>
|
||||||
<span className="inline sm:hidden">AI Roadmap Generator</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',
|
'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: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} />
|
<Wand size={20} />
|
||||||
Generate
|
Generate
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{isAuthenticatedUser && (
|
||||||
|
<>
|
||||||
|
{(!limit || canGenerateMore) && (
|
||||||
|
<>
|
||||||
|
<Wand size={20} />
|
||||||
|
Generate
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{limit > 0 && !canGenerateMore && (
|
{limit > 0 && !canGenerateMore && (
|
||||||
<span className="flex items-center text-base">
|
<span className="flex items-center text-base">
|
||||||
<Ban size={15} className="mr-2" />
|
<Ban size={15} className="mr-2" />
|
||||||
Limit reached
|
Limit reached
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</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) => (
|
{randomTerms.map((term) => (
|
||||||
<button
|
<button
|
||||||
key={term}
|
key={term}
|
||||||
disabled={!limit || !canGenerateMore}
|
disabled={isAuthenticatedUser && (!limit || !canGenerateMore)}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (!isAuthenticatedUser) {
|
||||||
|
showLoginPopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
onLoadTerm(term);
|
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"
|
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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-12 flex flex-col items-center gap-4">
|
{!isAuthenticatedUser && (
|
||||||
<p className="text-gray-500 text-center">
|
<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">
|
||||||
You have generated{' '}
|
<p className={'text-center text-gray-500'}>
|
||||||
<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() && (
|
|
||||||
<button
|
<button
|
||||||
onClick={showLoginPopup}
|
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{' '}
|
Sign up (free and takes 2s) or login
|
||||||
<span className="font-semibold">
|
</button>{' '}
|
||||||
signing up (free and takes 2 seconds)
|
to start generating roadmaps. Or explore the ones made by the
|
||||||
</span>{' '}
|
community.
|
||||||
or <span className="font-semibold">logging in</span>
|
</p>
|
||||||
</button>
|
<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"
|
||||||
|
>
|
||||||
|
add your own OpenAI API key.
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</p>
|
{openAPIKey && (
|
||||||
<p className="-mt-[75px] sm:-mt-[46px] flex min-h-[46px] sm:min-h-[26px] items-center text-sm">
|
<p className={'text-center text-gray-500'}>
|
||||||
{limit > 0 && isLoggedIn() && !openAPIKey && (
|
You have added your own OpenAI API key.{' '}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsConfiguring(true)}
|
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"
|
className="font-semibold text-purple-600 underline underline-offset-2"
|
||||||
>
|
>
|
||||||
By-pass all limits by{' '}
|
Configure it here if you want.
|
||||||
<span className="font-semibold">
|
</button>
|
||||||
adding your own OpenAI API key
|
</p>
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{limit > 0 && isLoggedIn() && openAPIKey && (
|
<p className="flex flex-col gap-2 text-center text-gray-500 sm:flex-row">
|
||||||
<button
|
<a
|
||||||
onClick={() => setIsConfiguring(true)}
|
href="/ai/explore"
|
||||||
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"
|
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} />
|
Explore AI Roadmaps <Telescope size={15} />
|
||||||
Configure OpenAI key
|
</a>
|
||||||
</button>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
1
src/lib/ai.ts
Normal file
1
src/lib/ai.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const IS_KEY_ONLY_ROADMAP_GENERATION = false;
|
Reference in New Issue
Block a user