chore: upgrade to astro v3 (#4437)

pull/4442/head
Kamran Ahmed 1 year ago committed by GitHub
parent b8c90948f9
commit 03d0a32fd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      astro.config.mjs
  2. 10
      package.json
  3. 855
      pnpm-lock.yaml
  4. 2
      src/components/Activity/EmptyActivity.tsx
  5. 6
      src/components/AuthenticationFlow/EmailLoginForm.tsx
  6. 11
      src/components/AuthenticationFlow/EmailSignupForm.tsx
  7. 4
      src/components/AuthenticationFlow/ForgotPasswordForm.tsx
  8. 8
      src/components/AuthenticationFlow/GitHubButton.tsx
  9. 2
      src/components/AuthenticationFlow/GoogleButton.tsx
  10. 7
      src/components/AuthenticationFlow/LinkedInButton.tsx
  11. 2
      src/components/AuthenticationFlow/LoginPopup.astro
  12. 6
      src/components/AuthenticationFlow/ResetPasswordForm.tsx
  13. 11
      src/components/AuthenticationFlow/TriggerVerifyAccount.tsx
  14. 4
      src/components/AuthenticationFlow/VerificationEmailMessage.tsx
  15. 2
      src/components/Befriend.tsx
  16. 44
      src/components/Breadcrumbs.astro
  17. 16
      src/components/CommandMenu/CommandMenu.tsx
  18. 4
      src/components/CreateTeam/CreateTeamForm.tsx
  19. 2
      src/components/CreateTeam/NotDropdown.tsx
  20. 2
      src/components/CreateTeam/SelectRoadmapModal.tsx
  21. 18
      src/components/CreateTeam/Step1.tsx
  22. 2
      src/components/CreateTeam/Step2.tsx
  23. 2
      src/components/CreateTeam/Step3.tsx
  24. 8
      src/components/DeleteAccount/DeleteAccountForm.tsx
  25. 4
      src/components/DeleteTeamPopup.tsx
  26. 2
      src/components/Feedback/SubmitFeedbackPopup.tsx
  27. 3
      src/components/FrameRenderer/renderer.ts
  28. 4
      src/components/Friends/EmptyFriends.tsx
  29. 2
      src/components/Friends/FriendsPage.tsx
  30. 13
      src/components/Friends/InviteFriendPopup.tsx
  31. 1
      src/components/Friends/SidebarFriendsCounter.tsx
  32. 4
      src/components/Notification/NotificationPage.tsx
  33. 2
      src/components/OpenSourceBanner.astro
  34. 2
      src/components/PageProgress.tsx
  35. 2
      src/components/PageSponsor.tsx
  36. 4
      src/components/RespondInviteForm.tsx
  37. 2
      src/components/RoadCard/Editor.tsx
  38. 2
      src/components/RoadCard/RoadCardPage.tsx
  39. 2
      src/components/TeamDropdown/TeamDropdown.tsx
  40. 4
      src/components/TeamMembers/InviteMemberPopup.tsx
  41. 4
      src/components/TeamMembers/LeaveTeamPopup.tsx
  42. 2
      src/components/TeamMembers/MemberActionDropdown.tsx
  43. 4
      src/components/TeamMembers/UpdateMemberPopup.tsx
  44. 2
      src/components/TeamProgress/GroupRoadmapItem.tsx
  45. 6
      src/components/TeamProgress/MemberProgressModal.tsx
  46. 6
      src/components/TeamRoadmaps.tsx
  47. 5
      src/components/TeamSettings/UpdateTeamForm.tsx
  48. 16
      src/components/TeamSidebar.tsx
  49. 2
      src/components/TeamVersions/TeamVersions.tsx
  50. 9
      src/components/TopicDetail/TopicDetail.tsx
  51. 7
      src/components/TopicDetail/TopicProgressButton.tsx
  52. 2
      src/components/UpdatePassword/UpdatePasswordForm.tsx
  53. 2
      src/components/UpdateProfile/UpdateProfileForm.tsx
  54. 2
      src/components/UpdateProfile/UploadProfilePicture.tsx
  55. 2
      src/components/UserProgress/UserProgressModal.tsx
  56. 2
      src/data/roadmaps/cpp/content/116-build-systems/102-ninja.md
  57. 4
      src/data/roadmaps/cpp/content/116-build-systems/index.md
  58. 2
      src/data/roadmaps/cpp/content/libraries/103-protobuf.md
  59. 2
      src/data/roadmaps/docker/content/106-building-container-images/102-image-size-and-security.md
  60. 2
      src/data/roadmaps/postgresql-dba/content/103-installation-and-setup/102-connect-using-psql.md
  61. 2
      src/data/roadmaps/postgresql-dba/content/103-installation-and-setup/106-using-pgctlcluster.md
  62. 16
      src/lib/best-practice-topic.ts
  63. 1
      src/lib/best-pratice.ts
  64. 158
      src/lib/roadmap-topic.ts
  65. 4
      src/pages/[roadmapId]/[...topicId].astro
  66. 2
      src/pages/[roadmapId]/index.astro
  67. 11
      src/pages/[roadmapId]/index.json.ts
  68. 71
      src/pages/[roadmapId]/topics.astro
  69. 28
      src/pages/best-practices/[bestPracticeId]/index.astro
  70. 11
      src/pages/best-practices/[bestPracticeId]/index.json.ts
  71. 2
      src/pages/login.astro
  72. 14
      src/pages/pages.json.ts
  73. 2
      src/pages/questions/[questionGroupId].astro
  74. 2
      src/pages/reset-password.astro
  75. 2
      src/pages/signup.astro
  76. 2
      src/styles/global.css
  77. 1
      tsconfig.json

@ -45,22 +45,6 @@ export default defineConfig({
format: 'file',
},
integrations: [
{
name: 'client-authenticated',
hooks: {
'astro:config:setup'(options) {
options.addClientDirective({
name: 'authenticated',
entrypoint: fileURLToPath(
new URL(
'./src/directives/client-authenticated.mjs',
import.meta.url
)
),
});
},
},
},
tailwind({
config: {
applyBaseStyles: false,
@ -71,6 +55,7 @@ export default defineConfig({
serialize: serializeSitemap,
}),
compress({
HTML: false,
CSS: false,
JavaScript: false,
}),

@ -4,7 +4,7 @@
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"dev": "astro dev --port 3000",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
@ -21,14 +21,14 @@
"test:e2e": "playwright test"
},
"dependencies": {
"@astrojs/react": "^2.2.2",
"@astrojs/react": "^3.0.0",
"@astrojs/sitemap": "^1.3.3",
"@astrojs/tailwind": "^3.1.3",
"@astrojs/tailwind": "^5.0.0",
"@fingerprintjs/fingerprintjs": "^3.4.1",
"@nanostores/react": "^0.7.1",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"astro": "^2.6.6",
"astro": "^3.0.5",
"astro-compress": "^2.0.8",
"jose": "^4.14.4",
"js-cookie": "^3.0.5",
@ -40,7 +40,7 @@
"rehype-external-links": "^2.1.0",
"roadmap-renderer": "^1.0.6",
"slugify": "^1.6.6",
"tailwindcss": "^3.3.2"
"tailwindcss": "^3.0.23"
},
"devDependencies": {
"@playwright/test": "^1.35.1",

File diff suppressed because it is too large Load Diff

@ -6,7 +6,7 @@ export function EmptyActivity() {
<div className="flex flex-col items-center p-7 text-center">
<img
alt="no roadmaps"
src={RoadmapIcon}
src={RoadmapIcon.src}
className="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10"
/>
<h2 className="text-lg sm:text-xl font-bold">No Progress</h2>

@ -4,7 +4,7 @@ import { useState } from 'react';
import { httpPost } from '../../lib/http';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
const EmailLoginForm = () => {
export function EmailLoginForm() {
const [email, setEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [error, setError] = useState('');
@ -99,6 +99,4 @@ const EmailLoginForm = () => {
</button>
</form>
);
};
export default EmailLoginForm;
}

@ -1,8 +1,7 @@
import type { FunctionComponent } from 'preact';
import { useState } from 'react';
import { type FormEvent, useState } from 'react';
import { httpPost } from '../../lib/http';
const EmailSignupForm: FunctionComponent = () => {
export function EmailSignupForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState('');
@ -10,7 +9,7 @@ const EmailSignupForm: FunctionComponent = () => {
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const onSubmit = async (e: Event) => {
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
@ -98,6 +97,4 @@ const EmailSignupForm: FunctionComponent = () => {
</button>
</form>
);
};
export default EmailSignupForm;
}

@ -1,4 +1,4 @@
import { useState } from 'react';
import { type FormEvent, useState } from 'react';
import { httpPost } from '../../lib/http';
export function ForgotPasswordForm() {
@ -7,7 +7,7 @@ export function ForgotPasswordForm() {
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError('');

@ -1,5 +1,4 @@
import { useEffect, useState } from 'react';
import GitHubIcon from '../../icons/github.svg';
import SpinnerIcon from '../../icons/spinner.svg';
import Cookies from 'js-cookie';
@ -91,8 +90,9 @@ export function GitHubButton(props: GitHubButtonProps) {
// For non authentication pages, we want to redirect back to the page
// the user was on before they clicked the social login button
if (!['/login', '/signup'].includes(window.location.pathname)) {
const pagePath =
['/respond-invite', '/befriend'].includes(window.location.pathname)
const pagePath = ['/respond-invite', '/befriend'].includes(
window.location.pathname
)
? window.location.pathname + window.location.search
: window.location.pathname;
@ -111,7 +111,7 @@ export function GitHubButton(props: GitHubButtonProps) {
onClick={handleClick}
>
<img
src={icon as any}
src={icon.src}
alt="GitHub"
className={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
/>

@ -111,7 +111,7 @@ export function GoogleButton(props: GoogleButtonProps) {
onClick={handleClick}
>
<img
src={icon as any}
src={icon.src}
alt="Google"
className={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
/>

@ -86,8 +86,9 @@ export function LinkedInButton(props: LinkedInButtonProps) {
// For non authentication pages, we want to redirect back to the page
// the user was on before they clicked the social login button
if (!['/login', '/signup'].includes(window.location.pathname)) {
const pagePath =
['/respond-invite', '/befriend'].includes(window.location.pathname)
const pagePath = ['/respond-invite', '/befriend'].includes(
window.location.pathname
)
? window.location.pathname + window.location.search
: window.location.pathname;
@ -111,7 +112,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
onClick={handleClick}
>
<img
src={icon as any}
src={icon.src}
alt="Google"
className={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
/>

@ -1,6 +1,6 @@
---
import Popup from '../Popup/Popup.astro';
import EmailLoginForm from './EmailLoginForm';
import { EmailLoginForm } from './EmailLoginForm';
import Divider from './Divider.astro';
import { GitHubButton } from './GitHubButton';
import { GoogleButton } from './GoogleButton';

@ -1,9 +1,9 @@
import { useEffect, useState } from 'react';
import { type FormEvent, useEffect, useState } from 'react';
import { httpPost } from '../../lib/http';
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
export default function ResetPasswordForm() {
export function ResetPasswordForm() {
const [code, setCode] = useState('');
const [password, setPassword] = useState('');
const [passwordConfirm, setPasswordConfirm] = useState('');
@ -21,7 +21,7 @@ export default function ResetPasswordForm() {
}
}, []);
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);

@ -1,10 +1,9 @@
import SpinnerIcon from '../../icons/spinner.svg';
import ErrorIcon from '../../icons/error.svg';
import { useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
import { httpPost } from '../../lib/http';
import ErrorIcon from '../../icons/error.svg';
import SpinnerIcon from '../../icons/spinner.svg';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
export function TriggerVerifyAccount() {
const [isLoading, setIsLoading] = useState(true);
@ -59,14 +58,14 @@ export function TriggerVerifyAccount() {
{isLoading && (
<img
alt={'Please wait.'}
src={SpinnerIcon}
src={SpinnerIcon.src}
className={'mx-auto h-16 w-16 animate-spin'}
/>
)}
{error && (
<img
alt={'Please wait.'}
src={ErrorIcon}
src={ErrorIcon.src}
className={'mx-auto h-16 w-16'}
/>
)}

@ -1,5 +1,5 @@
import VerifyLetterIcon from '../../icons/verify-letter.svg';
import { useEffect, useState } from 'react';
import VerifyLetterIcon from '../../icons/verify-letter.svg';
import { httpPost } from '../../lib/http';
export function VerificationEmailMessage() {
@ -39,7 +39,7 @@ export function VerificationEmailMessage() {
<div className="mx-auto max-w-md text-center">
<img
alt="Verify Email"
src={VerifyLetterIcon as any}
src={VerifyLetterIcon.src}
className="mx-auto mb-4 h-20 w-40 sm:h-40"
/>
<h2 className="my-2 text-center text-xl font-semibold sm:my-5 sm:text-2xl">

@ -141,7 +141,7 @@ export function Befriend() {
const isMe = currentUser?.id === user.id;
return (
<div className="container max-w-[400px] text-center">
<div className="container !max-w-[400px] text-center">
<img
alt={'join team'}
src={userAvatar}

@ -1,44 +0,0 @@
---
import type { BreadcrumbItem } from '../lib/roadmap-topic';
export interface Props {
breadcrumbs: BreadcrumbItem[];
roadmapId: string;
}
const { breadcrumbs, roadmapId } = Astro.props;
---
<div class='py-7 pb-6'>
<!-- Desktop breadcrumbs -->
<p class='text-gray-500 container hidden sm:block'>
{
breadcrumbs.map((breadcrumb, counter) => {
const isLast = counter === breadcrumbs.length - 1;
if (!isLast) {
return (
<>
<a class='hover:text-gray-800' href={`${breadcrumb.url}`}>
{breadcrumb.title}
</a>
<span>&nbsp;&middot;&nbsp;</span>
</>
);
}
return <span class='text-gray-400'>{breadcrumb.title}</span>;
})
}
</p>
<!-- Mobile breadcrums -->
<p class='container block sm:hidden'>
<a
class='bg-gray-500 py-1.5 px-3 rounded-md text-white text-xs sm:text-sm font-medium hover:bg-gray-600'
href={`/${roadmapId}`}
>
&larr; Back to Topics List
</a>
</p>
</div>

@ -22,13 +22,13 @@ export type PageType = {
};
const defaultPages: PageType[] = [
{ id: 'home', url: '/', title: 'Home', group: 'Pages', icon: HomeIcon },
{ id: 'home', url: '/', title: 'Home', group: 'Pages', icon: HomeIcon.src },
{
id: 'account',
url: '/account',
title: 'Account',
group: 'Pages',
icon: UserIcon,
icon: UserIcon.src,
isProtected: true,
},
{
@ -36,7 +36,7 @@ const defaultPages: PageType[] = [
url: '/team',
title: 'Teams',
group: 'Pages',
icon: GroupIcon,
icon: GroupIcon.src,
isProtected: true,
},
{
@ -44,28 +44,28 @@ const defaultPages: PageType[] = [
url: '/roadmaps',
title: 'Roadmaps',
group: 'Pages',
icon: RoadmapIcon,
icon: RoadmapIcon.src,
},
{
id: 'best-practices',
url: '/best-practices',
title: 'Best Practices',
group: 'Pages',
icon: BestPracticesIcon,
icon: BestPracticesIcon.src,
},
{
id: 'guides',
url: '/guides',
title: 'Guides',
group: 'Pages',
icon: GuideIcon,
icon: GuideIcon.src,
},
{
id: 'videos',
url: '/videos',
title: 'Videos',
group: 'Pages',
icon: VideoIcon,
icon: VideoIcon.src,
},
];
@ -221,7 +221,7 @@ export function CommandMenu() {
{page.icon && (
<img
alt={page.title}
src={page.icon as any}
src={page.icon}
className="mr-2 h-4 w-4"
/>
)}

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { Stepper } from '../Stepper';
import { Step0, ValidTeamType } from './Step0';
import { Step1, ValidTeamSize } from './Step1';
import { Step0, type ValidTeamType } from './Step0';
import { Step1, type ValidTeamSize } from './Step1';
import { Step2 } from './Step2';
import { httpGet } from '../../lib/http';
import { getUrlParams, setUrlParams } from '../../lib/browser';

@ -39,7 +39,7 @@ export function NotDropdown(props: NotDropdownProps) {
<img
alt={singularName}
src={ChevronDownIcon}
src={ChevronDownIcon.src}
className={'relative top-[1px] h-[17px] w-[17px] opacity-40'}
/>
</div>

@ -79,7 +79,7 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
className="popup-close absolute right-2.5 top-3 ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-100 hover:text-gray-900"
onClick={onClose}
>
<img alt={'close'} src={CloseIcon} className="h-4 w-4" />
<img alt={'close'} src={CloseIcon.src} className="h-4 w-4" />
<span className="sr-only">Close modal</span>
</button>
<input

@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react';
import { AppError, httpPost, httpPut } from '../../lib/http';
import { type FormEvent, useEffect, useRef, useState } from 'react';
import { type AppError, httpPost, httpPut } from '../../lib/http';
import type { ValidTeamType } from './Step0';
import type { TeamDocument } from './CreateTeamForm';
import { NextButton } from './NextButton';
@ -49,7 +49,7 @@ export function Step1(props: Step1Props) {
team?.teamSize || ('' as any)
);
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
if (!name || !selectedTeamType) {
@ -124,7 +124,7 @@ export function Step1(props: Step1Props) {
<form onSubmit={handleSubmit}>
<div className="flex w-full flex-col">
<label
for="name"
htmlFor="name"
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]'
>
{selectedTeamType === 'company' ? 'Company Name' : 'Group Name'}
@ -147,7 +147,7 @@ export function Step1(props: Step1Props) {
{selectedTeamType === 'company' && (
<div className="mt-4 flex w-full flex-col">
<label
for="website"
htmlFor="website"
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]'
>
Website
@ -168,7 +168,7 @@ export function Step1(props: Step1Props) {
{selectedTeamType === 'company' && (
<div className="mt-4 flex w-full flex-col">
<label for="website" className="text-sm leading-none text-slate-500">
<label htmlFor="website" className="text-sm leading-none text-slate-500">
Company LinkedIn URL
</label>
<input
@ -187,7 +187,7 @@ export function Step1(props: Step1Props) {
)}
<div className="mt-4 flex w-full flex-col">
<label for="website" className="text-sm leading-none text-slate-500">
<label htmlFor="website" className="text-sm leading-none text-slate-500">
GitHub Organization URL
</label>
<input
@ -205,7 +205,7 @@ export function Step1(props: Step1Props) {
{selectedTeamType === 'company' && (
<div className="mt-4 flex w-full flex-col">
<label
for="team-size"
htmlFor="team-size"
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]'
>
Tech Team Size
@ -237,7 +237,7 @@ export function Step1(props: Step1Props) {
</div>
)}
<div className="mt-4 flex flex-col md:flex-row items-center justify-between gap-2">
<div className="mt-4 flex flex-col items-center justify-between gap-2 md:flex-row">
<button
type="button"
onClick={onBack}

@ -1,4 +1,4 @@
import { RoadmapSelector, TeamResourceConfig } from './RoadmapSelector';
import { RoadmapSelector, type TeamResourceConfig } from './RoadmapSelector';
import type { TeamDocument } from './CreateTeamForm';
type Step2Props = {

@ -1,7 +1,7 @@
import type { TeamDocument } from './CreateTeamForm';
import { NextButton } from './NextButton';
import { TrashIcon } from '../ReactIcons/TrashIcon';
import { AllowedRoles, RoleDropdown } from './RoleDropdown';
import { type AllowedRoles, RoleDropdown } from './RoleDropdown';
import { useEffect, useRef, useState } from 'react';
import { httpPost } from '../../lib/http';

@ -1,4 +1,4 @@
import {useEffect, useState} from 'react';
import { type FormEvent, useEffect, useState } from 'react';
import { httpDelete } from '../../lib/http';
import { logout } from '../Navigation/navigation';
@ -10,9 +10,9 @@ export function DeleteAccountForm() {
useEffect(() => {
setError('');
setConfirmationText('');
}, [])
}, []);
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError('');
@ -53,7 +53,7 @@ export function DeleteAccountForm() {
type="text"
name="delete-account"
id="delete-account"
className="mt-2 block w-full rounded-md border border-gray-300 py-2 px-3 outline-none placeholder:text-gray-400 focus:border-gray-400"
className="mt-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 focus:border-gray-400"
placeholder={'Type "delete" to confirm'}
required
autoFocus

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react';
import { type FormEvent, useEffect, useRef, useState } from 'react';
import { httpDelete } from '../lib/http';
import type { TeamDocument } from './CreateTeam/CreateTeamForm';
import { useTeamId } from '../hooks/use-team-id';
@ -34,7 +34,7 @@ export function DeleteTeamPopup(props: DeleteTeamPopupProps) {
inputEl.current?.focus();
}, []);
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError('');

@ -1,4 +1,4 @@
import { FormEvent, useEffect, useRef, useState } from 'react';
import { type FormEvent, useEffect, useRef, useState } from 'react';
import { useToast } from '../../hooks/use-toast';
import { useTeamId } from '../../hooks/use-team-id';
import { useOutsideClick } from '../../hooks/use-outside-click';

@ -5,10 +5,9 @@ import {
refreshProgressCounters,
renderResourceProgress,
renderTopicProgress,
ResourceProgressType,
ResourceType,
updateResourceProgress,
} from '../../lib/resource-progress';
import type { ResourceProgressType, ResourceType } from '../../lib/resource-progress';
import { pageProgressMessage } from '../../stores/page';
import { showLoginPopup } from '../../lib/popup';

@ -15,7 +15,7 @@ export function EmptyFriends(props: EmptyFriendsProps) {
<div className="mx-auto flex flex-col items-center p-7 text-center">
<img
alt="no friends"
src={UserPlusIcon as any}
src={UserPlusIcon.src}
className="mb-2 h-[60px] w-[60px] opacity-10 sm:h-[120px] sm:w-[120px]"
/>
<h2 className="text-lg font-bold sm:text-xl">Invite your Friends</h2>
@ -44,7 +44,7 @@ export function EmptyFriends(props: EmptyFriendsProps) {
copyText(befriendUrl);
}}
>
<img src={CopyIcon as any} className="h-4 w-4" alt="Invite Friends" />
<img src={CopyIcon.src} className="h-4 w-4" alt="Invite Friends" />
{isCopied ? 'Copied' : 'Copy'}
</button>
</div>

@ -188,7 +188,7 @@ export function FriendsPage() {
{filteredFriends.length === 0 && (
<div className="flex flex-col items-center justify-center py-12">
<img
src={UserIcon}
src={UserIcon.src}
alt="Empty Friends"
className="mb-3 w-12 opacity-20"
/>

@ -1,4 +1,5 @@
import { useEffect, useRef } from 'react';
import type { MouseEvent } from 'react';
import { useRef } from 'react';
import { useOutsideClick } from '../../hooks/use-outside-click';
import CopyIcon from '../../icons/copy.svg';
import { useCopyText } from '../../hooks/use-copy-text';
@ -38,8 +39,8 @@ export function InviteFriendPopup(props: InviteFriendPopupProps) {
readOnly={true}
className="mt-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 focus:border-gray-400"
value={befriendUrl}
onClick={(e) => {
e?.target?.select();
onClick={(e: MouseEvent<HTMLInputElement>) => {
(e?.target as HTMLInputElement)?.select();
copyText(befriendUrl);
}}
/>
@ -53,7 +54,11 @@ export function InviteFriendPopup(props: InviteFriendPopupProps) {
copyText(befriendUrl);
}}
>
<img src={CopyIcon} className="h-4 w-4" alt="Invite Friends" />
<img
src={CopyIcon.src}
className="h-4 w-4"
alt="Invite Friends"
/>
{isCopied ? 'Copied' : 'Copy URL'}
</button>
</div>

@ -1,5 +1,4 @@
import { httpGet } from '../../lib/http';
import type { TeamListResponse } from '../TeamDropdown/TeamDropdown';
import { useEffect, useState } from 'react';
type GetFriendCountsResponse = {

@ -91,14 +91,14 @@ export function NotificationPage() {
className="inline-flex border p-1 rounded hover:bg-gray-50 disabled:opacity-75"
onClick={() => respondInvitation('accept', notification?._id!)}
>
<img src={AcceptIcon} className="h-4 w-4" />
<img src={AcceptIcon.src} className="h-4 w-4" />
</button>
<button type="button"
disabled={isLoading}
className="inline-flex border p-1 rounded hover:bg-gray-50 disabled:opacity-75"
onClick={() => respondInvitation('reject', notification?._id!)}
>
<img src={XIcon} className="h-4 w-4" />
<img alt={'Close'} src={XIcon.src} className="h-4 w-4" />
</button>
</div>
</div>

@ -6,7 +6,7 @@ const starCount = await getFormattedStars('kamranahmedse/developer-roadmap');
---
<div class='py-6 sm:py-16 border-b border-t text-left sm:text-center bg-white'>
<div class='max-w-[600px] container'>
<div class='!max-w-[600px] container'>
<h2 class='text-2xl sm:text-5xl font-bold'>Community</h2>
<p class='text-gray-600 text-sm sm:text-lg leading-relaxed my-2.5 sm:my-5'>
roadmap.sh is the <a

@ -31,7 +31,7 @@ export function PageProgress(props: Props) {
<div className="fixed left-0 top-0 z-50 flex h-full w-full items-center justify-center bg-white bg-opacity-75">
<div className="flex items-center justify-center rounded-md border bg-white px-4 py-2 ">
<img
src={SpinnerIcon as any}
src={SpinnerIcon.src}
alt="Loading"
className="h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-4 sm:w-4"
/>

@ -101,7 +101,7 @@ export function PageSponsor(props: PageSponsorProps) {
sponsorHidden.set(true);
}}
>
<img alt="Close" className="h-4 w-4" src={CloseIcon as any} />
<img alt="Close" className="h-4 w-4" src={CloseIcon.src} />
</span>
<img
src={imageUrl}

@ -87,7 +87,7 @@ export function RespondInviteForm() {
<div className="container text-center">
<img
alt={'error'}
src={ErrorIcon}
src={ErrorIcon.src}
className="mx-auto mb-4 mt-24 w-20 opacity-20"
/>
@ -112,7 +112,7 @@ export function RespondInviteForm() {
<div className="container text-center">
<img
alt={'join team'}
src={BuildingIcon}
src={BuildingIcon.src}
className="mx-auto mb-4 mt-24 w-20 opacity-20"
/>

@ -24,7 +24,7 @@ export function Editor(props: EditorProps) {
</span>
)}
<img src={CopyIcon} alt="Copy" className="inline-block h-4 w-4" />
<img src={CopyIcon.src} alt="Copy" className="inline-block h-4 w-4" />
</button>
</div>
<textarea

@ -146,7 +146,7 @@ export function RoadCardPage() {
className="flex cursor-pointer items-center justify-center rounded border border-gray-300 p-1.5 px-2 text-sm font-medium disabled:bg-blue-50"
onClick={() => copyText(badgeUrl.toString())}
>
<img alt="Copy" src={CopyIcon} className="mr-1" />
<img alt="Copy" src={CopyIcon.src} className="mr-1" />
{isCopied ? 'Copied!' : 'Copy Link'}
</button>

@ -140,7 +140,7 @@ export function TeamDropdown() {
{isLoading && 'Loading ..'}
</span>
</div>
<img alt={'show dropdown'} src={ChevronDown} className="h-4 w-4" />
<img alt={'show dropdown'} src={ChevronDown.src} className="h-4 w-4" />
</button>
{showDropdown && (

@ -1,8 +1,8 @@
import { FormEvent, useEffect, useRef, useState } from 'react';
import { type FormEvent, useEffect, useRef, useState } from 'react';
import { httpPost } from '../../lib/http';
import { useTeamId } from '../../hooks/use-team-id';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { AllowedRoles, RoleDropdown } from '../CreateTeam/RoleDropdown';
import { type AllowedRoles, RoleDropdown } from '../CreateTeam/RoleDropdown';
type InviteMemberPopupProps = {
onInvited: () => void;

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react';
import { type FormEvent, useEffect, useRef, useState } from 'react';
import { httpDelete } from '../../lib/http';
import { useTeamId } from '../../hooks/use-team-id';
import { useOutsideClick } from '../../hooks/use-outside-click';
@ -23,7 +23,7 @@ export function LeaveTeamPopup(props: LeaveTeamPopupProps) {
confirmationEl?.current?.focus();
}, []);
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError('');

@ -81,7 +81,7 @@ export function MemberActionDropdown({
onClick={() => setIsOpen(!isOpen)}
className="ml-2 flex items-center opacity-60 transition-opacity hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-30"
>
<img alt="menu" src={MoreIcon} className="h-4 w-4" />
<img alt="menu" src={MoreIcon.src} className="h-4 w-4" />
</button>
{isOpen && (

@ -1,8 +1,8 @@
import { FormEvent, useRef, useState } from 'react';
import { type FormEvent, useRef, useState } from 'react';
import { httpPut } from '../../lib/http';
import { useTeamId } from '../../hooks/use-team-id';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { AllowedRoles, RoleDropdown } from '../CreateTeam/RoleDropdown';
import { type AllowedRoles, RoleDropdown } from '../CreateTeam/RoleDropdown';
import type { TeamMemberDocument } from './TeamMembersPage';
type InviteMemberPopupProps = {

@ -31,7 +31,7 @@ export function GroupRoadmapItem(props: GroupRoadmapItemProps) {
>
<img
alt={'link'}
src={ExternalLinkIcon}
src={ExternalLinkIcon.src}
className="ml-2 h-4 w-4 opacity-20 transition-opacity group-hover:opacity-100"
/>
</a>

@ -8,8 +8,8 @@ import type { TeamMember } from './TeamProgressPage';
import { httpGet } from '../../lib/http';
import {
renderTopicProgress,
ResourceProgressType,
ResourceType,
type ResourceProgressType,
type ResourceType,
updateResourceProgress,
} from '../../lib/resource-progress';
import CloseIcon from '../../icons/close.svg';
@ -413,7 +413,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
}`}
onClick={onClose}
>
<img alt={'close'} src={CloseIcon} className="h-4 w-4" />
<img alt={'close'} src={CloseIcon.src} className="h-4 w-4" />
<span className="sr-only">Close modal</span>
</button>
</div>

@ -188,7 +188,7 @@ export function TeamRoadmaps() {
{addRoadmapModal}
<img
alt="roadmap"
src={RoadmapIcon}
src={RoadmapIcon.src}
className="mb-4 h-24 w-24 opacity-10"
/>
<h3 className="mb-1 text-2xl font-bold text-gray-900">No roadmaps</h3>
@ -259,7 +259,7 @@ export function TeamRoadmaps() {
<img
alt={'link'}
src={ExternalLinkIcon}
src={ExternalLinkIcon.src}
className="ml-2 h-4 w-4 opacity-20 transition-opacity group-hover:opacity-100"
/>
</a>
@ -332,7 +332,7 @@ export function TeamRoadmaps() {
>
<img
alt="add"
src={PlusIcon}
src={PlusIcon.src}
className="mb-1 h-6 w-6 opacity-20 transition-opacity group-hover:opacity-100"
/>
<span className="text-sm text-gray-400 transition-colors focus:outline-none group-hover:text-black">

@ -1,4 +1,4 @@
import { FormEvent, useEffect, useState } from 'react';
import { type FormEvent, useEffect, useState } from 'react';
import { httpGet, httpPut } from '../../lib/http';
import { Spinner } from '../ReactIcons/Spinner';
import UploadProfilePicture from '../UpdateProfile/UploadProfilePicture';
@ -9,7 +9,6 @@ import { DeleteTeamPopup } from '../DeleteTeamPopup';
import { $isCurrentTeamAdmin } from '../../stores/team';
import { useStore } from '@nanostores/react';
import { useToast } from '../../hooks/use-toast';
export function UpdateTeamForm() {
const [isLoading, setIsLoading] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
@ -25,8 +24,6 @@ export function UpdateTeamForm() {
const [gitHub, setGitHub] = useState('');
const [teamType, setTeamType] = useState('');
const [teamSize, setTeamSize] = useState('');
const [roadmaps, setRoadmaps] = useState<string[]>([]);
const [bestPractices, setBestPractices] = useState<string[]>([]);
const validTeamSizes = [
'0-1',
'2-10',

@ -29,26 +29,26 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
title: 'Progress',
href: `/team/progress?t=${teamId}`,
id: 'progress',
icon: TeamProgress,
icon: TeamProgress.src,
},
{
title: 'Roadmaps',
href: `/team/roadmaps?t=${teamId}`,
id: 'roadmaps',
icon: MapIcon,
icon: MapIcon.src,
hasWarning: currentTeam?.roadmaps?.length === 0,
},
{
title: 'Members',
href: `/team/members?t=${teamId}`,
id: 'members',
icon: GroupIcon,
icon: GroupIcon.src,
},
{
title: 'Settings',
href: `/team/settings?t=${teamId}`,
id: 'settings',
icon: SettingsIcon,
icon: SettingsIcon.src,
},
];
@ -66,7 +66,7 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
sidebarLinks.find((sidebarLink) => sidebarLink.id === activePageId)
?.title
}
<img alt="menu" src={ChevronDown} className="h-4 w-4" />
<img alt="menu" src={ChevronDown.src} className="h-4 w-4" />
</button>
{menuShown && (
<ul
@ -80,7 +80,7 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
activePageId === 'team' ? 'bg-slate-100' : ''
}`}
>
<img alt={'teams'} src={GroupIcon} className={`mr-2 h-4 w-4`} />
<img alt={'teams'} src={GroupIcon.src} className={`mr-2 h-4 w-4`} />
Personal Account / Teams
</a>
</li>
@ -113,7 +113,7 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
>
<img
alt={'menu icon'}
src={ChatIcon}
src={ChatIcon.src}
className="mr-2 h-4 w-4"
/>
Send Feedback
@ -174,7 +174,7 @@ export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
className="mr-3 mt-4 flex items-center justify-center rounded-md border p-2 text-sm text-gray-500 transition-colors hover:border-gray-300 hover:bg-gray-50 hover:text-black"
onClick={() => setShowFeedbackPopup(true)}
>
<img alt={'feedback'} src={ChatIcon} className="mr-2 h-4 w-4" />
<img alt={'feedback'} src={ChatIcon.src} className="mr-2 h-4 w-4" />
Send Feedback
</button>
</nav>

@ -144,7 +144,7 @@ export function TeamVersions(props: TeamVersionsProps) {
</span>
<img
alt="Dropdown"
src={DropdownIcon as any}
src={DropdownIcon.src}
className="h-3 w-3 sm:h-4 sm:w-4"
/>
</div>

@ -12,9 +12,9 @@ import {
isTopicDone,
refreshProgressCounters,
renderTopicProgress,
ResourceType,
updateResourceProgress as updateResourceProgressApi,
} from '../../lib/resource-progress';
import type { ResourceType } from '../../lib/resource-progress';
import { pageProgressMessage, sponsorHidden } from '../../stores/page';
import { TopicProgressButton } from './TopicProgressButton';
import { ContributionForm } from './ContributionForm';
@ -147,7 +147,7 @@ export function TopicDetail() {
{isLoading && (
<div className="flex w-full justify-center">
<img
src={SpinnerIcon as any}
src={SpinnerIcon.src}
alt="Loading"
className="h-6 w-6 animate-spin fill-blue-600 text-gray-200 sm:h-12 sm:w-12"
/>
@ -192,7 +192,7 @@ export function TopicDetail() {
setIsContributing(false);
}}
>
<img alt="Close" className="h-5 w-5" src={CloseIcon as any} />
<img alt="Close" className="h-5 w-5" src={CloseIcon.src} />
</button>
</div>
@ -206,7 +206,8 @@ export function TopicDetail() {
{/* Contribution */}
<div className="mt-8 flex-1 border-t">
<p className="mb-2 mt-2 text-sm leading-relaxed text-gray-400">
Help others learn by submitting links to learn more about this topic{' '}
Help others learn by submitting links to learn more about this
topic{' '}
</p>
<button
onClick={() => {

@ -5,13 +5,12 @@ import DownIcon from '../../icons/down.svg';
import SpinnerIcon from '../../icons/spinner.svg';
import { isLoggedIn } from '../../lib/jwt';
import {
ResourceProgressType,
ResourceType,
getTopicStatus,
refreshProgressCounters,
renderTopicProgress,
updateResourceProgress,
} from '../../lib/resource-progress';
import type { ResourceProgressType, ResourceType } from '../../lib/resource-progress';
import { showLoginPopup } from '../../lib/popup';
import { useToast } from '../../hooks/use-toast';
@ -165,7 +164,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
if (isUpdatingProgress) {
return (
<button className="inline-flex cursor-default items-center rounded-md border border-gray-300 bg-white p-1 px-2 text-sm text-black">
<img alt="Check" className="h-4 w-4 animate-spin" src={SpinnerIcon} />
<img alt="Check" className="h-4 w-4 animate-spin" src={SpinnerIcon.src} />
<span className="ml-2">Updating Status..</span>
</button>
);
@ -189,7 +188,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
onClick={() => setShowChangeStatus(true)}
>
<span className="mr-0.5">Update Status</span>
<img alt="Check" className="h-4 w-4" src={DownIcon} />
<img alt="Check" className="h-4 w-4" src={DownIcon.src} />
</button>
{showChangeStatus && (

@ -1,4 +1,4 @@
import { FormEvent, useEffect, useState } from 'react';
import { type FormEvent, useEffect, useState } from 'react';
import { httpGet, httpPost } from '../../lib/http';
import { pageProgressMessage } from '../../stores/page';

@ -1,4 +1,4 @@
import { FormEvent, useEffect, useState } from 'react';
import { type FormEvent, useEffect, useState } from 'react';
import { httpGet, httpPost } from '../../lib/http';
import { pageProgressMessage } from '../../stores/page';
import UploadProfilePicture from './UploadProfilePicture';

@ -1,5 +1,5 @@
import Cookies from 'js-cookie';
import { ChangeEvent, FormEvent, useEffect, useRef, useState } from 'react';
import { type ChangeEvent, type FormEvent, useEffect, useRef, useState } from 'react';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
interface PreviewFile extends File {

@ -292,7 +292,7 @@ export function UserProgressModal(props: ProgressMapProps) {
className={`absolute right-2.5 top-3 ml-auto inline-flex items-center rounded-lg bg-gray-100 bg-transparent p-1.5 text-sm text-gray-400 hover:text-gray-900 lg:hidden`}
onClick={onClose}
>
<img alt={'close'} src={CloseIcon as any} className="h-4 w-4" />
<img alt={'close'} src={CloseIcon.src} className="h-4 w-4" />
<span className="sr-only">Close modal</span>
</button>
</div>

@ -4,7 +4,7 @@ Ninja is a small build system with a focus on speed. It is designed to handle la
Ninja build files are typically named `build.ninja` and contain rules, build statements, and variable declarations. Here's a simple example of a Ninja build file for a C++ project:
```ninja
```
# Variable declarations
cxx = g++
cflags = -Wall -Wextra -std=c++17

@ -6,7 +6,7 @@ A build system is a collection of tools and utilities that automate the process
Code example:
```Makefile
```
# Makefile
CXX = g++
CPPFLAGS = -Wall -std=c++11
@ -25,7 +25,7 @@ A build system is a collection of tools and utilities that automate the process
Code example:
```CMake
```
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(HelloWorld)

@ -8,7 +8,7 @@ Here is a brief summary of protobuf and how to use it in C++:
*Example:*
```protobuf
```
syntax = "proto3";
message Person {

@ -46,7 +46,7 @@ When building container images, it's essential to be aware of both image size an
- **Use `.dockerignore` file:** Use a `.dockerignore` file to exclude unnecessary files from the build context that might cause cache invalidation and increase the final image size.
```dockerignore
```
node_modules
npm-debug.log
```

@ -28,7 +28,7 @@ Here are some basic commands to help you interact with your PostgreSQL database
- To execute an SQL query, simply type it at the prompt followed by a semicolon (`;`), and hit enter. For example:
```SQL
```sql
mydb=> SELECT * FROM mytable;
```

@ -12,7 +12,7 @@
The basic syntax for `pg_ctlcluster` is as follows:
```text
```
pg_ctlcluster <version> <cluster name> <action> [<options>]
```

@ -64,19 +64,3 @@ export async function getAllBestPracticeTopicFiles(): Promise<
return mapping;
}
/**
* Gets the the topics for a given best practice
*
* @param bestPracticeId BestPractice id for which you want the topics
*
* @returns Promise<TopicFileType[]>
*/
export async function getTopicsByBestPracticeId(
bestPracticeId: string
): Promise<BestPracticeTopicFileType[]> {
const topicFileMapping = await getAllBestPracticeTopicFiles();
const allTopics = Object.values(topicFileMapping);
return allTopics.filter((topic) => topic.bestPracticeId === bestPracticeId);
}

@ -57,7 +57,6 @@ export async function getBestPracticeIds() {
/**
* Gets all the best practice files
*
* @param tag Tag assigned to best practice
* @returns Promisified BestPracticeFileType[]
*/
export async function getAllBestPractices(): Promise<BestPracticeFileType[]> {

@ -1,5 +1,5 @@
import type {MarkdownFileType} from './file';
import type {RoadmapFrontmatter} from './roadmap';
import type { MarkdownFileType } from './file';
import type { RoadmapFrontmatter } from './roadmap';
// Generates URL from the topic file path e.g.
// -> /src/data/roadmaps/vue/content/102-ecosystem/102-ssr/101-nuxt-js.md
@ -15,61 +15,12 @@ function generateTopicUrl(filePath: string) {
.replace(/\.md$/, ''); // Remove `.md` from the end of file
}
/**
* Generates breadcrumbs for the given topic URL from the given topic file details
*
* @param topicUrl Topic URL for which breadcrumbs are required
* @param topicFiles Topic file mapping to read the topic data from
*/
function generateBreadcrumbs(
topicUrl: string,
topicFiles: Record<string, RoadmapTopicFileType>
): BreadcrumbItem[] {
// We need to collect all the pages with permalinks to generate breadcrumbs
// e.g. /backend/internet/how-does-internet-work/http
// /backend
// /backend/internet
// /backend/internet/how-does-internet-work
// /backend/internet/how-does-internet-work/http
const urlParts = topicUrl.split('/');
const breadcrumbUrls = [];
const subLinks = [];
for (let counter = 0; counter < urlParts.length; counter++) {
subLinks.push(urlParts[counter]);
// Skip the following
// -> [ '' ]
// -> [ '', 'vue' ]
if (subLinks.length > 2) {
breadcrumbUrls.push(subLinks.join('/'));
}
}
return breadcrumbUrls.map((breadCrumbUrl): BreadcrumbItem => {
const topicFile = topicFiles[breadCrumbUrl];
const topicFileContent = topicFile?.file;
const firstHeading = topicFileContent?.getHeadings()?.[0];
return {title: firstHeading?.text, url: breadCrumbUrl};
});
}
export type BreadcrumbItem = {
title: string;
url: string;
};
export interface RoadmapTopicFileType {
url: string;
heading: string;
file: MarkdownFileType;
roadmap: RoadmapFrontmatter;
roadmapId: string;
breadcrumbs: BreadcrumbItem[];
}
/**
@ -107,113 +58,8 @@ export async function getRoadmapTopicFiles(): Promise<
file: fileContent,
roadmap: currentRoadmap.frontmatter,
roadmapId: roadmapId,
breadcrumbs: [],
};
}
// Populate breadcrumbs inside the mapping
Object.keys(mapping).forEach((topicUrl) => {
const {
roadmap: currentRoadmap,
roadmapId,
file: currentTopic,
} = mapping[topicUrl];
const roadmapUrl = `/${roadmapId}`;
// Breadcrumbs for the file
mapping[topicUrl].breadcrumbs = [
{
title: 'Roadmaps',
url: '/roadmaps',
},
{
title: currentRoadmap.briefTitle,
url: `${roadmapUrl}`,
},
{
title: 'Topics',
url: `${roadmapUrl}/topics`,
},
...generateBreadcrumbs(topicUrl, mapping),
];
});
return mapping;
}
// [
// '/frontend/internet/how-does-the-internet-work',
// '/frontend/internet/what-is-http',
// '/frontend/internet/browsers-and-how-they-work',
// '/frontend/internet/dns-and-how-it-works',
// '/frontend/internet/what-is-domain-name',
// '/frontend/internet/what-is-hosting',
// '/frontend/internet',
// '/frontend/html/learn-the-basics',
// '/frontend/html/writing-semantic-html',
// '/frontend/html/forms-and-validations',
// '/frontend/html/conventions-and-best-practices',
// '/frontend/html/accessibility',
// '/frontend/html/seo-basics',
// '/frontend/html',
// '/frontend/css/learn-the-basics',
// '/frontend/css/making-layouts',
// '/frontend/css/responsive-design-and-media-queries',
// '/frontend/css',
// '/frontend/javascript/syntax-and-basic-constructs',
// '/frontend/javascript/learn-dom-manipulation',
// '/frontend/javascript/learn-fetch-api-ajax-xhr',
// '/frontend/javascript/es6-and-modular-javascript',
// '/frontend/javascript/concepts',
// '/frontend/javascript',
// '/frontend/version-control-systems/basic-usage-of-git',
// '/frontend/version-control-systems'
// ]
async function sortTopics(
topics: RoadmapTopicFileType[]
): Promise<RoadmapTopicFileType[]> {
let sortedTopics: RoadmapTopicFileType[] = [];
// For each of the topic, find its place in the sorted topics
for (let i = 0; i < topics.length; i++) {
const currTopic = topics[i];
const currUrl = currTopic.url;
let isPlaced = false;
// Find the first sorted topic which starts with the current topic
for (let j = 0; j < sortedTopics.length; j++) {
const sortedTopic = sortedTopics[j];
const sortedUrl = sortedTopic.url;
// Insert before the current URL and break
if (sortedUrl.startsWith(`${currUrl}/`)) {
sortedTopics.splice(j, 0, currTopic);
isPlaced = true;
break;
}
}
if (!isPlaced) {
sortedTopics.push(currTopic);
}
}
return sortedTopics;
}
/**
* Gets the the topics for a given roadmap
* @param roadmapId Roadmap id for which you want the topics
* @returns Promise<TopicFileType[]>
*/
export async function getTopicsByRoadmapId(
roadmapId: string
): Promise<RoadmapTopicFileType[]> {
const topicFileMapping = await getRoadmapTopicFiles();
const allTopics = Object.values(topicFileMapping);
const roadmapTopics = allTopics.filter(
(topic) => topic.roadmapId === roadmapId
);
return sortTopics(roadmapTopics);
}

@ -1,5 +1,4 @@
---
import Breadcrumbs from '../../components/Breadcrumbs.astro';
import RoadmapBanner from '../../components/RoadmapBanner.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getRoadmapTopicFiles,RoadmapTopicFileType } from '../../lib/roadmap-topic';
@ -23,7 +22,7 @@ export async function getStaticPaths() {
}
const { topicId } = Astro.params;
const { file, breadcrumbs, roadmapId, roadmap, heading } = Astro.props as RoadmapTopicFileType;
const { file, roadmapId, roadmap, heading } = Astro.props as RoadmapTopicFileType;
const gitHubBaseUrl = 'https://github.com/kamranahmedse/developer-roadmap/blob/master/src/data';
const gitHubFullUrl = file.file.replace(/^.+\/src\/data/, `${gitHubBaseUrl}/`);
@ -38,7 +37,6 @@ const gitHubRelativeUrl = file.file.replace(/^.+\/src\/data/, 'src/data');
>
<RoadmapBanner roadmapId={roadmapId} roadmap={roadmap} />
<div class='bg-gray-50'>
<Breadcrumbs breadcrumbs={breadcrumbs} roadmapId={roadmapId} />
<div class='container pb-16 prose prose-p:mt-0 prose-h1:mb-4 prose-h2:mb-3 prose-h2:mt-0'>
<main id='main-content'>

@ -91,7 +91,7 @@ if (roadmapFAQs.length) {
<div class='bg-gray-50 pt-4 sm:pt-12'>
{
!roadmapData.isUpcoming && roadmapData.briefTitle !== 'Android' && (
<div class='container relative max-w-[1000px]'>
<div class='container relative !max-w-[1000px]'>
<ShareIcons
description={roadmapData.briefDescription}
pageUrl={`https://roadmap.sh/${roadmapId}`}

@ -20,8 +20,11 @@ export async function getStaticPaths() {
});
}
export const get: APIRoute = async function ({ params, request, props }) {
return {
body: JSON.stringify(props.roadmapJson),
};
export const GET: APIRoute = async function ({ params, request, props }) {
return new Response(JSON.stringify(props.roadmapJson), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
};

@ -1,71 +0,0 @@
---
import RoadmapHeader from '../../components/RoadmapHeader.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getRoadmapIds, RoadmapFrontmatter } from '../../lib/roadmap';
import { getTopicsByRoadmapId } from '../../lib/roadmap-topic';
interface Params extends Record<string, string | undefined> {
roadmapId: string;
}
export async function getStaticPaths() {
const roadmapIds = await getRoadmapIds();
return roadmapIds.map((roadmapId) => ({
params: { roadmapId },
}));
}
const { roadmapId } = Astro.params as Params;
const topics = await getTopicsByRoadmapId(roadmapId);
const roadmapFile = await import(`../../data/roadmaps/${roadmapId}/${roadmapId}.md`);
const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter;
---
<BaseLayout
title={`${roadmapData.title} Topics`}
description={roadmapData.seo.description}
keywords={roadmapData.seo.keywords}
permalink={`/${roadmapId}/topics`}
>
<RoadmapHeader
description={roadmapData.description}
title={`${roadmapData.briefTitle} Topics`}
roadmapId={roadmapId}
hasSearch={true}
hasTopics={false}
/>
<div class='bg-gray-50 pt-5 pb-8 sm:pt-10 sm:pb-16'>
<div class='container'>
{
topics.map((topic) => {
// Breadcrumbs have three additional items e.g.
//
// Roadmaps / Frontend / Topics / Internet / HTTP
// ---^----------^---------^----
//
// Subtracting 3 to get the total parent count
const totalParentCount = topic.breadcrumbs.length - 3;
return (
<a
data-topic={topic.heading.toLowerCase()}
class:list={[
'cursor-pointer text-sm sm:text-md border-gray-200 border py-1.5 px-2 sm:py-2 sm:px-2.5 rounded-md block mb-0.5 sm:mb-1',
{
'bg-gray-400 hover:bg-gray-500': totalParentCount === 1,
'bg-gray-300 hover:bg-gray-400': totalParentCount === 2,
'bg-gray-100 hover:bg-gray-300': totalParentCount === 3,
},
]}
href={`${topic.url}`}
>
{topic.heading}
</a>
);
})
}
</div>
</div>
</BaseLayout>

@ -8,16 +8,21 @@ import UpcomingForm from '../../../components/UpcomingForm.astro';
import BaseLayout from '../../../layouts/BaseLayout.astro';
import { UserProgressModal } from '../../../components/UserProgress/UserProgressModal';
import {
type BestPracticeFileType,
BestPracticeFrontmatter,
getAllBestPractices,
getBestPracticeIds,
} from '../../../lib/best-pratice';
import { generateArticleSchema } from '../../../lib/jsonld-schema';
export async function getStaticPaths() {
const bestPracticeIds = await getBestPracticeIds();
const bestPractices = await getAllBestPractices();
return bestPracticeIds.map((bestPracticeId) => ({
params: { bestPracticeId },
return bestPractices.map((bestPractice: BestPracticeFileType) => ({
params: { bestPracticeId: bestPractice.id },
props: {
bestPractice: bestPractice,
},
}));
}
@ -25,12 +30,13 @@ interface Params extends Record<string, string | undefined> {
bestPracticeId: string;
}
interface Props {
bestPractice: BestPracticeFileType;
}
const { bestPracticeId } = Astro.params as Params;
const bestPracticeFile = await import(
`../../../data/best-practices/${bestPracticeId}/${bestPracticeId}.md`
);
const bestPracticeData =
bestPracticeFile.frontmatter as BestPracticeFrontmatter;
const { bestPractice } = Astro.props as Props;
const bestPracticeData = bestPractice.frontmatter as BestPracticeFrontmatter;
let jsonLdSchema = [];
@ -78,7 +84,7 @@ if (bestPracticeData.schema) {
<div class='bg-gray-50 py-4 sm:py-12'>
{
!bestPracticeData.isUpcoming && bestPracticeData.jsonUrl && (
<div class='container relative max-w-[1000px]'>
<div class='container relative !max-w-[1000px]'>
<ShareIcons
description={bestPracticeData.briefDescription}
pageUrl={`https://roadmap.sh/best-practices/${bestPracticeId}`}
@ -98,7 +104,7 @@ if (bestPracticeData.schema) {
{
!bestPracticeData.isUpcoming && !bestPracticeData.jsonUrl && (
<MarkdownFile>
<bestPracticeFile.Content />
<bestPractice.Content />
</MarkdownFile>
)
}
@ -107,7 +113,7 @@ if (bestPracticeData.schema) {
<UserProgressModal
resourceId={bestPracticeId}
resourceType='best-practice'
client:only="react"
client:only='react'
/>
{bestPracticeData.isUpcoming && <UpcomingForm />}

@ -23,8 +23,11 @@ export async function getStaticPaths() {
});
}
export const get: APIRoute = async function ({ params, request, props }) {
return {
body: JSON.stringify(props.bestPracticeJson),
};
export const GET: APIRoute = async function ({ params, request, props }) {
return new Response(JSON.stringify(props.bestPracticeJson), {
status: 200,
headers: {
'content-type': 'application/json',
},
});
};

@ -1,6 +1,6 @@
---
import Divider from '../components/AuthenticationFlow/Divider.astro';
import EmailLoginForm from '../components/AuthenticationFlow/EmailLoginForm';
import { EmailLoginForm } from '../components/AuthenticationFlow/EmailLoginForm';
import { GitHubButton } from '../components/AuthenticationFlow/GitHubButton';
import { GoogleButton } from '../components/AuthenticationFlow/GoogleButton';
import { LinkedInButton } from '../components/AuthenticationFlow/LinkedInButton';

@ -3,14 +3,14 @@ import { getAllGuides } from '../lib/guide';
import { getRoadmapsByTag } from '../lib/roadmap';
import { getAllVideos } from '../lib/video';
export async function get() {
export async function GET() {
const guides = await getAllGuides();
const videos = await getAllVideos();
const roadmaps = await getRoadmapsByTag('roadmap');
const bestPractices = await getAllBestPractices();
return {
body: JSON.stringify([
return new Response(
JSON.stringify([
...roadmaps.map((roadmap) => ({
id: roadmap.id,
url: `/${roadmap.id}`,
@ -39,5 +39,11 @@ export async function get() {
group: 'Videos',
})),
]),
};
{
status: 200,
headers: {
'Content-Type': 'application/json',
},
}
);
}

@ -22,7 +22,7 @@ export async function getStaticPaths() {
noIndex={true}
>
<div class='flex bg-gray-50 pb-14 pt-4 sm:pb-16 sm:pt-8'>
<div class='container max-w-[700px]'>
<div class='container !max-w-[700px]'>
<div class='mb-5 mt-2 text-center sm:mb-10 sm:mt-8'>
<h1
class='my-2 text-3xl font-bold sm:my-5 sm:text-5xl'

@ -1,5 +1,5 @@
---
import ResetPasswordForm from '../components/AuthenticationFlow/ResetPasswordForm';
import { ResetPasswordForm } from '../components/AuthenticationFlow/ResetPasswordForm';
import AccountLayout from '../layouts/AccountLayout.astro';
---

@ -1,6 +1,6 @@
---
import Divider from '../components/AuthenticationFlow/Divider.astro';
import EmailSignupForm from '../components/AuthenticationFlow/EmailSignupForm';
import { EmailSignupForm } from '../components/AuthenticationFlow/EmailSignupForm';
import { GitHubButton } from '../components/AuthenticationFlow/GitHubButton';
import { GoogleButton } from '../components/AuthenticationFlow/GoogleButton';
import { LinkedInButton } from '../components/AuthenticationFlow/LinkedInButton';

@ -4,7 +4,7 @@
@layer components {
.container {
@apply mx-auto max-w-[830px] px-4;
@apply mx-auto !max-w-[830px] px-4;
}
/* Chrome, Safari and Opera */

@ -1,6 +1,7 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"moduleResolution": "node",
"jsx": "react-jsx",
"jsxImportSource": "react"
}

Loading…
Cancel
Save