From 18deef46db5932f228183db8f29f73d9ed4a9408 Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Tue, 26 Mar 2024 21:40:26 +0600 Subject: [PATCH] feat: add increase generation limit options (#5388) * wip: limit increase options * feat: add increase AI limit options * fix: overflow issue * UI Updates * UI for bypassing limits * Refactor bypass limit --------- Co-authored-by: Kamran Ahmed --- .astro/settings.json | 5 + .../GenerateRoadmap/GenerateRoadmap.tsx | 30 +- .../GenerateRoadmap/IncreaseRoadmapLimit.tsx | 68 +++++ .../GenerateRoadmap/OpenAISettings.tsx | 266 +++++++++--------- .../GenerateRoadmap/PayToBypass.tsx | 164 +++++++++++ .../GenerateRoadmap/PickLimitOption.tsx | 50 ++++ .../GenerateRoadmap/ReferYourFriend.tsx | 84 ++++++ .../GenerateRoadmap/RoadmapSearch.tsx | 10 +- .../GenerateRoadmap/RoadmapTopicDetail.tsx | 4 +- src/components/Modal.tsx | 20 +- src/env.d.ts | 1 + src/lib/jwt.ts | 25 ++ 12 files changed, 577 insertions(+), 150 deletions(-) create mode 100644 .astro/settings.json create mode 100644 src/components/GenerateRoadmap/IncreaseRoadmapLimit.tsx create mode 100644 src/components/GenerateRoadmap/PayToBypass.tsx create mode 100644 src/components/GenerateRoadmap/PickLimitOption.tsx create mode 100644 src/components/GenerateRoadmap/ReferYourFriend.tsx diff --git a/.astro/settings.json b/.astro/settings.json new file mode 100644 index 000000000..bf82959bd --- /dev/null +++ b/.astro/settings.json @@ -0,0 +1,5 @@ +{ + "devToolbar": { + "enabled": false + } +} \ No newline at end of file diff --git a/src/components/GenerateRoadmap/GenerateRoadmap.tsx b/src/components/GenerateRoadmap/GenerateRoadmap.tsx index b9c1c0be3..5e492dc7e 100644 --- a/src/components/GenerateRoadmap/GenerateRoadmap.tsx +++ b/src/components/GenerateRoadmap/GenerateRoadmap.tsx @@ -16,6 +16,7 @@ import { getOpenAIKey, isLoggedIn, removeAuthToken, + setAIReferralCode, visitAIRoadmap, } from '../../lib/jwt'; import { RoadmapSearch } from './RoadmapSearch.tsx'; @@ -38,6 +39,7 @@ import { OpenAISettings } from './OpenAISettings.tsx'; import { IS_KEY_ONLY_ROADMAP_GENERATION } from '../../lib/ai.ts'; import { AITermSuggestionInput } from './AITermSuggestionInput.tsx'; import { useParams } from '../../hooks/use-params.ts'; +import { IncreaseRoadmapLimit } from './IncreaseRoadmapLimit.tsx'; import { AuthenticationForm } from '../AuthenticationFlow/AuthenticationForm.tsx'; export type GetAIRoadmapLimitResponse = { @@ -88,7 +90,10 @@ type GetAIRoadmapResponse = { export function GenerateRoadmap() { const roadmapContainerRef = useRef(null); - const { id: roadmapId } = getUrlParams() as { id: string }; + const { id: roadmapId, rc: referralCode } = getUrlParams() as { + id: string; + rc?: string; + }; const toast = useToast(); const [hasSubmitted, setHasSubmitted] = useState(false); @@ -108,7 +113,7 @@ export function GenerateRoadmap() { const [roadmapTopicLimitUsed, setRoadmapTopicLimitUsed] = useState(0); const [isConfiguring, setIsConfiguring] = useState(false); - const openAPIKey = getOpenAIKey(); + const [openAPIKey, setOpenAPIKey] = useState(getOpenAIKey()); const isKeyOnly = IS_KEY_ONLY_ROADMAP_GENERATION; const isAuthenticatedUser = isLoggedIn(); @@ -362,6 +367,17 @@ export function GenerateRoadmap() { loadAIRoadmapLimit().finally(() => {}); }, []); + useEffect(() => { + if (!referralCode || isLoggedIn()) { + deleteUrlParam('rc'); + return; + } + + setAIReferralCode(referralCode); + deleteUrlParam('rc'); + showLoginPopup(); + }, []); + useEffect(() => { if (!roadmapId || roadmapId === currentRoadmap?.id) { return; @@ -393,13 +409,13 @@ export function GenerateRoadmap() { const pageUrl = `https://roadmap.sh/ai?id=${roadmapId}`; const canGenerateMore = roadmapLimitUsed < roadmapLimit; - const isLoggedInUser = isLoggedIn(); return ( <> {isConfiguring && ( - { + setOpenAPIKey(getOpenAIKey()); setIsConfiguring(false); loadAIRoadmapLimit().finally(() => null); }} @@ -488,10 +504,8 @@ export function GenerateRoadmap() { 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{' '} - - adding your own OpenAI API key - + Need to generate more?{' '} + Click here. )} diff --git a/src/components/GenerateRoadmap/IncreaseRoadmapLimit.tsx b/src/components/GenerateRoadmap/IncreaseRoadmapLimit.tsx new file mode 100644 index 000000000..ca6873b74 --- /dev/null +++ b/src/components/GenerateRoadmap/IncreaseRoadmapLimit.tsx @@ -0,0 +1,68 @@ +import { useState } from 'react'; +import { cn } from '../../lib/classname'; +import { ChevronUp } from 'lucide-react'; +import { Modal } from '../Modal'; +import { ReferYourFriend } from './ReferYourFriend'; +import { OpenAISettings } from './OpenAISettings'; +import { PayToBypass } from './PayToBypass'; +import { PickLimitOption } from './PickLimitOption'; +import { getOpenAIKey } from '../../lib/jwt.ts'; + +export type IncreaseTab = 'api-key' | 'refer-friends' | 'payment'; + +export const increaseLimitTabs: { + key: IncreaseTab; + title: string; +}[] = [ + { key: 'api-key', title: 'Add your own API Key' }, + { key: 'refer-friends', title: 'Refer your Friends' }, + { key: 'payment', title: 'Pay to Bypass the limit' }, +]; + +type IncreaseRoadmapLimitProps = { + onClose: () => void; +}; +export function IncreaseRoadmapLimit(props: IncreaseRoadmapLimitProps) { + const { onClose } = props; + + const openAPIKey = getOpenAIKey(); + const [activeTab, setActiveTab] = useState( + openAPIKey ? 'api-key' : null, + ); + + return ( + + {!activeTab && ( + + )} + + {activeTab === 'api-key' && ( + { + onClose(); + }} + onBack={() => setActiveTab(null)} + /> + )} + {activeTab === 'refer-friends' && ( + setActiveTab(null)} /> + )} + {activeTab === 'payment' && ( + setActiveTab(null)} + onClose={() => { + onClose(); + }} + /> + )} + + ); +} diff --git a/src/components/GenerateRoadmap/OpenAISettings.tsx b/src/components/GenerateRoadmap/OpenAISettings.tsx index 29c8e0820..5cb6f897c 100644 --- a/src/components/GenerateRoadmap/OpenAISettings.tsx +++ b/src/components/GenerateRoadmap/OpenAISettings.tsx @@ -5,13 +5,15 @@ import { cn } from '../../lib/classname.ts'; import { CloseIcon } from '../ReactIcons/CloseIcon.tsx'; import { useToast } from '../../hooks/use-toast.ts'; import { httpPost } from '../../lib/http.ts'; +import { ChevronLeft } from 'lucide-react'; type OpenAISettingsProps = { onClose: () => void; + onBack: () => void; }; export function OpenAISettings(props: OpenAISettingsProps) { - const { onClose } = props; + const { onClose, onBack } = props; const [defaultOpenAIKey, setDefaultOpenAIKey] = useState(''); @@ -28,141 +30,143 @@ export function OpenAISettings(props: OpenAISettingsProps) { }, []); return ( - -
-

OpenAI Settings

-
-

- AI Roadmap generator uses OpenAI's GPT-4 model to generate roadmaps. -

- -

- - Create an account on OpenAI - {' '} - and enter your API key below to enable the AI Roadmap generator -

- -
{ - e.preventDefault(); +
+ + +

OpenAI Settings

+

+ Add your OpenAI API key below to bypass the roadmap generation limits. + You can use your existing key or{' '} + + create a new one here + + . +

+ + { + e.preventDefault(); + setHasError(false); + + const normalizedKey = openaiApiKey.trim(); + if (!normalizedKey) { + deleteOpenAIKey(); + toast.success('OpenAI API key removed'); + onClose(); + return; + } + + if (!normalizedKey.startsWith('sk-')) { + setHasError(true); + return; + } + + setIsLoading(true); + const { response, error } = await httpPost( + `${import.meta.env.PUBLIC_API_URL}/v1-validate-openai-key`, + { + key: normalizedKey, + }, + ); + + if (error) { + setHasError(true); + setIsLoading(false); + return; + } + + // Save the API key to cookies + saveOpenAIKey(normalizedKey); + toast.success('OpenAI API key saved'); + onClose(); + }} + > +
+ { setHasError(false); - - const normalizedKey = openaiApiKey.trim(); - if (!normalizedKey) { - deleteOpenAIKey(); - toast.success('OpenAI API key removed'); - onClose(); - return; - } - - if (!normalizedKey.startsWith('sk-')) { - setHasError(true); - return; - } - - setIsLoading(true); - const { response, error } = await httpPost( - `${import.meta.env.PUBLIC_API_URL}/v1-validate-openai-key`, - { - key: normalizedKey, - }, - ); - - if (error) { - setHasError(true); - setIsLoading(false); - return; - } - - // Save the API key to cookies - saveOpenAIKey(normalizedKey); - toast.success('OpenAI API key saved'); - onClose(); + setOpenaiApiKey((e.target as HTMLInputElement).value); }} - > -
- { - setHasError(false); - setOpenaiApiKey((e.target as HTMLInputElement).value); - }} - /> - - {openaiApiKey && ( - - )} -
-

- We do not store your API key on our servers. -

- - {hasError && ( -

- Please enter a valid OpenAI API key -

- )} + /> + + {openaiApiKey && ( - {!defaultOpenAIKey && ( - - )} - {defaultOpenAIKey && ( - - )} - + )}
-
- +

+ We do not store your API key on our servers. +

+ + {hasError && ( +

+ Please enter a valid OpenAI API key +

+ )} + + {!defaultOpenAIKey && ( + + )} + {defaultOpenAIKey && ( + + )} + +
); } diff --git a/src/components/GenerateRoadmap/PayToBypass.tsx b/src/components/GenerateRoadmap/PayToBypass.tsx new file mode 100644 index 000000000..ed0db74f8 --- /dev/null +++ b/src/components/GenerateRoadmap/PayToBypass.tsx @@ -0,0 +1,164 @@ +import { ChevronLeft } from 'lucide-react'; +import { useAuth } from '../../hooks/use-auth'; + +type PayToBypassProps = { + onBack: () => void; + onClose: () => void; +}; + +export function PayToBypass(props: PayToBypassProps) { + const { onBack, onClose } = props; + const user = useAuth(); + + const userId = 'entry.1665642993'; + const nameId = 'entry.527005328'; + const emailId = 'entry.982906376'; + const amountId = 'entry.1826002937'; + const roadmapCountId = 'entry.1161404075'; + const usageId = 'entry.535914744'; + const feedbackId = 'entry.1024388959'; + + return ( +
+ + +

Pay to Bypass

+

+ Tell us more about how you will be using this. +

+ +
+ + + + +
+ + +
+
+ +