Compare commits

...

68 Commits

Author SHA1 Message Date
Arik Chakma
41184fdb17 feat: add explore page slug 2024-04-20 00:50:04 +06:00
Arik Chakma
e4c0bad2c0 Merge branch 'master' into feat/ai-rdm-slug 2024-04-20 00:35:44 +06:00
Arik Chakma
73e46ad0d6 feat: add ai roadmap slug 2024-04-20 00:25:08 +06:00
Arik Chakma
5d82c1080f feat: add slug navigation 2024-04-19 15:20:54 +06:00
Arik Chakma
9166b56af7 wip 2024-04-19 02:05:08 +06:00
Arik Chakma
231013744a wip 2024-04-18 11:36:02 +06:00
Arik Chakma
b6a6d6394b wip 2024-04-17 16:39:03 +06:00
Kamran Ahmed
ed99d97468 Update deployment workflow 2024-04-11 22:14:37 +01:00
Kamran Ahmed
fd55a52839 Update deployment workflow 2024-04-11 22:12:54 +01:00
Kamran Ahmed
5c423e149f Do not pre-render stats pages 2024-04-11 19:58:24 +01:00
Kamran Ahmed
9d63306fc9 Use hybrid mode of rendering 2024-04-11 19:33:53 +01:00
Kamran Ahmed
1d04dd9970 Add error detail 2024-04-11 03:23:31 +01:00
Kamran Ahmed
ab2b7688f1 Update fetch depth 2024-04-10 23:29:13 +01:00
Arik Chakma
fe651b21bc feat: on blur check username 2024-04-11 00:39:31 +06:00
Kamran Ahmed
d4a563fe49 Merge branch 'master' into feat/ssr 2024-04-10 10:02:32 +01:00
Kamran Ahmed
7fd319a989 Add private profile banner 2024-04-10 03:36:20 +01:00
Kamran Ahmed
edaa108900 Fix vite warnings 2024-04-10 03:26:35 +01:00
Kamran Ahmed
51b8e58d7c Add public profile page 2024-04-09 23:14:07 +01:00
Kamran Ahmed
634af33756 Revamp UI for profile page 2024-04-09 22:40:51 +01:00
Kamran Ahmed
e7277585d0 Error page for user 2024-04-09 21:26:05 +01:00
Kamran Ahmed
384d73363d Refactor public profile form 2024-04-09 20:54:30 +01:00
Kamran Ahmed
3a18207b32 Minor text update 2024-04-09 17:59:32 +01:00
Kamran Ahmed
a42d9cfb64 Update UI for public form 2024-04-09 17:56:47 +01:00
Kamran Ahmed
f0c8121d42 Rearrange sidebar items 2024-04-09 17:36:40 +01:00
Kamran Ahmed
b2b5bfc8a1 Refactor public profile form 2024-04-09 17:34:03 +01:00
Kamran Ahmed
7600b1af89 Refactor profile and form 2024-04-09 17:18:53 +01:00
Kamran Ahmed
3cb31da862 Changes to form 2024-04-09 15:10:00 +01:00
Kamran Ahmed
5f97ea8e4f Fix beginner roadmaps not working 2024-04-09 13:42:18 +01:00
Arik Chakma
7f26ce5e2f fix: implement author page 2024-04-06 00:27:12 +06:00
Arik Chakma
fa483763f1 fix: update pnpm lock 2024-04-06 00:05:08 +06:00
Arik Chakma
90924eefd2 Merge branch 'master' into feat/ssr 2024-04-06 00:04:23 +06:00
Arik Chakma
7db85cc91b feat: add email icon 2024-04-03 23:42:58 +06:00
Arik Chakma
4300d08f70 wip 2024-04-03 21:23:09 +06:00
Arik Chakma
a9d8869510 fix: add disable props 2024-04-03 19:49:55 +06:00
Arik Chakma
6866dae012 wip: remove getStaticPaths 2024-03-25 17:33:28 +06:00
Arik Chakma
005c66c60a fix: heatmap focus 2024-03-23 01:10:55 +06:00
Arik Chakma
4acdbaebea fix: improper avatars 2024-03-06 01:38:17 +06:00
Kamran Ahmed
fae496bdb7 Upgrade dependencies 2024-02-26 10:41:34 +00:00
Kamran Ahmed
9a49a04934 Update dependencies 2024-02-26 10:31:51 +00:00
Kamran Ahmed
f8e5833f6a Merge branch 'master' into feat/ssr 2024-02-26 10:31:36 +00:00
Arik Chakma
4e0d12af7d fix: breakpoint for roadmaps 2024-02-25 01:08:11 +06:00
Arik Chakma
3c785f7044 fix: remove title margin 2024-02-25 00:54:39 +06:00
Arik Chakma
0473c8e460 fix: increase progress gap 2024-02-24 02:57:59 +06:00
Arik Chakma
09266aed5a refactor: remove logs 2024-02-24 02:46:21 +06:00
Arik Chakma
5e48b4f7cd feat: implement user profile roadmap page 2024-02-24 02:43:00 +06:00
Arik Chakma
d95d30caf8 feat: implement profile form 2024-02-20 23:21:58 +06:00
Arik Chakma
c72b0b2cf2 feat: user public profile page 2024-02-15 04:06:14 +06:00
Arik Chakma
afad1ce3c3 fix: replace with toast 2024-02-14 01:50:04 +06:00
Arik Chakma
e006f63e14 feat: update public profile 2024-02-14 01:34:58 +06:00
Arik Chakma
707a7242fc Merge branch 'master' into feat/ssr 2024-02-12 13:03:52 +06:00
Arik Chakma
324b17e878 feat: show roadmap progress 2024-02-12 12:55:23 +06:00
Arik Chakma
5e5f7427f9 fix: user public page 2024-02-12 02:21:31 +06:00
Arik Chakma
8bf0b51065 feat: username route 2024-02-11 04:50:16 +06:00
Arik Chakma
13c55faa71 feat: custom roadmap slug routes (#4987)
* feat: replace roadmap slug

* fix: remove roadmap slug
2024-01-06 22:03:00 +05:00
Kamran Ahmed
febeb6f586 Minor UI change 2023-12-29 07:21:09 +05:00
Kamran Ahmed
9c82a4d35c Add custom roadmap page 2023-12-29 06:53:56 +05:00
Kamran Ahmed
4a3030948f Fix stats 2023-12-28 11:39:27 +05:00
Kamran Ahmed
3d012f9c64 Fix absolute import 2023-12-28 11:28:42 +05:00
Kamran Ahmed
7856a78bba Refactor best practices 2023-12-28 11:06:28 +05:00
Kamran Ahmed
7b4ced400c Refactor faqs 2023-12-28 11:03:34 +05:00
Kamran Ahmed
bd01586e8e Handle SSR for static pages 2023-12-28 10:56:51 +05:00
Kamran Ahmed
33af126728 Fix best practice topics not loading 2023-12-28 10:41:45 +05:00
Kamran Ahmed
e48fa4f593 Rename 2023-12-28 10:06:48 +05:00
Kamran Ahmed
f00f5d16ea Fix generate-renderer issue 2023-12-28 09:47:29 +05:00
Arik Chakma
4fcf1f6d50 fix: redirect to the error page 2023-12-28 08:24:20 +06:00
Kamran Ahmed
7c1c295c63 Add pre-render 2023-12-27 21:39:03 +05:00
Kamran Ahmed
39e55a06e8 Add stats and health endpoints 2023-12-27 19:55:44 +05:00
Kamran Ahmed
8d6facd983 Update 2023-12-26 14:53:02 +05:00
6 changed files with 148 additions and 31 deletions

20
src/api/ai-roadmap.ts Normal file
View File

@@ -0,0 +1,20 @@
import { type APIContext } from 'astro';
import { api } from './api.ts';
export type GetAIRoadmapBySlugResponse = {
id: string;
term: string;
title: string;
data: string;
isAuthenticatedUser: boolean;
};
export function aiRoadmapApi(context: APIContext) {
return {
getAIRoadmapBySlug: async function (roadmapSlug: string) {
return api(context).get<GetAIRoadmapBySlugResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-ai-roadmap-by-slug/${roadmapSlug}`,
);
},
};
}

View File

@@ -26,7 +26,8 @@ export function AIRoadmapsList(props: AIRoadmapsListProps) {
return (
<ul className="mb-4 grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
{roadmaps.map((roadmap) => {
const roadmapLink = `/ai?id=${roadmap._id}`;
const roadmapLink = `/ai/${roadmap.slug}`;
return (
<a
key={roadmap._id}

View File

@@ -19,6 +19,7 @@ export interface AIRoadmapDocument {
term: string;
title: string;
data: string;
slug: string;
viewCount: number;
createdAt: Date;
updatedAt: Date;

View File

@@ -50,6 +50,7 @@ export type GetAIRoadmapLimitResponse = {
};
const ROADMAP_ID_REGEX = new RegExp('@ROADMAPID:(\\w+)@');
const ROADMAP_SLUG_REGEX = new RegExp(/@ROADMAPSLUG:([\w-]+)@/);
export type RoadmapNodeDetails = {
nodeId: string;
@@ -87,22 +88,39 @@ type GetAIRoadmapResponse = {
data: string;
};
export function GenerateRoadmap() {
type GenerateRoadmapProps = {
roadmapId?: string;
slug?: string;
isAuthenticatedUser?: boolean;
};
export function GenerateRoadmap(props: GenerateRoadmapProps) {
const {
roadmapId: defaultRoadmapId,
slug: defaultRoadmapSlug,
isAuthenticatedUser = isLoggedIn(),
} = props;
const roadmapContainerRef = useRef<HTMLDivElement>(null);
const { id: roadmapId, rc: referralCode } = getUrlParams() as {
id: string;
const { rc: referralCode } = getUrlParams() as {
rc?: string;
};
const toast = useToast();
const [hasSubmitted, setHasSubmitted] = useState<boolean>(false);
const [roadmapId, setRoadmapId] = useState<string | undefined>(
defaultRoadmapId,
);
const [roadmapSlug, setRoadmapSlug] = useState<string | undefined>(
defaultRoadmapSlug,
);
const [hasSubmitted, setHasSubmitted] = useState<boolean>(Boolean(roadmapId));
const [isLoading, setIsLoading] = useState(false);
const [isLoadingResults, setIsLoadingResults] = useState(false);
const [roadmapTerm, setRoadmapTerm] = useState('');
const [generatedRoadmapContent, setGeneratedRoadmapContent] = useState('');
const [currentRoadmap, setCurrentRoadmap] =
useState<GetAIRoadmapResponse | null>(null);
const [generatedRoadmapContent, setGeneratedRoadmapContent] = useState('');
const [selectedNode, setSelectedNode] = useState<RoadmapNodeDetails | null>(
null,
);
@@ -117,7 +135,6 @@ export function GenerateRoadmap() {
getOpenAIKey(),
);
const isKeyOnly = IS_KEY_ONLY_ROADMAP_GENERATION;
const isAuthenticatedUser = isLoggedIn();
const renderRoadmap = async (roadmap: string) => {
const { nodes, edges } = generateAIRoadmapFromText(roadmap);
@@ -134,6 +151,7 @@ export function GenerateRoadmap() {
deleteUrlParam('id');
setCurrentRoadmap(null);
const origin = window.location.origin;
const response = await fetch(
`${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-roadmap`,
{
@@ -169,13 +187,31 @@ export function GenerateRoadmap() {
await readAIRoadmapStream(reader, {
onStream: async (result) => {
if (result.includes('@ROADMAPID')) {
if (result.includes('@ROADMAPID') || result.includes('@ROADMAPSLUG')) {
// @ROADMAPID: is a special token that we use to identify the roadmap
// @ROADMAPID:1234@ is the format, we will remove the token and the id
// and replace it with a empty string
const roadmapId = result.match(ROADMAP_ID_REGEX)?.[1] || '';
setUrlParams({ id: roadmapId });
result = result.replace(ROADMAP_ID_REGEX, '');
const roadmapSlug = result.match(ROADMAP_SLUG_REGEX)?.[1] || '';
if (roadmapSlug) {
window.history.pushState(
{
roadmapId,
roadmapSlug,
},
'',
`${origin}/ai/${roadmapSlug}`,
);
}
result = result
.replace(ROADMAP_ID_REGEX, '')
.replace(ROADMAP_SLUG_REGEX, '');
setRoadmapId(roadmapId);
setRoadmapSlug(roadmapSlug);
const roadmapTitle =
result.trim().split('\n')[0]?.replace('#', '')?.trim() || term;
setRoadmapTerm(roadmapTitle);
@@ -190,7 +226,10 @@ export function GenerateRoadmap() {
await renderRoadmap(result);
},
onStreamEnd: async (result) => {
result = result.replace(ROADMAP_ID_REGEX, '');
result = result
.replace(ROADMAP_ID_REGEX, '')
.replace(ROADMAP_SLUG_REGEX, '');
setGeneratedRoadmapContent(result);
loadAIRoadmapLimit().finally(() => {});
},
@@ -322,7 +361,7 @@ export function GenerateRoadmap() {
data,
});
setRoadmapTerm(term);
setRoadmapTerm(title || term);
setGeneratedRoadmapContent(data);
visitAIRoadmap(roadmapId);
};
@@ -385,12 +424,35 @@ export function GenerateRoadmap() {
return;
}
setHasSubmitted(true);
loadAIRoadmap(roadmapId).finally(() => {
pageProgressMessage.set('');
});
}, [roadmapId, currentRoadmap]);
useEffect(() => {
const handlePopState = (e: PopStateEvent) => {
const { roadmapId, roadmapSlug } = e.state || {};
if (!roadmapId || !roadmapSlug) {
window.location.reload();
return;
}
setIsLoading(true);
setHasSubmitted(true);
setRoadmapId(roadmapId);
setRoadmapSlug(roadmapSlug);
loadAIRoadmap(roadmapId).finally(() => {
setIsLoading(false);
pageProgressMessage.set('');
});
};
window.addEventListener('popstate', handlePopState);
return () => {
window.removeEventListener('popstate', handlePopState);
};
}, []);
if (!hasSubmitted) {
return (
<RoadmapSearch
@@ -401,7 +463,7 @@ export function GenerateRoadmap() {
limitUsed={roadmapLimitUsed}
loadAIRoadmapLimit={loadAIRoadmapLimit}
isKeyOnly={isKeyOnly}
onLoadTerm={(term: string) => {
onLoadTerm={(term) => {
setRoadmapTerm(term);
loadTermRoadmap(term).finally(() => {});
}}
@@ -409,7 +471,7 @@ export function GenerateRoadmap() {
);
}
const pageUrl = `https://roadmap.sh/ai?id=${roadmapId}`;
const pageUrl = `https://roadmap.sh/ai/${roadmapSlug}`;
const canGenerateMore = roadmapLimitUsed < roadmapLimit;
return (
@@ -524,7 +586,7 @@ export function GenerateRoadmap() {
)}
{!isAuthenticatedUser && (
<button
className="rounded-xl border border-current px-2.5 py-0.5 text-left text-sm font-medium text-blue-500 transition-colors hover:bg-blue-500 hover:text-white sm:text-center"
className="mt-2 rounded-xl border border-current px-2.5 py-0.5 text-left text-sm font-medium text-blue-500 transition-colors hover:bg-blue-500 hover:text-white sm:text-center"
onClick={showLoginPopup}
>
Login to generate your own roadmaps

View File

@@ -3,14 +3,13 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { useKeydown } from '../../hooks/use-keydown';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { markdownToHtml } from '../../lib/markdown';
import {Ban, Cog, Contact, FileText, User, UserRound, X} from 'lucide-react';
import { Ban, Cog, Contact, FileText, User, UserRound, X } from 'lucide-react';
import { Spinner } from '../ReactIcons/Spinner';
import type { RoadmapNodeDetails } from './GenerateRoadmap';
import { getOpenAIKey, isLoggedIn, removeAuthToken } from '../../lib/jwt';
import { readAIRoadmapContentStream } from '../../helper/read-stream';
import { cn } from '../../lib/classname';
import { showLoginPopup } from '../../lib/popup';
import { OpenAISettings } from './OpenAISettings.tsx';
type RoadmapTopicDetailProps = RoadmapNodeDetails & {
onClose?: () => void;
@@ -179,19 +178,19 @@ export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) {
)}
{!isLoggedIn() && (
<div className="flex h-full flex-col items-center justify-center">
<Contact className="h-14 w-14 text-gray-200 mb-3.5" />
<h2 className='font-medium text-xl'>You must be logged in</h2>
<p className="text-base text-gray-400">
Sign up or login to generate topic content.
</p>
<button
className="mt-3.5 text-base font-medium text-white bg-black px-3 py-2 rounded-md w-full max-w-[300px]"
onClick={showLoginPopup}
>
Sign up / Login
</button>
</div>
<div className="flex h-full flex-col items-center justify-center">
<Contact className="mb-3.5 h-14 w-14 text-gray-200" />
<h2 className="text-xl font-medium">You must be logged in</h2>
<p className="text-base text-gray-400">
Sign up or login to generate topic content.
</p>
<button
className="mt-3.5 w-full max-w-[300px] rounded-md bg-black px-3 py-2 text-base font-medium text-white"
onClick={showLoginPopup}
>
Sign up / Login
</button>
</div>
)}
{!isLoading && !error && (

View File

@@ -0,0 +1,34 @@
---
import { aiRoadmapApi } from '../../api/ai-roadmap';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { GenerateRoadmap } from '../../components/GenerateRoadmap/GenerateRoadmap';
export const prerender = false;
interface Params extends Record<string, string | undefined> {
aiRoadmapSlug: string;
}
const { aiRoadmapSlug } = Astro.params as Params;
if (!aiRoadmapSlug) {
return Astro.redirect('/404');
}
const aiRoadmapClient = aiRoadmapApi(Astro as any);
const { response: roadmap, error } =
await aiRoadmapClient.getAIRoadmapBySlug(aiRoadmapSlug);
let errorMessage = '';
if (error || !roadmap) {
errorMessage = error?.message || 'Error loading AI Roadmap';
}
const title = roadmap?.title || 'Roadmap AI';
---
<BaseLayout title={title}>
<GenerateRoadmap
roadmapId={roadmap?.id}
isAuthenticatedUser={roadmap?.isAuthenticatedUser}
client:load
/>
</BaseLayout>