mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-12 17:51:53 +08:00
chore: upgrade to astro v3 (#4437)
This commit is contained in:
@@ -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,
|
||||
}),
|
||||
|
||||
10
package.json
10
package.json
@@ -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",
|
||||
|
||||
853
pnpm-lock.yaml
generated
853
pnpm-lock.yaml
generated
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,10 +90,11 @@ 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)
|
||||
? window.location.pathname + window.location.search
|
||||
: window.location.pathname;
|
||||
const pagePath = ['/respond-invite', '/befriend'].includes(
|
||||
window.location.pathname
|
||||
)
|
||||
? window.location.pathname + window.location.search
|
||||
: window.location.pathname;
|
||||
|
||||
localStorage.setItem(GITHUB_REDIRECT_AT, Date.now().toString());
|
||||
localStorage.setItem(GITHUB_LAST_PAGE, pagePath);
|
||||
@@ -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,10 +86,11 @@ 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)
|
||||
? window.location.pathname + window.location.search
|
||||
: window.location.pathname;
|
||||
const pagePath = ['/respond-invite', '/befriend'].includes(
|
||||
window.location.pathname
|
||||
)
|
||||
? window.location.pathname + window.location.search
|
||||
: window.location.pathname;
|
||||
|
||||
localStorage.setItem(LINKEDIN_REDIRECT_AT, Date.now().toString());
|
||||
localStorage.setItem(LINKEDIN_LAST_PAGE, pagePath);
|
||||
@@ -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> · </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}`}
|
||||
>
|
||||
← 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>]
|
||||
```
|
||||
|
||||
|
||||
@@ -63,20 +63,4 @@ 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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user