chore: migrate from preact to react (#4435)

chore/upgrade-v3
Kamran Ahmed 1 year ago committed by GitHub
parent c274feced1
commit 5c57a84e82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      astro.config.mjs
  2. 9
      package.json
  3. 713
      pnpm-lock.yaml
  4. 10
      src/components/Activity/ActivityCounters.tsx
  5. 9
      src/components/Activity/ActivityPage.tsx
  6. 12
      src/components/Activity/EmptyActivity.tsx
  7. 2
      src/components/Activity/ResourceProgress.tsx
  8. 30
      src/components/AddTeamRoadmap.tsx
  9. 10
      src/components/AuthenticationFlow/EmailLoginForm.tsx
  10. 2
      src/components/AuthenticationFlow/EmailSignupForm.tsx
  11. 4
      src/components/AuthenticationFlow/ForgotPasswordForm.tsx
  12. 8
      src/components/AuthenticationFlow/GitHubButton.tsx
  13. 8
      src/components/AuthenticationFlow/GoogleButton.tsx
  14. 8
      src/components/AuthenticationFlow/LinkedInButton.tsx
  15. 2
      src/components/AuthenticationFlow/ResetPasswordForm.tsx
  16. 6
      src/components/AuthenticationFlow/TriggerVerifyAccount.tsx
  17. 16
      src/components/AuthenticationFlow/VerificationEmailMessage.tsx
  18. 38
      src/components/Befriend.tsx
  19. 28
      src/components/CommandMenu/CommandMenu.tsx
  20. 2
      src/components/CreateTeam/CreateTeamForm.tsx
  21. 2
      src/components/CreateTeam/RoadmapSelector.tsx
  22. 2
      src/components/CreateTeam/RoleDropdown.tsx
  23. 10
      src/components/CreateTeam/SelectRoadmapModal.tsx
  24. 2
      src/components/CreateTeam/Step0.tsx
  25. 4
      src/components/CreateTeam/Step1.tsx
  26. 10
      src/components/CreateTeam/Step3.tsx
  27. 8
      src/components/CreateTeam/Step4.tsx
  28. 12
      src/components/CreateTeam/UpdateTeamResourceModal.tsx
  29. 2
      src/components/DeleteAccount/DeleteAccountForm.tsx
  30. 2
      src/components/DeleteAccount/DeleteAccountPopup.astro
  31. 12
      src/components/DeleteTeamPopup.tsx
  32. 8
      src/components/FeaturedItems/FavoriteIcon.tsx
  33. 2
      src/components/FeaturedItems/FeaturedItem.astro
  34. 8
      src/components/FeaturedItems/MarkFavorite.tsx
  35. 19
      src/components/Feedback/SubmitFeedbackPopup.tsx
  36. 20
      src/components/Friends/EmptyFriends.tsx
  37. 12
      src/components/Friends/FriendProgressItem.tsx
  38. 5
      src/components/Friends/FriendsPage.tsx
  39. 12
      src/components/Friends/InviteFriendPopup.tsx
  40. 10
      src/components/Friends/SidebarFriendsCounter.tsx
  41. 10
      src/components/HeroSection/FavoriteRoadmaps.tsx
  42. 2
      src/components/HeroSection/HeroSection.astro
  43. 2
      src/components/HeroSection/ProgressList.tsx
  44. 4
      src/components/Notification/NotificationPage.tsx
  45. 8
      src/components/PageProgress.tsx
  46. 27
      src/components/PageSponsor.tsx
  47. 6
      src/components/ReactIcons/AddUserIcon.tsx
  48. 6
      src/components/ReactIcons/AddedUserIcon.tsx
  49. 2
      src/components/ReactIcons/CheckIcon.tsx
  50. 6
      src/components/ReactIcons/ChevronDownIcon.tsx
  51. 2
      src/components/ReactIcons/CloseIcon.tsx
  52. 6
      src/components/ReactIcons/DeleteUserIcon.tsx
  53. 18
      src/components/ReactIcons/ErrorIcon.tsx
  54. 18
      src/components/ReactIcons/InfoIcon.tsx
  55. 6
      src/components/ReactIcons/MailIcon.tsx
  56. 6
      src/components/ReactIcons/ShareIcon.tsx
  57. 9
      src/components/ReactIcons/Spinner.tsx
  58. 6
      src/components/ReactIcons/StopIcon.tsx
  59. 6
      src/components/ReactIcons/TrashIcon.tsx
  60. 18
      src/components/ReactIcons/WarningIcon.tsx
  61. 4
      src/components/ResourceProgressStats.astro
  62. 12
      src/components/RespondInviteForm.tsx
  63. 5
      src/components/RoadCard/Editor.tsx
  64. 4
      src/components/RoadCard/RoadCardPage.tsx
  65. 15
      src/components/RoadCard/RoadmapSelect.tsx
  66. 2
      src/components/RoadmapHeader.astro
  67. 8
      src/components/SearchSelector.tsx
  68. 2
      src/components/Stepper.tsx
  69. 16
      src/components/TeamDropdown/TeamDropdown.tsx
  70. 20
      src/components/TeamMembers/InviteMemberPopup.tsx
  71. 2
      src/components/TeamMembers/LeaveTeamButton.tsx
  72. 10
      src/components/TeamMembers/LeaveTeamPopup.tsx
  73. 2
      src/components/TeamMembers/MemberActionDropdown.tsx
  74. 4
      src/components/TeamMembers/TeamMemberItem.tsx
  75. 4
      src/components/TeamMembers/TeamMembersPage.tsx
  76. 22
      src/components/TeamMembers/UpdateMemberPopup.tsx
  77. 2
      src/components/TeamProgress/GroupRoadmapItem.tsx
  78. 4
      src/components/TeamProgress/MemberProgressItem.tsx
  79. 28
      src/components/TeamProgress/MemberProgressModal.tsx
  80. 28
      src/components/TeamProgress/TeamProgressPage.tsx
  81. 6
      src/components/TeamRoadmaps.tsx
  82. 36
      src/components/TeamSettings/UpdateTeamForm.tsx
  83. 51
      src/components/TeamSidebar.tsx
  84. 24
      src/components/TeamVersions/TeamVersions.tsx
  85. 10
      src/components/TeamsList.tsx
  86. 4
      src/components/Toast.tsx
  87. 4
      src/components/TopicDetail/ContributionForm.tsx
  88. 10
      src/components/TopicDetail/TopicDetail.tsx
  89. 34
      src/components/TopicDetail/TopicProgressButton.tsx
  90. 23
      src/components/UpdatePassword/UpdatePasswordForm.tsx
  91. 28
      src/components/UpdateProfile/UpdateProfileForm.tsx
  92. 6
      src/components/UpdateProfile/UploadProfilePicture.tsx
  93. 1
      src/components/UserProgress/ProgressShareButton.tsx
  94. 42
      src/components/UserProgress/UserProgressModal.tsx
  95. 2
      src/hooks/use-copy-text.ts
  96. 2
      src/hooks/use-keydown.ts
  97. 2
      src/hooks/use-load-topic.ts
  98. 2
      src/hooks/use-outside-click.ts
  99. 2
      src/hooks/use-params.ts
  100. 2
      src/hooks/use-team-id.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,5 +1,4 @@
// https://astro.build/config
import preact from '@astrojs/preact';
import sitemap from '@astrojs/sitemap';
import tailwind from '@astrojs/tailwind';
import compress from 'astro-compress';
@ -8,6 +7,8 @@ import rehypeExternalLinks from 'rehype-external-links';
import { fileURLToPath } from 'node:url';
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
import react from '@astrojs/react';
// https://astro.build/config
export default defineConfig({
site: 'https://roadmap.sh/',
@ -31,11 +32,9 @@ export default defineConfig({
'https://cs.fyi',
'https://roadmap.sh',
];
if (whiteListedStarts.some((start) => href.startsWith(start))) {
return [];
}
return 'noopener noreferrer nofollow';
},
},
@ -75,6 +74,6 @@ export default defineConfig({
CSS: false,
JavaScript: false,
}),
preact(),
react(),
],
});

@ -21,11 +21,13 @@
"test:e2e": "playwright test"
},
"dependencies": {
"@astrojs/preact": "^2.2.1",
"@astrojs/react": "^2.2.2",
"@astrojs/sitemap": "^1.3.3",
"@astrojs/tailwind": "^3.1.3",
"@fingerprintjs/fingerprintjs": "^3.4.1",
"@nanostores/preact": "^0.5.0",
"@nanostores/react": "^0.7.1",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"astro": "^2.6.6",
"astro-compress": "^2.0.8",
"jose": "^4.14.4",
@ -33,7 +35,8 @@
"nanostores": "^0.9.2",
"node-html-parser": "^6.1.5",
"npm-check-updates": "^16.10.12",
"preact": "^10.15.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"rehype-external-links": "^2.1.0",
"roadmap-renderer": "^1.0.6",
"slugify": "^1.6.6",

File diff suppressed because it is too large Load Diff

@ -21,11 +21,11 @@ function ActivityCounter(props: ActivityCounterType) {
const { text, count } = props;
return (
<div class="relative flex flex-1 flex-row-reverse sm:flex-col px-0 sm:px-4 py-2 sm:py-4 text-center sm:pt-10 items-center gap-2 sm:gap-0 justify-end">
<h2 class="text-base sm:text-5xl font-bold">
<div className="relative flex flex-1 flex-row-reverse sm:flex-col px-0 sm:px-4 py-2 sm:py-4 text-center sm:pt-10 items-center gap-2 sm:gap-0 justify-end">
<h2 className="text-base sm:text-5xl font-bold">
{count}
</h2>
<p class="mt-0 sm:mt-2 text-sm text-gray-400">{text}</p>
<p className="mt-0 sm:mt-2 text-sm text-gray-400">{text}</p>
</div>
);
}
@ -34,8 +34,8 @@ export function ActivityCounters(props: ActivityCountersType) {
const { done, learning, streak } = props;
return (
<div class="mx-0 -mt-5 sm:-mx-10 md:-mt-10">
<div class="flex flex-col sm:flex-row gap-0 sm:gap-2 divide-y sm:divide-y-0 divide-x-0 sm:divide-x border-b">
<div className="mx-0 -mt-5 sm:-mx-10 md:-mt-10">
<div className="flex flex-col sm:flex-row gap-0 sm:gap-2 divide-y sm:divide-y-0 divide-x-0 sm:divide-x border-b">
<ActivityCounter
text={'Topics Completed'}
count={`${done?.total || 0}`}

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { httpGet } from '../../lib/http';
import { ActivityCounters } from './ActivityCounters';
import { ResourceProgress } from './ResourceProgress';
@ -91,16 +91,16 @@ export function ActivityPage() {
streak={activity?.streak || { count: 0 }}
/>
<div class="mx-0 px-0 py-5 md:-mx-10 md:px-8 md:py-8">
<div className="mx-0 px-0 py-5 md:-mx-10 md:px-8 md:py-8">
{learningRoadmaps.length === 0 &&
learningBestPractices.length === 0 && <EmptyActivity />}
{(learningRoadmaps.length > 0 || learningBestPractices.length > 0) && (
<>
<h2 class="mb-3 text-xs uppercase text-gray-400">
<h2 className="mb-3 text-xs uppercase text-gray-400">
Continue Following
</h2>
<div class="flex flex-col gap-3">
<div className="flex flex-col gap-3">
{learningRoadmaps
.sort((a, b) => {
const updatedAtA = new Date(a.updatedAt);
@ -110,6 +110,7 @@ export function ActivityPage() {
})
.map((roadmap) => (
<ResourceProgress
key={roadmap.id}
doneCount={roadmap.done || 0}
learningCount={roadmap.learning || 0}
totalCount={roadmap.total || 0}

@ -2,21 +2,21 @@ import RoadmapIcon from '../../icons/roadmap.svg';
export function EmptyActivity() {
return (
<div class="rounded-md">
<div class="flex flex-col items-center p-7 text-center">
<div className="rounded-md">
<div className="flex flex-col items-center p-7 text-center">
<img
alt="no roadmaps"
src={RoadmapIcon}
class="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10"
className="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10"
/>
<h2 class="text-lg sm:text-xl font-bold">No Progress</h2>
<h2 className="text-lg sm:text-xl font-bold">No Progress</h2>
<p className="my-1 sm:my-2 max-w-[400px] text-gray-500 text-sm sm:text-base">
Progress will appear here as you start tracking your{' '}
<a href="/roadmaps" class="mt-4 text-blue-500 hover:underline">
<a href="/roadmaps" className="mt-4 text-blue-500 hover:underline">
Roadmaps
</a>{' '}
or{' '}
<a href="/best-practices" class="mt-4 text-blue-500 hover:underline">
<a href="/best-practices" className="mt-4 text-blue-500 hover:underline">
Best Practices
</a>{' '}
progress.

@ -1,8 +1,8 @@
import { useState } from 'preact/hooks';
import { httpPost } from '../../lib/http';
import { getRelativeTimeString } from '../../lib/date';
import { useToast } from '../../hooks/use-toast';
import { ProgressShareButton } from '../UserProgress/ProgressShareButton';
import { useState } from 'react';
type ResourceProgressType = {
resourceType: 'roadmap' | 'best-practice';

@ -1,4 +1,4 @@
import { useRef, useState } from 'preact/hooks';
import { useRef, useState } from 'react';
import { useOutsideClick } from '../hooks/use-outside-click';
import { OptionType, SearchSelector } from './SearchSelector';
import type { PageType } from './CommandMenu/CommandMenu';
@ -65,15 +65,15 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
)?.title;
return (
<div class="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div class="relative h-full w-full max-w-md p-4 md:h-auto">
<div className="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
<div
ref={popupBodyEl}
class="popup-body relative rounded-lg bg-white p-4 shadow"
className="popup-body relative rounded-lg bg-white p-4 shadow"
>
{isLoading && (
<>
<div class="flex items-center justify-center gap-2 py-8">
<div className="flex items-center justify-center gap-2 py-8">
<Spinner isDualRing={false} className="h-4 w-4" />
<h2 className="font-medium">Loading...</h2>
</div>
@ -82,7 +82,7 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
{!isLoading && !error && selectedRoadmap && (
<div className={'text-center'}>
<CheckIcon additionalClasses="h-10 w-10 mx-auto opacity-20 mb-3 mt-4" />
<h3 class="mb-1.5 text-2xl font-medium">
<h3 className="mb-1.5 text-2xl font-medium">
{selectedRoadmapTitle} Added
</h3>
<p className="mb-4 text-sm leading-none text-gray-400">
@ -95,11 +95,11 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
to make changes to the roadmap.
</p>
<div class="flex items-center gap-2">
<div className="flex items-center gap-2">
<button
onClick={onClose}
type="button"
class="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
className="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
>
Done
</button>
@ -110,7 +110,7 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
setIsLoading(false);
}}
type="button"
class="flex-grow cursor-pointer rounded-lg bg-black py-2 text-center text-white"
className="flex-grow cursor-pointer rounded-lg bg-black py-2 text-center text-white"
>
+ Add More
</button>
@ -119,14 +119,14 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
)}
{!isLoading && error && (
<>
<h3 class="mb-1.5 text-2xl font-medium">Error</h3>
<h3 className="mb-1.5 text-2xl font-medium">Error</h3>
<p className="mb-3 text-sm leading-none text-red-400">{error}</p>
<div class="flex items-center gap-2">
<div className="flex items-center gap-2">
<button
onClick={onClose}
type="button"
class="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
className="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
>
Cancel
</button>
@ -135,7 +135,7 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
)}
{!isLoading && !error && !selectedRoadmap && (
<>
<h3 class="mb-1.5 text-2xl font-medium">Add Roadmap</h3>
<h3 className="mb-1.5 text-2xl font-medium">Add Roadmap</h3>
<p className="mb-3 text-sm leading-none text-gray-400">
Search and add a roadmap
</p>
@ -156,11 +156,11 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
placeholder={'Search for roadmap'}
/>
<div class="flex items-center gap-2">
<div className="flex items-center gap-2">
<button
onClick={onClose}
type="button"
class="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
className="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
>
Cancel
</button>

@ -1,17 +1,17 @@
import Cookies from 'js-cookie';
import type { FunctionComponent } from 'preact';
import { useState } from 'preact/hooks';
import type { FormEvent } from 'react';
import { useState } from 'react';
import { httpPost } from '../../lib/http';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
const EmailLoginForm: FunctionComponent<{}> = () => {
const EmailLoginForm = () => {
const [email, setEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleFormSubmit = async (e: Event) => {
const handleFormSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError('');
@ -77,7 +77,7 @@ const EmailLoginForm: FunctionComponent<{}> = () => {
onInput={(e) => setPassword(String((e.target as any).value))}
/>
<p class="mb-3 mt-2 text-sm text-gray-500">
<p className="mb-3 mt-2 text-sm text-gray-500">
<a
href="/forgot-password"
className="text-blue-800 hover:text-blue-600"

@ -1,5 +1,5 @@
import type { FunctionComponent } from 'preact';
import { useState } from 'preact/hooks';
import { useState } from 'react';
import { httpPost } from '../../lib/http';
const EmailSignupForm: FunctionComponent = () => {

@ -1,4 +1,4 @@
import { useState } from 'preact/hooks';
import { useState } from 'react';
import { httpPost } from '../../lib/http';
export function ForgotPasswordForm() {
@ -29,7 +29,7 @@ export function ForgotPasswordForm() {
};
return (
<form onSubmit={handleSubmit} class="w-full">
<form onSubmit={handleSubmit} className="w-full">
<input
type="email"
name="email"

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import GitHubIcon from '../../icons/github.svg';
import SpinnerIcon from '../../icons/spinner.svg';
@ -106,14 +106,14 @@ export function GitHubButton(props: GitHubButtonProps) {
return (
<>
<button
class="inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
disabled={isLoading}
onClick={handleClick}
>
<img
src={icon}
src={icon as any}
alt="GitHub"
class={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
className={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
/>
Continue with GitHub
</button>

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import GoogleIcon from '../../icons/google.svg';
import SpinnerIcon from '../../icons/spinner.svg';
@ -106,14 +106,14 @@ export function GoogleButton(props: GoogleButtonProps) {
return (
<>
<button
class="inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
disabled={isLoading}
onClick={handleClick}
>
<img
src={icon}
src={icon as any}
alt="Google"
class={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
className={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
/>
Continue with Google
</button>

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import LinkedIn from '../../icons/linkedin.svg';
import SpinnerIcon from '../../icons/spinner.svg';
@ -106,14 +106,14 @@ export function LinkedInButton(props: LinkedInButtonProps) {
return (
<>
<button
class="inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
disabled={isLoading}
onClick={handleClick}
>
<img
src={icon}
src={icon as any}
alt="Google"
class={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
className={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
/>
Continue with LinkedIn
</button>

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { httpPost } from '../../lib/http';
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';

@ -1,7 +1,7 @@
import SpinnerIcon from '../../icons/spinner.svg';
import ErrorIcon from '../../icons/error.svg';
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
import { httpPost } from '../../lib/http';
@ -60,7 +60,7 @@ export function TriggerVerifyAccount() {
<img
alt={'Please wait.'}
src={SpinnerIcon}
class={'mx-auto h-16 w-16 animate-spin'}
className={'mx-auto h-16 w-16 animate-spin'}
/>
)}
{error && (
@ -75,7 +75,7 @@ export function TriggerVerifyAccount() {
</h2>
<div className="text-sm sm:text-base">
{isLoading && <p>Please wait while we verify your account..</p>}
{error && <p class="text-red-700">{error}</p>}
{error && <p className="text-red-700">{error}</p>}
</div>
</div>
</div>

@ -1,5 +1,5 @@
import VerifyLetterIcon from '../../icons/verify-letter.svg';
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { httpPost } from '../../lib/http';
export function VerificationEmailMessage() {
@ -39,13 +39,13 @@ export function VerificationEmailMessage() {
<div className="mx-auto max-w-md text-center">
<img
alt="Verify Email"
src={VerifyLetterIcon}
class="mx-auto mb-4 h-20 w-40 sm:h-40"
src={VerifyLetterIcon as any}
className="mx-auto mb-4 h-20 w-40 sm:h-40"
/>
<h2 class="my-2 text-center text-xl font-semibold sm:my-5 sm:text-2xl">
<h2 className="my-2 text-center text-xl font-semibold sm:my-5 sm:text-2xl">
Verify your email address
</h2>
<div class="text-sm sm:text-base">
<div className="text-sm sm:text-base">
<p>
We have sent you an email at{' '}
<span className="font-bold">{email}</span>. Please click the link to
@ -53,7 +53,7 @@ export function VerificationEmailMessage() {
soon!
</p>
<hr class="my-4" />
<hr className="my-4" />
{!isEmailResent && (
<>
@ -72,12 +72,12 @@ export function VerificationEmailMessage() {
</p>
)}
{error && <p class="text-red-700">{error}</p>}
{error && <p className="text-red-700">{error}</p>}
</>
)}
{isEmailResent && (
<p class="text-green-700">Verification email has been sent!</p>
<p className="text-green-700">Verification email has been sent!</p>
)}
</div>
</div>

@ -1,5 +1,5 @@
import { useEffect, useState } from 'preact/hooks';
import { httpDelete, httpGet, httpPatch, httpPost } from '../lib/http';
import { useEffect, useState } from 'react';
import { httpDelete, httpGet, httpPost } from '../lib/http';
import { pageProgressMessage } from '../stores/page';
import { isLoggedIn } from '../lib/jwt';
import { showLoginPopup } from '../lib/popup';
@ -118,7 +118,7 @@ export function Befriend() {
<ErrorIcon additionalClasses="mx-auto mb-4 mt-24 w-20 opacity-20" />
<h2 className={'mb-1 text-2xl font-bold'}>Error</h2>
<p class="mb-4 text-base leading-6 text-gray-600">
<p className="mb-4 text-base leading-6 text-gray-600">
{error || 'There was a problem, please try again.'}
</p>
@ -149,13 +149,13 @@ export function Befriend() {
/>
<h2 className={'mb-1 text-3xl font-bold'}>{user.name}</h2>
<p class="mb-6 text-base leading-6 text-gray-600">
<p className="mb-6 text-base leading-6 text-gray-600">
After you add {user.name} as a friend, you will be able to view each
other's skills and progress.
</p>
<div class="mx-auto w-full duration-500 sm:max-w-md">
<div class="flex w-full flex-col items-center gap-2">
<div className="mx-auto w-full duration-500 sm:max-w-md">
<div className="flex w-full flex-col items-center gap-2">
{user.status === 'none' && (
<button
disabled={isMe}
@ -169,7 +169,7 @@ export function Befriend() {
});
}}
type="button"
class="w-full flex-grow cursor-pointer rounded-lg bg-black px-3 py-2 text-center text-white disabled:cursor-not-allowed disabled:opacity-40"
className="w-full flex-grow cursor-pointer rounded-lg bg-black px-3 py-2 text-center text-white disabled:cursor-not-allowed disabled:opacity-40"
>
{isMe ? "You can't add yourself" : 'Add Friend'}
</button>
@ -177,7 +177,7 @@ export function Befriend() {
{user.status === 'sent' && (
<>
<span class="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
<CheckIcon additionalClasses="mr-2 h-4 w-4" />
Request Sent
</span>
@ -188,7 +188,7 @@ export function Befriend() {
setIsConfirming(true);
}}
type="button"
class="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-red-600 px-3 py-2 text-center text-white hover:bg-red-700"
className="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-red-600 px-3 py-2 text-center text-white hover:bg-red-700"
>
<DeleteUserIcon additionalClasses="mr-2 h-[19px] w-[19px]" />
Withdraw Request
@ -196,7 +196,7 @@ export function Befriend() {
)}
{isConfirming && (
<span class="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
Are you sure?{' '}
<button
className="ml-2 text-red-700 underline"
@ -225,7 +225,7 @@ export function Befriend() {
{user.status === 'accepted' && (
<>
<span class="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
<AddedUserIcon additionalClasses="mr-2 h-5 w-5" />
You are friends
</span>
@ -236,7 +236,7 @@ export function Befriend() {
setIsConfirming(true);
}}
type="button"
class="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-red-600 px-3 py-2 text-center text-white hover:bg-red-700"
className="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-red-600 px-3 py-2 text-center text-white hover:bg-red-700"
>
<DeleteUserIcon additionalClasses="mr-2 h-[19px] w-[19px]" />
Remove Friend
@ -244,7 +244,7 @@ export function Befriend() {
)}
{isConfirming && (
<span class="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
Are you sure?{' '}
<button
className="ml-2 text-red-700 underline"
@ -271,12 +271,12 @@ export function Befriend() {
{user.status === 'rejected' && (
<>
<span class="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
<DeleteUserIcon additionalClasses="mr-2 h-4 w-4" />
Request Rejected
</span>
<span class="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
Changed your mind?{' '}
<button
className="ml-2 text-red-700 underline"
@ -296,7 +296,7 @@ export function Befriend() {
{user.status === 'got_rejected' && (
<>
<span class="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-500 px-3 py-2 text-center text-red-500">
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-500 px-3 py-2 text-center text-red-500">
<StopIcon additionalClasses="mr-2 h-4 w-4" />
Request Rejected
</span>
@ -311,7 +311,7 @@ export function Befriend() {
pageProgressMessage.set('');
});
}}
class="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-gray-800 bg-gray-800 px-3 py-2 text-center text-white hover:bg-black"
className="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-gray-800 bg-gray-800 px-3 py-2 text-center text-white hover:bg-black"
>
<CheckIcon additionalClasses="mr-2 h-4 w-4" />
Accept Request
@ -323,7 +323,7 @@ export function Befriend() {
setIsConfirming(true);
}}
type="button"
class="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-white px-3 py-2 text-center text-red-600 hover:bg-red-100"
className="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-white px-3 py-2 text-center text-red-600 hover:bg-red-100"
>
<DeleteUserIcon additionalClasses="mr-2 h-[19px] w-[19px]" />
Reject Request
@ -331,7 +331,7 @@ export function Befriend() {
)}
{isConfirming && (
<span class="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
Are you sure?{' '}
<button
className="ml-2 text-red-700 underline"

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { Fragment, useEffect, useRef, useState } from 'react';
import { useKeydown } from '../../hooks/use-keydown';
import { useOutsideClick } from '../../hooks/use-outside-click';
import BestPracticesIcon from '../../icons/best-practices.svg';
@ -158,12 +158,12 @@ export function CommandMenu() {
<div className="relative rounded-lg bg-white shadow" ref={modalRef}>
<input
ref={inputRef}
autofocus={true}
autoFocus={true}
type="text"
value={searchedText}
className="w-full rounded-t-md border-b p-4 text-sm focus:bg-gray-50 focus:outline-none"
placeholder="Search roadmaps, guides or pages .."
autocomplete="off"
autoComplete="off"
onInput={(e) => {
const value = (e.target as HTMLInputElement).value.trim();
setSearchedText(value);
@ -191,39 +191,43 @@ export function CommandMenu() {
}}
/>
<div class="px-2 py-2">
<div className="px-2 py-2">
<div className="flex flex-col">
{searchResults.length === 0 && (
<div class="p-5 text-center text-sm text-gray-400">
<div className="p-5 text-center text-sm text-gray-400">
No results found
</div>
)}
{searchResults.map((page, counter) => {
{searchResults.map((page: PageType, counter: number) => {
const prevPage = searchResults[counter - 1];
const groupChanged = prevPage && prevPage.group !== page.group;
return (
<>
<Fragment key={page.id}>
{groupChanged && (
<div class="border-b border-gray-100"></div>
<div className="border-b border-gray-100"></div>
)}
<a
class={`flex w-full items-center rounded p-2 text-sm ${
className={`flex w-full items-center rounded p-2 text-sm ${
counter === activeCounter ? 'bg-gray-100' : ''
}`}
onMouseOver={() => setActiveCounter(counter)}
href={page.url}
>
{!page.icon && (
<span class="mr-2 text-gray-400">{page.group}</span>
<span className="mr-2 text-gray-400">{page.group}</span>
)}
{page.icon && (
<img alt={page.title} src={page.icon} class="mr-2 h-4 w-4" />
<img
alt={page.title}
src={page.icon as any}
className="mr-2 h-4 w-4"
/>
)}
{page.title}
</a>
</>
</Fragment>
);
})}
</div>

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { Stepper } from '../Stepper';
import { Step0, ValidTeamType } from './Step0';
import { Step1, ValidTeamSize } from './Step1';

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { httpGet, httpPut } from '../../lib/http';
import type { PageType } from '../CommandMenu/CommandMenu';
import ChevronDownIcon from '../../icons/chevron-down.svg';

@ -1,5 +1,5 @@
import { ChevronDownIcon } from '../ReactIcons/ChevronDownIcon';
import { useRef, useState } from 'preact/hooks';
import { useRef, useState } from 'react';
import { useOutsideClick } from '../../hooks/use-outside-click';
const allowedRoles = [

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import { useKeydown } from '../../hooks/use-keydown';
import { useOutsideClick } from '../../hooks/use-outside-click';
import type { PageType } from '../CommandMenu/CommandMenu';
@ -68,11 +68,11 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
);
return (
<div class="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
<div class="relative mx-auto h-full w-full max-w-2xl p-4 md:h-auto">
<div className="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
<div className="relative mx-auto h-full w-full max-w-2xl p-4 md:h-auto">
<div
ref={popupBodyEl}
class="popup-body relative mt-4 overflow-hidden rounded-lg bg-white shadow"
className="popup-body relative mt-4 overflow-hidden rounded-lg bg-white shadow"
>
<button
type="button"
@ -80,7 +80,7 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
onClick={onClose}
>
<img alt={'close'} src={CloseIcon} className="h-4 w-4" />
<span class="sr-only">Close modal</span>
<span className="sr-only">Close modal</span>
</button>
<input
ref={searchInputEl}

@ -2,7 +2,7 @@ import BuildingIcon from '../../icons/building.svg';
import UsersIcon from '../../icons/users.svg';
import type { TeamDocument } from './CreateTeamForm';
import { httpPut } from '../../lib/http';
import { useState } from 'preact/hooks';
import { useState } from 'react';
import { NextButton } from './NextButton';
export const validTeamTypes = [

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import { AppError, httpPost, httpPut } from '../../lib/http';
import type { ValidTeamType } from './Step0';
import type { TeamDocument } from './CreateTeamForm';
@ -133,7 +133,7 @@ export function Step1(props: Step1Props) {
type="text"
name="name"
ref={nameRef as any}
autofocus={true}
autoFocus={true}
id="name"
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
placeholder="Roadmap Inc."

@ -2,7 +2,7 @@ import type { TeamDocument } from './CreateTeamForm';
import { NextButton } from './NextButton';
import { TrashIcon } from '../ReactIcons/TrashIcon';
import { AllowedRoles, RoleDropdown } from './RoleDropdown';
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import { httpPost } from '../../lib/http';
type Step3Props = {
@ -75,9 +75,9 @@ export function Step3(props: Step3Props) {
return (
<form className="mt-4 flex w-full flex-col" onSubmit={onSubmit}>
<div class="mb-1 mt-2">
<h2 class="mb-1 md:mb-2 text-lg md:text-2xl font-bold">Invite your Team</h2>
<p class="text-sm text-gray-700">
<div className="mb-1 mt-2">
<h2 className="mb-1 md:mb-2 text-lg md:text-2xl font-bold">Invite your Team</h2>
<p className="text-sm text-gray-700">
Use the form below to invite your team members to your team. You can
also invite them later.
</p>
@ -88,7 +88,7 @@ export function Step3(props: Step3Props) {
<div className="flex flex-col sm:flex-row gap-2" key={user.id}>
<input
ref={userCounter === users.length - 1 ? emailInputRef : null}
autofocus={true}
autoFocus={true}
type="email"
name="email"
required

@ -8,15 +8,15 @@ type Step4Props = {
export function Step4({ team }: Step4Props) {
return (
<div className="mt-4 flex flex-col rounded-xl border py-12 text-center">
<div class="mb-1 flex flex-col items-center">
<div className="mb-1 flex flex-col items-center">
<CheckIcon additionalClasses={'h-14 w-14 mb-4 opacity-100'} />
<h2 class="mb-2 text-2xl font-bold">Team Created</h2>
<p class="text-sm text-gray-700">
<h2 className="mb-2 text-2xl font-bold">Team Created</h2>
<p className="text-sm text-gray-700">
Your team has been created. Happy learning!
</p>
<a
href={`/team/progress?t=${team._id}`}
class="mt-4 rounded-md bg-black px-5 py-1.5 text-sm text-white"
className="mt-4 rounded-md bg-black px-5 py-1.5 text-sm text-white"
>
View Team
</a>

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import { wireframeJSONToSVG } from 'roadmap-renderer';
import { Spinner } from '../ReactIcons/Spinner';
import { httpGet, httpPut } from '../../lib/http';
@ -8,7 +8,7 @@ import { useOutsideClick } from '../../hooks/use-outside-click';
import { useKeydown } from '../../hooks/use-keydown';
import type { TeamResourceConfig } from './RoadmapSelector';
import { useToast } from '../../hooks/use-toast';
import { useStore } from '@nanostores/preact';
import { useStore } from '@nanostores/react';
import { $currentTeam } from '../../stores/team';
export type ProgressMapProps = {
@ -148,8 +148,8 @@ export function UpdateTeamResourceModal(props: ProgressMapProps) {
}, []);
return (
<div class="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
<div class="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto">
<div className="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
<div className="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto">
<div
id={
currentTeam?.type === 'company'
@ -157,7 +157,7 @@ export function UpdateTeamResourceModal(props: ProgressMapProps) {
: 'original-roadmap'
}
ref={popupBodyEl}
class="popup-body relative rounded-lg bg-white shadow"
className="popup-body relative rounded-lg bg-white shadow"
>
<div
className={
@ -201,7 +201,7 @@ export function UpdateTeamResourceModal(props: ProgressMapProps) {
<div id="resource-svg-wrap" ref={containerEl} className="px-4"></div>
{isLoading && (
<div class="flex w-full justify-center">
<div className="flex w-full justify-center">
<Spinner
isDualRing={false}
className="mb-4 mt-2 h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-8 sm:w-8"

@ -1,4 +1,4 @@
import {useEffect, useState} from 'preact/hooks';
import {useEffect, useState} from 'react';
import { httpDelete } from '../../lib/http';
import { logout } from '../Navigation/navigation';

@ -12,6 +12,6 @@ import { DeleteAccountForm } from './DeleteAccountForm';
<p class="text-black font-medium -mb-2 mt-3 text-base">Please type "delete" to confirm.</p>
<DeleteAccountForm client:only />
<DeleteAccountForm client:only="react" />
</div>
</Popup>

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import { httpDelete } from '../lib/http';
import type { TeamDocument } from './CreateTeam/CreateTeamForm';
import { useTeamId } from '../hooks/use-team-id';
@ -69,18 +69,18 @@ export function DeleteTeamPopup(props: DeleteTeamPopupProps) {
return (
<>
<div class="fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div class="relative h-full w-full max-w-md p-4 md:h-auto">
<div className="fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
<div
ref={popupBodyEl}
class="popup-body relative rounded-lg bg-white p-4 shadow"
className="popup-body relative rounded-lg bg-white p-4 shadow"
>
<h2 class="text-2xl font-semibold text-black">Delete Team</h2>
<h2 className="text-2xl font-semibold text-black">Delete Team</h2>
<p className="text-gray-500">
This will permanently delete your team and all associated data.
</p>
<p class="-mb-2 mt-3 text-base font-medium text-black">
<p className="-mb-2 mt-3 text-base font-medium text-black">
Please type "delete" to confirm.
</p>
<form onSubmit={handleSubmit}>

@ -16,8 +16,8 @@ export function FavoriteIcon(props: FavoriteIconProps) {
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M5.93682 0.5H2.06282C1.63546 0.500094 1.22423 0.663195 0.912987 0.956045C0.601741 1.2489 0.413919 1.64944 0.387822 2.076L0.00182198 8.461C-0.012178 8.6905 0.0548218 8.9185 0.191822 9.104L0.242322 9.1665C0.575322 9.5485 1.15132 9.6165 1.56582 9.31L3.99982 7.5115L6.43382 9.31C6.58413 9.42115 6.76305 9.48708 6.94954 9.50006C7.13603 9.51303 7.32235 9.4725 7.4866 9.38323C7.65085 9.29397 7.78621 9.15967 7.87677 8.99613C7.96733 8.83258 8.00932 8.64659 7.99782 8.46L7.61232 2.0765C7.58622 1.64981 7.39835 1.24914 7.08701 0.956192C6.77567 0.663248 6.36431 0.500094 5.93682 0.5ZM5.93682 1.25C6.42732 1.25 6.83382 1.632 6.86382 2.122L7.24932 8.506C7.25216 8.55018 7.24229 8.59425 7.22089 8.63301C7.19949 8.67176 7.16745 8.70359 7.12854 8.72472C7.08964 8.74585 7.0455 8.75542 7.00134 8.75228C6.95718 8.74914 6.91484 8.73343 6.87932 8.707L4.27582 6.783C4.19591 6.72397 4.09917 6.69211 3.99982 6.69211C3.90047 6.69211 3.80373 6.72397 3.72382 6.783L1.11982 8.707C1.0843 8.73343 1.04196 8.74914 0.9978 8.75228C0.953639 8.75542 0.909502 8.74585 0.8706 8.72472C0.831697 8.70359 0.799653 8.67176 0.778252 8.63301C0.756851 8.59425 0.746986 8.55018 0.749822 8.506L1.13632 2.122C1.16632 1.632 1.57232 1.25 2.06282 1.25H5.93682Z"
fill="currentColor"
/>
@ -35,8 +35,8 @@ export function FavoriteIcon(props: FavoriteIconProps) {
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M5.93682 0.5H2.06282C1.63546 0.500094 1.22423 0.663195 0.912987 0.956045C0.601741 1.2489 0.413919 1.64944 0.387822 2.076L0.00182198 8.461C-0.012178 8.6905 0.0548218 8.9185 0.191822 9.104L0.242322 9.1665C0.575322 9.5485 1.15132 9.6165 1.56582 9.31L3.99982 7.5115L6.43382 9.31C6.58413 9.42115 6.76305 9.48708 6.94954 9.50006C7.13603 9.51303 7.32235 9.4725 7.4866 9.38323C7.65085 9.29397 7.78621 9.15967 7.87677 8.99613C7.96733 8.83258 8.00932 8.64659 7.99782 8.46L7.61232 2.0765C7.58622 1.64981 7.39835 1.24914 7.08701 0.956192C6.77567 0.663248 6.36431 0.500094 5.93682 0.5Z"
fill="currentColor"
/>

@ -29,7 +29,7 @@ const { isUpcoming = false, isNew = false, text, url } = Astro.props;
<MarkFavorite
resourceId={url.split('/').pop()!}
resourceType={url.includes('best-practices') ? 'best-practice' : 'roadmap'}
client:load
client:only="react"
/>
{

@ -1,4 +1,5 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import type { MouseEvent } from "react";
import { httpPatch } from '../../lib/http';
import type { ResourceType } from '../../lib/resource-progress';
import { isLoggedIn } from '../../lib/jwt';
@ -20,15 +21,16 @@ export function MarkFavorite({
favorite,
className,
}: MarkFavoriteType) {
const isAuthenticated = isLoggedIn();
const localStorageKey = `${resourceType}-${resourceId}-favorite`;
const toast = useToast();
const [isLoading, setIsLoading] = useState(false);
const [isFavorite, setIsFavorite] = useState(
favorite ?? localStorage.getItem(localStorageKey) === '1'
isAuthenticated ? (favorite ?? localStorage.getItem(localStorageKey) === '1') : false
);
async function toggleFavoriteHandler(e: Event) {
async function toggleFavoriteHandler(e: MouseEvent<HTMLButtonElement>) {
e.preventDefault();
if (!isLoggedIn()) {
showLoginPopup();

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { 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';
@ -13,7 +13,6 @@ type SubmitFeedbackPopupProps = {
export function SubmitFeedbackPopup(props: SubmitFeedbackPopupProps) {
const { onClose } = props;
const toast = useToast();
const popupBodyEl = useRef<HTMLDivElement>(null);
const inputEl = useRef<HTMLTextAreaElement>(null);
@ -35,7 +34,7 @@ export function SubmitFeedbackPopup(props: SubmitFeedbackPopupProps) {
inputEl.current?.focus();
}, []);
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError('');
@ -65,25 +64,27 @@ export function SubmitFeedbackPopup(props: SubmitFeedbackPopupProps) {
};
return (
<div class="fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div class="relative h-full w-full max-w-md p-4 md:h-auto">
<div className="fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
<div
ref={popupBodyEl}
class="popup-body relative rounded-lg bg-white p-4 shadow"
className="popup-body relative rounded-lg bg-white p-4 shadow"
>
{!isSuccess && (
<>
<h2 class="text-xl font-semibold mb-1 text-black">
<h2 className="mb-1 text-xl font-semibold text-black">
Enter your feedback
</h2>
<p className={'text-sm text-gray-500'}>Help us improve your experience.</p>
<p className={'text-sm text-gray-500'}>
Help us improve your experience.
</p>
<form onSubmit={handleSubmit}>
<div className="my-4">
<textarea
ref={inputEl}
name="submit-feedback"
id="submit-feedback"
className="mt-2 block min-h-[150px] w-full rounded-md border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 resize-none"
className="mt-2 block min-h-[150px] w-full resize-none rounded-md border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400"
placeholder="Enter your feedback"
required
autoFocus

@ -11,19 +11,19 @@ export function EmptyFriends(props: EmptyFriendsProps) {
const { isCopied, copyText } = useCopyText();
return (
<div class="rounded-md">
<div class="mx-auto flex flex-col items-center p-7 text-center">
<div className="rounded-md">
<div className="mx-auto flex flex-col items-center p-7 text-center">
<img
alt="no friends"
src={UserPlusIcon}
class="mb-2 h-[60px] w-[60px] opacity-10 sm:h-[120px] sm:w-[120px]"
src={UserPlusIcon as any}
className="mb-2 h-[60px] w-[60px] opacity-10 sm:h-[120px] sm:w-[120px]"
/>
<h2 class="text-lg font-bold sm:text-xl">Invite your Friends</h2>
<h2 className="text-lg font-bold sm:text-xl">Invite your Friends</h2>
<p className="mb-4 mt-1 max-w-[400px] text-sm leading-relaxed text-gray-500">
Share the unique link below with your friends to track their skills and progress.
</p>
<div class="flex w-full max-w-[352px] items-center justify-center gap-2 rounded-lg border-2 p-1 text-sm">
<div className="flex w-full max-w-[352px] items-center justify-center gap-2 rounded-lg border-2 p-1 text-sm">
<input
onClick={(e) => {
e.currentTarget.select();
@ -31,11 +31,11 @@ export function EmptyFriends(props: EmptyFriendsProps) {
}}
type="text"
value={befriendUrl}
class="w-full border-none bg-transparent px-1.5 outline-none"
readonly
className="w-full border-none bg-transparent px-1.5 outline-none"
readOnly
/>
<button
class={`flex items-center justify-center gap-1 rounded-md border-0 p-2 px-4 text-sm text-black ${
className={`flex items-center justify-center gap-1 rounded-md border-0 p-2 px-4 text-sm text-black ${
isCopied
? 'bg-green-300 hover:bg-green-300'
: 'bg-gray-200 hover:bg-gray-300'
@ -44,7 +44,7 @@ export function EmptyFriends(props: EmptyFriendsProps) {
copyText(befriendUrl);
}}
>
<img src={CopyIcon} className="h-4 w-4" alt="Invite Friends" />
<img src={CopyIcon as any} className="h-4 w-4" alt="Invite Friends" />
{isCopied ? 'Copied' : 'Copy'}
</button>
</div>

@ -1,4 +1,4 @@
import { useState } from 'preact/hooks';
import { useState } from 'react';
import type { ListFriendsResponse } from './FriendsPage';
import { DeleteUserIcon } from '../ReactIcons/DeleteUserIcon';
import { pageProgressMessage } from '../../stores/page';
@ -177,12 +177,12 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
<div
className={'flex w-full flex-grow items-center justify-center'}
>
<span class=" flex flex-col items-center text-red-500">
<span className=" flex flex-col items-center text-red-500">
<DeleteUserIcon additionalClasses="mr-2 h-8 w-8 mb-1" />
Request Rejected
</span>
</div>
<span class="flex cursor-default items-center justify-center border-t py-2 text-center text-sm">
<span className="flex cursor-default items-center justify-center border-t py-2 text-center text-sm">
Changed your mind?{' '}
<button
className="ml-2 font-medium text-red-700 underline underline-offset-2 hover:text-red-500"
@ -205,12 +205,12 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
<div
className={'flex w-full flex-grow items-center justify-center'}
>
<span class=" flex flex-col items-center text-sm text-red-500">
<span className=" flex flex-col items-center text-sm text-red-500">
<DeleteUserIcon additionalClasses="mr-2 h-8 w-8 mb-1" />
Request Rejected
</span>
</div>
<span class="flex cursor-default items-center justify-center border-t py-2.5 text-center text-sm">
<span className="flex cursor-default items-center justify-center border-t py-2.5 text-center text-sm">
<button
className="ml-2 flex items-center font-medium text-red-700 underline underline-offset-2 hover:text-red-500"
onClick={() => {
@ -233,7 +233,7 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
<div
className={'flex w-full flex-grow items-center justify-center'}
>
<span class=" flex flex-col items-center text-green-500">
<span className=" flex flex-col items-center text-green-500">
<AddedUserIcon additionalClasses="mr-2 h-8 w-8 mb-1" />
Request Sent
</span>

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { pageProgressMessage } from '../../stores/page';
import { useAuth } from '../../hooks/use-auth';
import { AddUserIcon } from '../ReactIcons/AddUserIcon';
@ -133,6 +133,7 @@ export function FriendsPage() {
return (
<button
key={grouping.value}
className={`relative flex items-center justify-center rounded-md border p-1 px-3 text-sm ${
selectedGrouping === grouping.value
? ' border-gray-400 bg-gray-200 '
@ -154,7 +155,7 @@ export function FriendsPage() {
onClick={() => {
setShowInviteFriendPopup(true);
}}
class="flex items-center justify-center gap-1.5 rounded-md border border-gray-400 bg-gray-50 p-1 px-2 text-sm hover:border-gray-500 hover:bg-gray-100"
className="flex items-center justify-center gap-1.5 rounded-md border border-gray-400 bg-gray-50 p-1 px-2 text-sm hover:border-gray-500 hover:bg-gray-100"
>
<AddUserIcon additionalClasses="w-4 h-4" />
Invite Friends

@ -1,4 +1,4 @@
import { useEffect, useRef } from 'preact/hooks';
import { useEffect, useRef } from 'react';
import { useOutsideClick } from '../../hooks/use-outside-click';
import CopyIcon from '../../icons/copy.svg';
import { useCopyText } from '../../hooks/use-copy-text';
@ -22,13 +22,13 @@ export function InviteFriendPopup(props: InviteFriendPopupProps) {
useOutsideClick(popupBodyRef, handleClosePopup);
return (
<div class="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div class="relative h-full w-full max-w-md p-4 md:h-auto">
<div className="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
<div
ref={popupBodyRef}
class="popup-body relative rounded-lg bg-white p-4 shadow"
className="popup-body relative rounded-lg bg-white p-4 shadow"
>
<h3 class="mb-1.5 text-xl font-medium sm:text-2xl">Invite URL</h3>
<h3 className="mb-1.5 text-xl font-medium sm:text-2xl">Invite URL</h3>
<p className="mb-3 hidden text-sm leading-none text-gray-400 sm:block">
Share the link below with your friends to invite them.
</p>
@ -44,7 +44,7 @@ export function InviteFriendPopup(props: InviteFriendPopupProps) {
}}
/>
<button
class={`flex items-center justify-center gap-1 rounded-md border-0 px-3 py-2.5 text-sm text-black ${
className={`flex items-center justify-center gap-1 rounded-md border-0 px-3 py-2.5 text-sm text-black ${
isCopied
? 'bg-green-300 hover:bg-green-300'
: 'bg-gray-200 hover:bg-gray-300'

@ -1,6 +1,6 @@
import { httpGet } from '../../lib/http';
import type { TeamListResponse } from '../TeamDropdown/TeamDropdown';
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
type GetFriendCountsResponse = {
sentCount: number;
@ -31,15 +31,15 @@ export function SidebarFriendsCounter() {
const pendingCount = friendCounts?.receivedCount || 0;
if (!pendingCount) {
return (
<span class="relative mr-1 flex items-center">
<span class="relative rounded-full bg-gray-200 p-1 text-xs" />
<span class="absolute bottom-0 left-0 right-0 top-0 animate-ping rounded-full bg-gray-400 p-1 text-xs" />
<span className="relative mr-1 flex items-center">
<span className="relative rounded-full bg-gray-200 p-1 text-xs" />
<span className="absolute bottom-0 left-0 right-0 top-0 animate-ping rounded-full bg-gray-400 p-1 text-xs" />
</span>
);
}
return (
<span class="flex h-4 w-4 items-center justify-center rounded-full bg-red-500 text-xs font-medium text-white">
<span className="flex h-4 w-4 items-center justify-center rounded-full bg-red-500 text-xs font-medium text-white">
{pendingCount}
</span>
);

@ -1,7 +1,8 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { EmptyProgress } from './EmptyProgress';
import { httpGet } from '../../lib/http';
import { ProgressList } from './ProgressList';
import {isLoggedIn} from "../../lib/jwt";
export type UserProgressResponse = {
resourceId: string;
@ -48,6 +49,11 @@ function renderProgress(progressList: UserProgressResponse) {
}
export function FavoriteRoadmaps() {
const isAuthenticated = isLoggedIn();
if (!isAuthenticated) {
return null;
}
const [isPreparing, setIsPreparing] = useState(true);
const [isLoading, setIsLoading] = useState(true);
const [progress, setProgress] = useState<UserProgressResponse>([]);
@ -109,7 +115,7 @@ export function FavoriteRoadmaps() {
return (
<div
class={`flex min-h-[192px] bg-gradient-to-b transition-opacity duration-500 sm:min-h-[280px] opacity-${containerOpacity} ${
className={`flex min-h-[192px] bg-gradient-to-b transition-opacity duration-500 sm:min-h-[280px] opacity-${containerOpacity} ${
hasProgress && `border-t border-t-[#1e293c]`
}`}
>

@ -24,5 +24,5 @@ import { FavoriteRoadmaps } from './FavoriteRoadmaps';
their career.
</p>
</div>
<FavoriteRoadmaps client:authenticated />
<FavoriteRoadmaps client:only="react" />
</div>

@ -44,7 +44,7 @@ export function ProgressList(props: ProgressListProps) {
<span className="relative z-20">{resource.resourceTitle}</span>
<span
class="absolute bottom-0 left-0 top-0 z-10 bg-[#172a3a]"
className="absolute bottom-0 left-0 top-0 z-10 bg-[#172a3a]"
style={{ width: `${percentageDone}%` }}
></span>
<MarkFavorite

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { httpGet, httpPatch, httpPost } from '../../lib/http';
import { pageProgressMessage } from '../../stores/page';
import type { TeamMemberDocument } from '../TeamMembers/TeamMembersPage';
@ -62,7 +62,7 @@ export function NotificationPage() {
return (
<div>
<div class="mb-8 hidden md:block">
<div className="mb-8 hidden md:block">
<h2 className="text-3xl font-bold sm:text-4xl">Notification</h2>
<p className="mt-2 text-gray-400">Manage your notifications</p>
</div>

@ -1,7 +1,7 @@
import { useStore } from '@nanostores/preact';
import { useStore } from '@nanostores/react';
import SpinnerIcon from '../icons/spinner.svg';
import { pageProgressMessage } from '../stores/page';
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
export interface Props {
initialMessage: string;
@ -29,9 +29,9 @@ export function PageProgress(props: Props) {
<div>
{/* Tailwind based spinner for full page */}
<div className="fixed left-0 top-0 z-50 flex h-full w-full items-center justify-center bg-white bg-opacity-75">
<div class="flex items-center justify-center rounded-md border bg-white px-4 py-2 ">
<div className="flex items-center justify-center rounded-md border bg-white px-4 py-2 ">
<img
src={SpinnerIcon}
src={SpinnerIcon as any}
alt="Loading"
className="h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-4 sm:w-4"
/>

@ -1,8 +1,8 @@
import { useStore } from '@nanostores/preact';
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import CloseIcon from '../icons/close.svg';
import { httpGet } from '../lib/http';
import { sponsorHidden } from '../stores/page';
import { useStore } from '@nanostores/react';
export type PageSponsorType = {
company: string;
@ -77,15 +77,14 @@ export function PageSponsor(props: PageSponsorProps) {
return null;
}
const { url, title, imageUrl, description, company, gaLabel, pageUrl } =
sponsor;
const { url, title, imageUrl, description, company, gaLabel } = sponsor;
return (
<a
href={url}
target="_blank"
rel="noopener sponsored nofollow"
class="fixed bottom-[15px] right-[15px] z-50 flex max-w-[350px] bg-white shadow-lg outline-0 outline-transparent"
className="fixed bottom-[15px] right-[15px] z-50 flex max-w-[350px] bg-white shadow-lg outline-0 outline-transparent"
onClick={() => {
window.fireEvent({
category: 'SponsorClick',
@ -95,28 +94,26 @@ export function PageSponsor(props: PageSponsorProps) {
}}
>
<span
class="absolute right-1.5 top-1.5 text-gray-300 hover:text-gray-800"
className="absolute right-1.5 top-1.5 text-gray-300 hover:text-gray-800"
aria-label="Close"
onClick={(e) => {
e.preventDefault();
e.stopImmediatePropagation();
sponsorHidden.set(true);
}}
>
<img alt="Close" class="h-4 w-4" src={CloseIcon} />
<img alt="Close" className="h-4 w-4" src={CloseIcon as any} />
</span>
<img
src={imageUrl}
class="block h-[150px] w-[104.89px] object-contain lg:h-[169px] lg:w-[118.18px]"
className="block h-[150px] w-[104.89px] object-contain lg:h-[169px] lg:w-[118.18px]"
alt="Sponsor Banner"
/>
<span class="flex flex-1 flex-col justify-between text-sm">
<span class="p-[10px]">
<span class="mb-0.5 block font-semibold">{title}</span>
<span class="block text-gray-500">{description}</span>
<span className="flex flex-1 flex-col justify-between text-sm">
<span className="p-[10px]">
<span className="mb-0.5 block font-semibold">{title}</span>
<span className="block text-gray-500">{description}</span>
</span>
<span class="sponsor-footer">Partner Content</span>
<span className="sponsor-footer">Partner Content</span>
</span>
</a>
);

@ -13,9 +13,9 @@ export function AddUserIcon(props: CheckIconProps) {
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={`relative ${additionalClasses}`}
>
<path d="M14 19a6 6 0 0 0-12 0" />

@ -14,9 +14,9 @@ export function AddedUserIcon(props: CheckIconProps) {
fill="none"
className={`relative ${additionalClasses}`}
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M14 19a6 6 0 0 0-12 0" />
<circle cx="8" cy="9" r="4" />

@ -10,7 +10,7 @@ export function CheckIcon(props: CheckIconProps) {
className={`relative ${additionalClasses}`}
stroke="currentColor"
fill="currentColor"
stroke-width="0"
strokeWidth="0"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>

@ -12,13 +12,13 @@ export function ChevronDownIcon(props: ChevronDownIconProps) {
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="1.5"
strokeWidth="1.5"
stroke="currentColor"
className={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
strokeLinecap="round"
strokeLinejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
/>
</svg>

@ -12,7 +12,7 @@ export function CloseIcon(props: CloseIconProps) {
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="1.5"
strokeWidth="1.5"
stroke="currentColor"
className={className}
>

@ -14,9 +14,9 @@ export function DeleteUserIcon(props: CheckIconProps) {
fill="none"
className={`relative ${additionalClasses}`}
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />

@ -18,23 +18,23 @@ export function ErrorIcon(props: ErrorIconProps) {
d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"
fill="currentColor"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15 9L9 15"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9 9L15 15"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);

@ -18,23 +18,23 @@ export function InfoIcon(props: InfoIconProps) {
d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"
fill="currentColor"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12 16V12"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12 8H12.01"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);

@ -11,9 +11,9 @@ export function MailIcon(props: MailIconProps) {
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<rect width="20" height="16" x="2" y="4" rx="2" />

@ -11,9 +11,9 @@ export function ShareIcon(props: ShareIconProps) {
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />

@ -11,7 +11,6 @@ export function Spinner({
outerFill = '#404040',
innerFill = '#94a3b8',
}: SpinnerProps) {
className += className?.includes('w-') ? '' : ' w-3.5 h-3.5';
return (
@ -23,15 +22,15 @@ export function Spinner({
>
{isDualRing && (
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M46.5 93C72.1812 93 93 72.1812 93 46.5C93 20.8188 72.1812 0 46.5 0C20.8188 0 0 20.8188 0 46.5C0 72.1812 20.8188 93 46.5 93ZM46.5 77C63.3447 77 77 63.3447 77 46.5C77 29.6553 63.3447 16 46.5 16C29.6553 16 16 29.6553 16 46.5C16 63.3447 29.6553 77 46.5 77Z"
style={`fill: ${outerFill};`}
style={{ fill: outerFill }}
></path>
)}
<path
d="M84.9746 49.5667C89.3257 49.9135 93.2042 46.6479 92.81 42.3008C92.3588 37.3251 91.1071 32.437 89.0872 27.8298C86.0053 20.7998 81.2311 14.6422 75.1905 9.90623C69.15 5.17027 62.031 2.00329 54.4687 0.687889C49.5126 -0.174203 44.467 -0.223422 39.5274 0.525737C35.2118 1.18024 32.966 5.72596 34.3411 9.86865V9.86865C35.7161 14.0113 40.2118 16.1424 44.5681 15.8677C46.9635 15.7166 49.3773 15.8465 51.7599 16.2609C56.7515 17.1291 61.4505 19.2196 65.4377 22.3456C69.4249 25.4717 72.5762 29.5362 74.6105 34.1764C75.5815 36.3912 76.2835 38.7044 76.7084 41.0666C77.4811 45.3626 80.6234 49.2199 84.9746 49.5667V49.5667Z"
style={`fill: ${innerFill};`}
style={{ fill: innerFill }}
></path>
</svg>
);

@ -13,9 +13,9 @@ export function StopIcon(props: CheckIconProps) {
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={`relative ${additionalClasses}`}
>
<circle cx="12" cy="12" r="10" />

@ -12,9 +12,9 @@ export function TrashIcon(props: TrashIconProps) {
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M3 6h18" />

@ -18,23 +18,23 @@ export function WarningIcon(props: WarningIconProps) {
d="M21.7304 18.0002L13.7304 4.00022C13.556 3.69243 13.303 3.43641 12.9973 3.25829C12.6917 3.08017 12.3442 2.98633 11.9904 2.98633C11.6366 2.98633 11.2892 3.08017 10.9835 3.25829C10.6778 3.43641 10.4249 3.69243 10.2504 4.00022L2.25042 18.0002C2.0741 18.3056 1.98165 18.6521 1.98243 19.0047C1.98321 19.3573 2.0772 19.7035 2.25486 20.008C2.43253 20.3126 2.68757 20.5648 2.99411 20.7391C3.30066 20.9133 3.64783 21.0034 4.00042 21.0002H20.0004C20.3513 20.9999 20.6959 20.9072 20.9997 20.7315C21.3035 20.5558 21.5556 20.3033 21.7309 19.9993C21.9062 19.6954 21.9985 19.3506 21.9984 18.9997C21.9983 18.6488 21.9059 18.3041 21.7304 18.0002Z"
fill="currentColor"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12 9V13"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12 17H12.01"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);

@ -55,7 +55,7 @@ const { isSecondaryBanner = false, resourceId, resourceType } = Astro.props;
<ProgressShareButton
resourceId={resourceId}
resourceType={resourceType}
client:load
client:only="react"
/>
<button
data-popup='progress-help'
@ -86,7 +86,7 @@ const { isSecondaryBanner = false, resourceId, resourceType } = Astro.props;
<ProgressShareButton
resourceId={resourceId}
resourceType={resourceType}
client:load
client:only="react"
/>
</div>
</div>

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { httpGet, httpPatch } from '../lib/http';
import BuildingIcon from '../icons/building.svg';
import ErrorIcon from '../icons/error.svg';
@ -92,7 +92,7 @@ export function RespondInviteForm() {
/>
<h2 className={'mb-1 text-2xl font-bold'}>Error</h2>
<p class="mb-4 text-base leading-6 text-gray-600">
<p className="mb-4 text-base leading-6 text-gray-600">
{error || 'There was a problem, please try again.'}
</p>
@ -117,19 +117,19 @@ export function RespondInviteForm() {
/>
<h2 className={'mb-1 text-2xl font-bold'}>Join Team</h2>
<p class="mb-3 text-base leading-6 text-gray-600">
<p className="mb-3 text-base leading-6 text-gray-600">
You have been invited to join the team{' '}
<strong id="team-name">{invite?.team?.name}</strong>.
</p>
{!isAuthenticated && (
<div class="mx-auto w-full duration-500 sm:max-w-md">
<div class="flex w-full items-center gap-2">
<div className="mx-auto w-full duration-500 sm:max-w-md">
<div className="flex w-full items-center gap-2">
<button
onClick={() => showLoginPopup()}
data-popup="login-popup"
type="button"
class="flex-grow cursor-pointer rounded-lg bg-gray-200 px-3 py-2 text-center"
className="flex-grow cursor-pointer rounded-lg bg-gray-200 px-3 py-2 text-center"
>
Login to respond
</button>

@ -34,9 +34,8 @@ export function Editor(props: EditorProps) {
e.target.select();
copyText(e.target.value);
}}
>
{text}
</textarea>
value={text}
/>
</div>
);
}

@ -1,4 +1,4 @@
import { useState } from 'preact/hooks';
import { useState } from 'react';
import { useCopyText } from '../../hooks/use-copy-text';
import { useAuth } from '../../hooks/use-auth';
@ -113,7 +113,7 @@ export function RoadCardPage() {
<div className="flex items-start gap-4 mx-0 sm:-mx-10 px-0 sm:px-10 border-b py-4">
<StepCounter step={4} />
<div class="flex-grow">
<div className="flex-grow">
<StepLabel label="Share your #RoadCard with others" />
<div className={'rounded-md border bg-gray-50 p-2 text-center'}>
<a

@ -1,5 +1,5 @@
import { httpGet } from '../../lib/http';
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { pageProgressMessage } from '../../stores/page';
import type { UserProgressResponse } from '../HeroSection/FavoriteRoadmaps';
import { SelectionButton } from './SelectionButton';
@ -33,19 +33,24 @@ export function RoadmapSelect(props: RoadmapSelectProps) {
}, []);
const canSelectMore = selectedRoadmaps.length < 4;
const allProgress = progressList?.filter(
(progress) => progress.resourceType === 'roadmap'
) || [];
const allProgress =
progressList?.filter((progress) => progress.resourceType === 'roadmap') ||
[];
return (
<div className="flex flex-wrap gap-1">
{allProgress?.length === 0 && <p className={'text-sm text-gray-400 italic'}>No progress tracked so far.</p>}
{allProgress?.length === 0 && (
<p className={'text-sm italic text-gray-400'}>
No progress tracked so far.
</p>
)}
{allProgress?.map((progress) => {
const isSelected = selectedRoadmaps.includes(progress.resourceId);
const canSelect = isSelected || canSelectMore;
return (
<SelectionButton
key={progress.resourceId}
text={progress.resourceTitle}
isDisabled={!canSelect}
isSelected={isSelected}

@ -46,7 +46,7 @@ const isRoadmapReady = !isUpcoming;
resourceId={roadmapId}
resourceType='roadmap'
className='text-gray-500 !opacity-100 hover:text-gray-600 [&>svg]:stroke-[0.4] [&>svg]:stroke-gray-400 hover:[&>svg]:stroke-gray-600 [&>svg]:h-4 [&>svg]:w-4 sm:[&>svg]:h-5 sm:[&>svg]:w-5 ml-1.5 relative focus:outline-0'
client:load
client:only="react"
/>
</h1>
<p class='text-sm text-gray-500 sm:text-lg'>{description}</p>

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
export type OptionType = {
value: string;
@ -118,12 +118,12 @@ export function SearchSelector({
{isActive && (
<div
class="absolute top-full z-50 mt-2 w-full rounded-md bg-gray-100 px-2 py-2"
className="absolute top-full z-50 mt-2 w-full rounded-md bg-gray-100 px-2 py-2"
ref={dropdownRef}
>
<div className="flex flex-col">
{searchResults.length === 0 && (
<div class="p-5 text-center text-sm text-gray-400">
<div className="p-5 text-center text-sm text-gray-400">
No results found
</div>
)}
@ -133,7 +133,7 @@ export function SearchSelector({
<>
<button
type="button"
class={`flex w-full items-center rounded p-2 text-sm ${
className={`flex w-full items-center rounded p-2 text-sm ${
counter === activeCounter ? 'bg-gray-200' : ''
}`}
onMouseOver={() => setActiveCounter(counter)}

@ -34,7 +34,7 @@ export function Stepper(props: StepperProps) {
/>
)}
{!isComplete && (
<span class="mr-2 font-semibold">{stepCounter + 1}</span>
<span className="mr-2 font-semibold">{stepCounter + 1}</span>
)}
<span className="flex flex-grow">{step.label}</span>
</li>

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import ChevronDown from '../../icons/dropdown.svg';
import { httpGet } from '../../lib/http';
import { useAuth } from '../../hooks/use-auth';
@ -6,7 +6,7 @@ import { useOutsideClick } from '../../hooks/use-outside-click';
import { Spinner } from '../ReactIcons/Spinner';
import type { AllowedRoles } from '../CreateTeam/RoleDropdown';
import { $currentTeam, $teamList } from '../../stores/team';
import { useStore } from '@nanostores/preact';
import { useStore } from '@nanostores/react';
import { useTeamId } from '../../hooks/use-team-id';
import { useToast } from '../../hooks/use-toast';
import type { ValidTeamType } from '../CreateTeam/Step0';
@ -103,10 +103,10 @@ export function TeamDropdown() {
<span>Choose Team</span>
{shouldShowTeamsIndicator && (
<span class="mr-1 inline-flex h-1 w-1 items-center justify-center font-medium text-blue-300">
<span class="relative flex items-center">
<span class="relative rounded-full bg-gray-200 p-1 text-xs" />
<span class="absolute bottom-0 left-0 right-0 top-0 animate-ping rounded-full bg-gray-400 p-1 text-xs" />
<span className="mr-1 inline-flex h-1 w-1 items-center justify-center font-medium text-blue-300">
<span className="relative flex items-center">
<span className="relative rounded-full bg-gray-200 p-1 text-xs" />
<span className="absolute bottom-0 left-0 right-0 top-0 animate-ping rounded-full bg-gray-400 p-1 text-xs" />
</span>
</span>
)}
@ -166,7 +166,7 @@ export function TeamDropdown() {
}
return (
<li>
<li key={team?._id}>
<a
className="flex w-full cursor-pointer items-center gap-2 rounded p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
href={`${pageLink}`}
@ -194,7 +194,7 @@ export function TeamDropdown() {
</div>
)}
</div>
<hr class="my-4" />
<hr className="my-4" />
</>
);
}

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { 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';
@ -24,7 +24,7 @@ export function InviteMemberPopup(props: InviteMemberPopupProps) {
emailRef?.current?.focus();
}, []);
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError('');
@ -55,19 +55,21 @@ export function InviteMemberPopup(props: InviteMemberPopupProps) {
useOutsideClick(popupBodyRef, handleClosePopup);
return (
<div class="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div class="relative h-full w-full max-w-md p-4 md:h-auto">
<div className="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
<div
ref={popupBodyRef}
class="popup-body relative rounded-lg bg-white p-4 shadow"
className="popup-body relative rounded-lg bg-white p-4 shadow"
>
<h3 class="mb-1.5 text-xl sm:text-2xl font-medium">Invite Member</h3>
<p className="mb-3 text-sm leading-none text-gray-400 hidden sm:block">
<h3 className="mb-1.5 text-xl font-medium sm:text-2xl">
Invite Member
</h3>
<p className="mb-3 hidden text-sm leading-none text-gray-400 sm:block">
Enter the email and role below to invite a member.
</p>
<form onSubmit={handleSubmit}>
<div className="mt-0 sm:mt-4 my-4 flex flex-col gap-2">
<div className="my-4 mt-0 flex flex-col gap-2 sm:mt-4">
<input
ref={emailRef}
type="email"
@ -108,7 +110,7 @@ export function InviteMemberPopup(props: InviteMemberPopupProps) {
<button
type="submit"
disabled={isLoading || !email}
class="flex-grow cursor-pointer rounded-lg bg-black py-2 text-center text-white disabled:opacity-40"
className="flex-grow cursor-pointer rounded-lg bg-black py-2 text-center text-white disabled:opacity-40"
>
{isLoading ? 'Please wait ..' : 'Invite'}
</button>

@ -1,4 +1,4 @@
import { useState } from 'preact/hooks';
import { useState } from 'react';
import { LeaveTeamPopup } from './LeaveTeamPopup';
type LeaveTeamButtonProps = {

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import { httpDelete } from '../../lib/http';
import { useTeamId } from '../../hooks/use-team-id';
import { useOutsideClick } from '../../hooks/use-outside-click';
@ -59,13 +59,13 @@ export function LeaveTeamPopup(props: LeaveTeamPopupProps) {
useOutsideClick(popupBodyRef, handleClosePopup);
return (
<div class="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div class="relative h-full w-full max-w-md p-4 md:h-auto">
<div className="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
<div
ref={popupBodyRef}
class="popup-body relative rounded-lg bg-white p-4 shadow"
className="popup-body relative rounded-lg bg-white p-4 shadow"
>
<h2 class="text-2xl font-semibold text-black">
<h2 className="text-2xl font-semibold text-black">
Leave Team
</h2>
<p className="text-gray-500">

@ -1,4 +1,4 @@
import { useRef, useState } from 'preact/hooks';
import { useRef, useState } from 'react';
import type { TeamMemberDocument } from './TeamMembersPage';
import MoreIcon from '../../icons/more-vertical.svg';
import { useOutsideClick } from '../../hooks/use-outside-click';

@ -3,7 +3,7 @@ import { MemberActionDropdown } from './MemberActionDropdown';
import { MemberRoleBadge } from './RoleBadge';
import type { TeamMemberItem } from './TeamMembersPage';
import { $canManageCurrentTeam } from '../../stores/team';
import { useStore } from '@nanostores/preact';
import { useStore } from '@nanostores/react';
type TeamMemberProps = {
member: TeamMemberItem;
@ -91,7 +91,7 @@ export function TeamMemberItem(props: TeamMemberProps) {
</div>
<div className="flex shrink-0 items-center text-sm">
<span class={'hidden sm:block'}>
<span className={'hidden sm:block'}>
<MemberRoleBadge role={member.role} />
</span>
{canManageCurrentTeam && (

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import { httpDelete, httpGet, httpPatch } from '../../lib/http';
import { useAuth } from '../../hooks/use-auth';
import { pageProgressMessage } from '../../stores/page';
@ -9,7 +9,7 @@ import type { AllowedMemberStatus } from '../TeamDropdown/TeamDropdown';
import { InviteMemberPopup } from './InviteMemberPopup';
import { getUrlParams } from '../../lib/browser';
import { UpdateMemberPopup } from './UpdateMemberPopup';
import { useStore } from '@nanostores/preact';
import { useStore } from '@nanostores/react';
import { $canManageCurrentTeam } from '../../stores/team';
import { useToast } from '../../hooks/use-toast';
import { TeamMemberItem } from './TeamMemberItem';

@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { httpPost, httpPut } from '../../lib/http';
import { 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';
@ -20,7 +20,7 @@ export function UpdateMemberPopup(props: InviteMemberPopupProps) {
const [error, setError] = useState('');
const { teamId } = useTeamId();
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError('');
@ -52,19 +52,21 @@ export function UpdateMemberPopup(props: InviteMemberPopupProps) {
useOutsideClick(popupBodyRef, handleClosePopup);
return (
<div class="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div class="relative h-full w-full max-w-md p-4 md:h-auto">
<div className="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
<div
ref={popupBodyRef}
class="popup-body relative rounded-lg bg-white p-4 shadow"
className="popup-body relative rounded-lg bg-white p-4 shadow"
>
<h3 class="mb-1.5 text-xl sm:text-2xl font-medium">Update Role</h3>
<p className="mb-3 text-sm leading-none text-gray-400 hidden sm:block">
<h3 className="mb-1.5 text-xl font-medium sm:text-2xl">
Update Role
</h3>
<p className="mb-3 hidden text-sm leading-none text-gray-400 sm:block">
Select the role to update for this member
</p>
<form onSubmit={handleSubmit}>
<div className="mt-0 sm:mt-4 my-4 flex flex-col gap-2">
<div className="my-4 mt-0 flex flex-col gap-2 sm:mt-4">
<span className="mt-2 block w-full rounded-md bg-gray-100 p-2">
{member.invitedEmail}
</span>
@ -96,7 +98,7 @@ export function UpdateMemberPopup(props: InviteMemberPopupProps) {
<button
type="submit"
disabled={isLoading || !selectedRole}
class="flex-grow cursor-pointer rounded-lg bg-black py-2 text-center text-white disabled:opacity-40"
className="flex-grow cursor-pointer rounded-lg bg-black py-2 text-center text-white disabled:opacity-40"
>
{isLoading ? 'Please wait ..' : 'Update Role'}
</button>

@ -1,4 +1,4 @@
import { useState } from 'preact/hooks';
import { useState } from 'react';
import type { GroupByRoadmap, TeamMember } from './TeamProgressPage';
import { getUrlParams } from '../../lib/browser';
import ExternalLinkIcon from '../../icons/external-link.svg';

@ -1,5 +1,5 @@
import type { TeamMember } from './TeamProgressPage';
import { useState } from 'preact/hooks';
import { useState } from 'react';
type MemberProgressItemProps = {
member: TeamMember;
@ -29,7 +29,7 @@ export function MemberProgressItem(props: MemberProgressItemProps) {
: '/images/default-avatar.png'
}
alt={member.name || ''}
className="h-8 w-8 rounded-full"
className="min-w-[32px] min-h-[32px] h-8 w-8 rounded-full"
/>
<div className="inline-grid w-full">
{!isMyProgress && (

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import { wireframeJSONToSVG } from 'roadmap-renderer';
import { Spinner } from '../ReactIcons/Spinner';
import '../FrameRenderer/FrameRenderer.css';
@ -16,7 +16,7 @@ import CloseIcon from '../../icons/close.svg';
import { useToast } from '../../hooks/use-toast';
import { useAuth } from '../../hooks/use-auth';
import { pageProgressMessage } from '../../stores/page';
import { useStore } from '@nanostores/preact';
import { useStore } from '@nanostores/react';
import { $currentTeam } from '../../stores/team';
export type ProgressMapProps = {
@ -277,14 +277,14 @@ export function MemberProgressModal(props: ProgressMapProps) {
const progressPercentage = Math.round((memberDone / memberTotal) * 100);
return (
<div class="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
<div className="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
<div
id={currentTeam?.type === 'company' ? 'customized-roadmap' : 'original-roadmap'}
class="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto"
className="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto"
>
<div
ref={popupBodyEl}
class="popup-body relative rounded-lg bg-white pt-[1px] shadow"
className="popup-body relative rounded-lg bg-white pt-[1px] shadow"
>
{isCurrentUser && (
<div className="sticky top-1 mx-1 mb-0 mt-1 rounded-xl bg-gray-900 p-4 text-gray-300">
@ -344,11 +344,11 @@ export function MemberProgressModal(props: ProgressMapProps) {
</div>
)}
<p
class={`-mx-4 mb-3 flex items-center justify-start border-b border-t px-4 py-2 text-sm sm:hidden ${
className={`-mx-4 mb-3 flex items-center justify-start border-b border-t px-4 py-2 text-sm sm:hidden ${
isLoading ? 'striped-loader' : ''
}`}
>
<span class="mr-2.5 block rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900">
<span className="mr-2.5 block rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900">
<span>{progressPercentage}</span>% Done
</span>
@ -357,18 +357,18 @@ export function MemberProgressModal(props: ProgressMapProps) {
</span>
</p>
<p
class={`-mx-4 mb-3 hidden items-center justify-center border-b border-t py-2 text-sm sm:flex ${
className={`-mx-4 mb-3 hidden items-center justify-center border-b border-t py-2 text-sm sm:flex ${
isLoading ? 'striped-loader' : ''
}`}
>
<span class="mr-2.5 block rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900">
<span className="mr-2.5 block rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900">
<span>{progressPercentage}</span>% Done
</span>
<span>
<span>{memberDone}</span> completed
</span>
<span class="mx-1.5 text-gray-400">·</span>
<span className="mx-1.5 text-gray-400">·</span>
<span>
<span data-progress-learning="">{memberLearning}</span> in
progress
@ -376,7 +376,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
{memberSkipped > 0 && (
<>
<span class="mx-1.5 text-gray-400">·</span>
<span className="mx-1.5 text-gray-400">·</span>
<span>
<span data-progress-skipped="">{memberSkipped}</span>{' '}
skipped
@ -384,7 +384,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
</>
)}
<span class="mx-1.5 text-gray-400">·</span>
<span className="mx-1.5 text-gray-400">·</span>
<span>
<span data-progress-total="">{memberTotal}</span> Total
</span>
@ -398,7 +398,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
></div>
{isLoading && (
<div class="flex w-full justify-center">
<div className="flex w-full justify-center">
<Spinner
isDualRing={false}
className="mb-4 mt-2 h-4 w-4 animate-spin fill-blue-600 text-gray-200 sm:h-8 sm:w-8"
@ -414,7 +414,7 @@ export function MemberProgressModal(props: ProgressMapProps) {
onClick={onClose}
>
<img alt={'close'} src={CloseIcon} className="h-4 w-4" />
<span class="sr-only">Close modal</span>
<span className="sr-only">Close modal</span>
</button>
</div>
</div>

@ -1,10 +1,9 @@
import { useEffect, useState } from 'preact/hooks';
import { useTeamId } from '../../hooks/use-team-id';
import { useEffect, useState } from 'react';
import { httpGet } from '../../lib/http';
import { pageProgressMessage } from '../../stores/page';
import { MemberProgressItem } from './MemberProgressItem';
import { useToast } from '../../hooks/use-toast';
import { useStore } from '@nanostores/preact';
import { useStore } from '@nanostores/react';
import { $currentTeam } from '../../stores/team';
import { GroupRoadmapItem } from './GroupRoadmapItem';
import { getUrlParams, setUrlParams } from '../../lib/browser';
@ -49,8 +48,7 @@ const groupingTypes = [
] as const;
export function TeamProgressPage() {
const { teamId } = useTeamId();
const { gb: groupBy } = getUrlParams();
const { t: teamId, gb: groupBy } = getUrlParams();
const [isLoading, setIsLoading] = useState(true);
const toast = useToast();
@ -100,15 +98,6 @@ export function TeamProgressPage() {
});
}, [teamId]);
if (isLoading) {
return null;
}
if (!teamId) {
window.location.href = '/';
return;
}
useEffect(() => {
if (!selectedGrouping) {
return;
@ -145,6 +134,15 @@ export function TeamProgressPage() {
});
}
if (!teamId) {
window.location.href = '/';
return;
}
if (isLoading) {
return null;
}
return (
<div>
{showMemberProgress && (
@ -170,6 +168,7 @@ export function TeamProgressPage() {
<div className="flex items-center gap-2">
{groupingTypes.map((grouping) => (
<button
key={grouping.value}
className={`rounded-md border p-1 px-2 text-sm ${
selectedGrouping === grouping.value
? ' border-gray-400 bg-gray-200 '
@ -205,6 +204,7 @@ export function TeamProgressPage() {
<div className="grid gap-4 sm:grid-cols-2">
{teamMembers.map((member) => (
<MemberProgressItem
key={member._id}
member={member}
isMyProgress={member?.email === user?.email}
onShowResourceProgress={(resourceId) => {

@ -1,5 +1,5 @@
import { getUrlParams } from '../lib/browser';
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
import type { TeamDocument } from './CreateTeam/CreateTeamForm';
import type { TeamResourceConfig } from './CreateTeam/RoadmapSelector';
import { httpGet, httpPut } from '../lib/http';
@ -9,7 +9,7 @@ import RoadmapIcon from '../icons/roadmap.svg';
import PlusIcon from '../icons/plus.svg';
import type { PageType } from './CommandMenu/CommandMenu';
import { UpdateTeamResourceModal } from './CreateTeam/UpdateTeamResourceModal';
import { useStore } from '@nanostores/preact';
import { useStore } from '@nanostores/react';
import { $canManageCurrentTeam } from '../stores/team';
import { useToast } from '../hooks/use-toast';
import { SelectRoadmapModal } from './CreateTeam/SelectRoadmapModal';
@ -248,7 +248,7 @@ export function TeamRoadmaps() {
'...';
return (
<div className="flex flex-col items-start rounded-md border border-gray-300">
<div key={resourceId} className="flex flex-col items-start rounded-md border border-gray-300">
<div className={'w-full px-3 py-4'}>
<a
href={`/${resourceId}?t=${teamId}`}

@ -1,14 +1,13 @@
import { useEffect, useState } from 'preact/hooks';
import { FormEvent, useEffect, useState } from 'react';
import { httpGet, httpPut } from '../../lib/http';
import { Spinner } from '../ReactIcons/Spinner';
import { useAuth } from '../../hooks/use-auth';
import UploadProfilePicture from '../UpdateProfile/UploadProfilePicture';
import type { TeamDocument } from '../CreateTeam/CreateTeamForm';
import { pageProgressMessage } from '../../stores/page';
import { useTeamId } from '../../hooks/use-team-id';
import { DeleteTeamPopup } from '../DeleteTeamPopup';
import { $currentTeam, $isCurrentTeamAdmin } from '../../stores/team';
import { useStore } from '@nanostores/preact';
import { $isCurrentTeamAdmin } from '../../stores/team';
import { useStore } from '@nanostores/react';
import { useToast } from '../../hooks/use-toast';
export function UpdateTeamForm() {
@ -44,7 +43,7 @@ export function UpdateTeamForm() {
setIsDisabled(!isCurrentTeamAdmin);
}, [isCurrentTeamAdmin]);
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
if (!name || !teamType) {
@ -123,7 +122,7 @@ export function UpdateTeamForm() {
<form onSubmit={handleSubmit}>
<div className="mt-4 flex w-full flex-col">
<label
for="name"
htmlFor="name"
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]'
>
Name
@ -142,7 +141,7 @@ export function UpdateTeamForm() {
</div>
<div className="mt-4 flex w-full flex-col">
<label
for="website"
htmlFor="website"
className={`text-sm leading-none text-slate-500 ${
teamType === 'company' ? 'after:content-["*"]' : ''
}`}
@ -164,7 +163,7 @@ export function UpdateTeamForm() {
{teamType === 'company' && (
<div className="mt-4 flex w-full flex-col">
<label
for="linkedIn"
htmlFor="linkedIn"
className="text-sm leading-none text-slate-500"
>
LinkedIn URL
@ -182,7 +181,10 @@ export function UpdateTeamForm() {
</div>
)}
<div className="mt-4 flex w-full flex-col">
<label for="gitHub" className="text-sm leading-none text-slate-500">
<label
htmlFor="gitHub"
className="text-sm leading-none text-slate-500"
>
GitHub URL
</label>
<input
@ -198,7 +200,7 @@ export function UpdateTeamForm() {
</div>
<div className="mt-4 flex w-full flex-col">
<label
for="type"
htmlFor="type"
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]'
>
Type
@ -208,12 +210,12 @@ export function UpdateTeamForm() {
id="type"
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
disabled={isDisabled}
value={teamType}
value={teamType || ''}
onChange={(e) =>
setTeamType((e.target as HTMLSelectElement).value as any)
}
>
<option value="" selected>
<option value="">
Select type
</option>
<option value="company">Company</option>
@ -224,7 +226,7 @@ export function UpdateTeamForm() {
{teamType === '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-["*"]'
>
Team size
@ -269,7 +271,7 @@ export function UpdateTeamForm() {
{isCurrentTeamAdmin && (
<>
<hr class="my-8" />
<hr className="my-8" />
{isDeleting && (
<DeleteTeamPopup
onClose={() => {
@ -277,15 +279,15 @@ export function UpdateTeamForm() {
}}
/>
)}
<h2 class="text-xl font-bold sm:text-2xl">Delete Team</h2>
<p class="mt-2 text-gray-400">
<h2 className="text-xl font-bold sm:text-2xl">Delete Team</h2>
<p className="mt-2 text-gray-400">
Permanently delete this team and all of its resources.
</p>
<button
onClick={() => setIsDeleting(true)}
data-popup="delete-team-popup"
class="font-regular mt-4 w-full rounded-lg bg-red-600 py-2 text-base text-white outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1"
className="font-regular mt-4 w-full rounded-lg bg-red-600 py-2 text-base text-white outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1"
>
Delete Team
</button>

@ -1,4 +1,3 @@
import type { FunctionalComponent } from 'preact';
import { TeamDropdown } from './TeamDropdown/TeamDropdown';
import ChevronDown from '../icons/dropdown.svg';
import { useTeamId } from '../hooks/use-team-id';
@ -7,14 +6,18 @@ import SettingsIcon from '../icons/cog.svg';
import ChatIcon from '../icons/chat.svg';
import MapIcon from '../icons/map.svg';
import GroupIcon from '../icons/group.svg';
import { useState } from 'preact/hooks';
import { useStore } from '@nanostores/preact';
import { useState } from 'react';
import type { ReactNode } from 'react';
import { useStore } from '@nanostores/react';
import { $currentTeam } from '../stores/team';
import { SubmitFeedbackPopup } from './Feedback/SubmitFeedbackPopup';
export const TeamSidebar: FunctionalComponent<{
type TeamSidebarProps = {
activePageId: string;
}> = ({ activePageId, children }) => {
children: ReactNode;
};
export function TeamSidebar({ activePageId, children }: TeamSidebarProps) {
const [menuShown, setMenuShown] = useState(false);
const currentTeam = useStore($currentTeam);
const [showFeedbackPopup, setShowFeedbackPopup] = useState(false);
@ -51,9 +54,9 @@ export const TeamSidebar: FunctionalComponent<{
return (
<>
<div class="relative mb-5 block border-b p-4 shadow-inner md:hidden">
<div className="relative mb-5 block border-b p-4 shadow-inner md:hidden">
<button
class="flex h-10 w-full items-center justify-between rounded-md border bg-white px-2 text-center text-sm font-medium text-gray-900"
className="flex h-10 w-full items-center justify-between rounded-md border bg-white px-2 text-center text-sm font-medium text-gray-900"
id="settings-menu"
aria-haspopup="true"
aria-expanded="true"
@ -63,21 +66,21 @@ export const TeamSidebar: FunctionalComponent<{
sidebarLinks.find((sidebarLink) => sidebarLink.id === activePageId)
?.title
}
<img alt="menu" src={ChevronDown} class="h-4 w-4" />
<img alt="menu" src={ChevronDown} className="h-4 w-4" />
</button>
{menuShown && (
<ul
id="settings-menu-dropdown"
class="absolute left-0 right-0 z-50 mt-1 space-y-1.5 bg-white p-2 shadow-lg"
className="absolute left-0 right-0 z-50 mt-1 space-y-1.5 bg-white p-2 shadow-lg"
>
<li>
<a
href="/team"
class={`flex w-full items-center rounded px-3 py-1.5 text-sm text-slate-900 hover:bg-slate-200 ${
className={`flex w-full items-center rounded px-3 py-1.5 text-sm text-slate-900 hover:bg-slate-200 ${
activePageId === 'team' ? 'bg-slate-100' : ''
}`}
>
<img alt={'teams'} src={GroupIcon} class={`mr-2 h-4 w-4`} />
<img alt={'teams'} src={GroupIcon} className={`mr-2 h-4 w-4`} />
Personal Account / Teams
</a>
</li>
@ -85,10 +88,10 @@ export const TeamSidebar: FunctionalComponent<{
const isActive = activePageId === sidebarLink.id;
return (
<li>
<li key={sidebarLink.id}>
<a
href={sidebarLink.href}
class={`flex w-full items-center rounded px-3 py-1.5 text-sm text-slate-900 hover:bg-slate-200 ${
className={`flex w-full items-center rounded px-3 py-1.5 text-sm text-slate-900 hover:bg-slate-200 ${
isActive ? 'bg-slate-100' : ''
}`}
>
@ -127,25 +130,25 @@ export const TeamSidebar: FunctionalComponent<{
/>
)}
<div class="container flex min-h-screen items-stretch">
<aside class="hidden w-44 shrink-0 border-r border-slate-200 py-10 md:block">
<div className="container flex min-h-screen items-stretch">
<aside className="hidden w-44 shrink-0 border-r border-slate-200 py-10 md:block">
<TeamDropdown />
<nav>
<ul class="space-y-1">
<ul className="space-y-1">
{sidebarLinks.map((sidebarLink) => {
const isActive = activePageId === sidebarLink.id;
return (
<li>
<li key={sidebarLink.id}>
<a
href={sidebarLink.href}
class={`font-regular flex w-full items-center border-r-2 px-2 py-1.5 text-sm ${
className={`font-regular flex w-full items-center border-r-2 px-2 py-1.5 text-sm ${
isActive
? 'border-r-black bg-gray-100 text-black'
: 'border-r-transparent text-gray-500 hover:border-r-gray-300'
}`}
>
<span class="flex flex-grow items-center justify-between">
<span className="flex flex-grow items-center justify-between">
<span className="flex">
<img
alt="menu icon"
@ -155,9 +158,9 @@ export const TeamSidebar: FunctionalComponent<{
{sidebarLink.title}
</span>
{sidebarLink.hasWarning && (
<span class="relative mr-1 flex items-center">
<span class="relative rounded-full bg-red-200 p-1 text-xs" />
<span class="absolute bottom-0 left-0 right-0 top-0 animate-ping rounded-full bg-red-400 p-1 text-xs" />
<span className="relative mr-1 flex items-center">
<span className="relative rounded-full bg-red-200 p-1 text-xs" />
<span className="absolute bottom-0 left-0 right-0 top-0 animate-ping rounded-full bg-red-400 p-1 text-xs" />
</span>
)}
</span>
@ -171,7 +174,7 @@ export const TeamSidebar: FunctionalComponent<{
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 src={ChatIcon} className="mr-2 h-4 w-4" />
<img alt={'feedback'} src={ChatIcon} className="mr-2 h-4 w-4" />
Send Feedback
</button>
</nav>
@ -180,4 +183,4 @@ export const TeamSidebar: FunctionalComponent<{
</div>
</>
);
};
}

@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'preact/hooks';
import { useState, useEffect, useRef } from 'react';
import type { TeamDocument } from '../CreateTeam/CreateTeamForm';
import type { TeamResourceConfig } from '../CreateTeam/RoadmapSelector';
import { httpGet } from '../../lib/http';
@ -44,7 +44,8 @@ export function TeamVersions(props: TeamVersionsProps) {
const [selectedTeamVersion, setSelectedTeamVersion] = useState<
TeamVersionsResponse[0] | null
>(null);
let shouldShowAvatar = true;
let shouldShowAvatar: boolean;
const selectedAvatar = selectedTeamVersion
? selectedTeamVersion.team.avatar
: user?.avatar;
@ -55,10 +56,8 @@ export function TeamVersions(props: TeamVersionsProps) {
// Show avatar if team has one, or if user has one otherwise use first letter of name
if (selectedTeamVersion?.team.avatar) {
shouldShowAvatar = true;
} else if (!selectedTeamVersion && user?.avatar) {
shouldShowAvatar = true;
} else {
shouldShowAvatar = false;
shouldShowAvatar = !!(!selectedTeamVersion && user?.avatar);
}
useOutsideClick(teamDropdownRef, () => {
@ -105,10 +104,6 @@ export function TeamVersions(props: TeamVersionsProps) {
});
}, []);
if (isPreparing) {
return null;
}
useEffect(() => {
clearResourceProgress();
if (!selectedTeamVersion) {
@ -127,6 +122,10 @@ export function TeamVersions(props: TeamVersionsProps) {
});
}, [selectedTeamVersion]);
if (isPreparing) {
return null;
}
if (!teamVersions.length) {
return null;
}
@ -145,8 +144,8 @@ export function TeamVersions(props: TeamVersionsProps) {
</span>
<img
alt="Dropdown"
src={DropdownIcon}
class="h-3 w-3 sm:h-4 sm:w-4"
src={DropdownIcon as any}
className="h-3 w-3 sm:h-4 sm:w-4"
/>
</div>
<div className="sm:hidden">
@ -192,12 +191,13 @@ export function TeamVersions(props: TeamVersionsProps) {
<span className="truncate">Personal</span>
</div>
</button>
{teamVersions.map((team) => {
{teamVersions.map((team: TeamVersionsResponse[0]) => {
const isSelectedTeam =
selectedTeamVersion?.team._id === team.team._id;
return (
<button
key={team?.team?._id}
className={`flex h-8 w-full items-center justify-between px-3 py-1.5 text-xs font-medium hover:bg-gray-100 sm:text-sm ${
isSelectedTeam ? 'bg-gray-100' : 'bg-white'
}`}

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import ChevronDown from '../icons/dropdown.svg';
import { httpGet } from '../lib/http';
import { useTeamId } from '../hooks/use-team-id';
@ -33,7 +33,7 @@ export function TeamsList() {
}, []);
return (
<div className="relative max-w-[500px] mx-auto">
<div className="relative mx-auto max-w-[500px]">
<div className="w-full px-2 py-2">
<div className={'mb-8 hidden md:block'}>
<h2 className={'text-3xl font-bold sm:text-4xl'}>Teams</h2>
@ -41,7 +41,7 @@ export function TeamsList() {
Here are the teams you are part of
</p>
</div>
<ul class="mb-3 flex flex-col gap-1">
<ul className="mb-3 flex flex-col gap-1">
<li>
<a
className="flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded border p-2 text-sm font-medium hover:border-gray-300 hover:bg-gray-50"
@ -65,7 +65,7 @@ export function TeamsList() {
</a>
</li>
{teamList.map((team) => (
<li>
<li key={team._id}>
<a
className="flex w-full cursor-pointer items-center justify-between gap-2 rounded border p-2 text-sm font-medium hover:border-gray-300 hover:bg-gray-50"
href={`/team/progress?t=${team._id}`}
@ -93,7 +93,7 @@ export function TeamsList() {
className="inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-none focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400"
href="/team/new"
>
<span class='mr-2'>+</span>
<span className="mr-2">+</span>
<span>New Team</span>
</a>
</div>

@ -1,6 +1,6 @@
import { useStore } from '@nanostores/preact';
import { useStore } from '@nanostores/react';
import { $toastMessage } from '../stores/toast';
import { useEffect } from 'preact/hooks';
import { useEffect } from 'react';
import { CheckIcon } from './ReactIcons/CheckIcon';
import { ErrorIcon } from './ReactIcons/ErrorIcon';
import { WarningIcon } from './ReactIcons/WarningIcon';

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import { httpPost } from '../../lib/http';
import { useToast } from '../../hooks/use-toast';
@ -157,7 +157,7 @@ export function ContributionForm(props: ContributionFormProps) {
<div>
<div className="mb-2 mt-2 rounded-md border bg-gray-100 p-3">
<h1 className="mb-2 text-2xl font-bold">Guidelines</h1>
<ul class="flex flex-col gap-1 text-sm text-gray-700">
<ul className="flex flex-col gap-1 text-sm text-gray-700">
<li>Content should only be in English.</li>
<li>Do not add things you have not evaluated personally.</li>
<li>It should strictly be relevant to the topic.</li>

@ -1,4 +1,4 @@
import { useMemo, useRef, useState } from 'preact/hooks';
import { useMemo, useRef, useState } from 'react';
import CloseIcon from '../../icons/close.svg';
import SpinnerIcon from '../../icons/spinner.svg';
@ -147,7 +147,7 @@ export function TopicDetail() {
{isLoading && (
<div className="flex w-full justify-center">
<img
src={SpinnerIcon}
src={SpinnerIcon as any}
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" class="h-5 w-5" src={CloseIcon} />
<img alt="Close" className="h-5 w-5" src={CloseIcon as any} />
</button>
</div>
@ -205,7 +205,7 @@ export function TopicDetail() {
{/* Contribution */}
<div className="mt-8 flex-1 border-t">
<p class="mb-2 mt-2 text-sm leading-relaxed text-gray-400">
<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{' '}
</p>
<button
@ -229,7 +229,7 @@ export function TopicDetail() {
</>
)}
</div>
<div class="fixed inset-0 z-30 bg-gray-900 bg-opacity-50 dark:bg-opacity-80"></div>
<div className="fixed inset-0 z-30 bg-gray-900 bg-opacity-50 dark:bg-opacity-80"></div>
</div>
);
}

@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useKeydown } from '../../hooks/use-keydown';
import { useOutsideClick } from '../../hooks/use-outside-click';
import DownIcon from '../../icons/down.svg';
@ -165,7 +165,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" class="h-4 w-4 animate-spin" src={SpinnerIcon} />
<img alt="Check" className="h-4 w-4 animate-spin" src={SpinnerIcon} />
<span className="ml-2">Updating Status..</span>
</button>
);
@ -174,9 +174,9 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
return (
<div className="relative inline-flex rounded-md border border-gray-300">
<span className="inline-flex cursor-default items-center p-1 px-2 text-sm text-black">
<span class="flex h-2 w-2">
<span className="flex h-2 w-2">
<span
class={`relative inline-flex h-2 w-2 rounded-full ${statusColors[progress]}`}
className={`relative inline-flex h-2 w-2 rounded-full ${statusColors[progress]}`}
></span>
</span>
<span className="ml-2 capitalize">
@ -189,7 +189,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
onClick={() => setShowChangeStatus(true)}
>
<span className="mr-0.5">Update Status</span>
<img alt="Check" class="h-4 w-4" src={DownIcon} />
<img alt="Check" className="h-4 w-4" src={DownIcon} />
</button>
{showChangeStatus && (
@ -199,59 +199,59 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
>
{allowMarkingDone && (
<button
class="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100"
className="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100"
onClick={() => handleUpdateResourceProgress('done')}
>
<span>
<span
class={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['done']}`}
className={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['done']}`}
></span>
Done
</span>
<span class="text-xs text-gray-500">D</span>
<span className="text-xs text-gray-500">D</span>
</button>
)}
{allowMarkingLearning && (
<button
class="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100"
className="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100"
onClick={() => handleUpdateResourceProgress('learning')}
>
<span>
<span
class={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['learning']}`}
className={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['learning']}`}
></span>
In Progress
</span>
<span class="text-xs text-gray-500">L</span>
<span className="text-xs text-gray-500">L</span>
</button>
)}
{allowMarkingPending && (
<button
class="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100"
className="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100"
onClick={() => handleUpdateResourceProgress('pending')}
>
<span>
<span
class={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['pending']}`}
className={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['pending']}`}
></span>
Reset
</span>
<span class="text-xs text-gray-500">R</span>
<span className="text-xs text-gray-500">R</span>
</button>
)}
{allowMarkingSkipped && (
<button
class="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100"
className="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100"
onClick={() => handleUpdateResourceProgress('skipped')}
>
<span>
<span
class={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['skipped']}`}
className={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['skipped']}`}
></span>
Skip
</span>
<span class="text-xs text-gray-500">S</span>
<span className="text-xs text-gray-500">S</span>
</button>
)}
</div>

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { FormEvent, useEffect, useState } from 'react';
import { httpGet, httpPost } from '../../lib/http';
import { pageProgressMessage } from '../../stores/page';
@ -13,7 +13,7 @@ export default function UpdatePasswordForm() {
const [isLoading, setIsLoading] = useState(true);
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError('');
@ -78,15 +78,17 @@ export default function UpdatePasswordForm() {
return (
<form onSubmit={handleSubmit}>
<div class="hidden md:block mb-8">
<div className="mb-8 hidden md:block">
<h2 className="text-3xl font-bold sm:text-4xl">Password</h2>
<p className="mt-2 text-gray-400">Use the form below to update your password.</p>
<p className="mt-2 text-gray-400">
Use the form below to update your password.
</p>
</div>
<div className="space-y-4">
{authProvider === 'email' && (
<div className="flex w-full flex-col">
<label
for="current-password"
htmlFor="current-password"
className="text-sm leading-none text-slate-500"
>
Current Password
@ -96,6 +98,7 @@ export default function UpdatePasswordForm() {
type="password"
name="current-password"
id="current-password"
autoComplete={"current-password"}
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-100"
required
minLength={6}
@ -110,7 +113,7 @@ export default function UpdatePasswordForm() {
<div className="flex w-full flex-col">
<label
for="new-password"
htmlFor="new-password"
className="text-sm leading-none text-slate-500"
>
New Password
@ -119,6 +122,7 @@ export default function UpdatePasswordForm() {
type="password"
name="new-password"
id="new-password"
autoComplete={"new-password"}
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
required
minLength={6}
@ -131,7 +135,7 @@ export default function UpdatePasswordForm() {
</div>
<div className="flex w-full flex-col">
<label
for="new-password-confirmation"
htmlFor="new-password-confirmation"
className="text-sm leading-none text-slate-500"
>
Confirm New Password
@ -141,6 +145,7 @@ export default function UpdatePasswordForm() {
name="new-password-confirmation"
id="new-password-confirmation"
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
autoComplete={"new-password"}
required
minLength={6}
placeholder="Confirm New Password"
@ -152,11 +157,11 @@ export default function UpdatePasswordForm() {
</div>
{error && (
<p class="mt-2 rounded-lg bg-red-100 p-2 text-red-700">{error}</p>
<p className="mt-2 rounded-lg bg-red-100 p-2 text-red-700">{error}</p>
)}
{success && (
<p class="mt-2 rounded-lg bg-green-100 p-2 text-green-700">
<p className="mt-2 rounded-lg bg-green-100 p-2 text-green-700">
{success}
</p>
)}

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { FormEvent, useEffect, useState } from 'react';
import { httpGet, httpPost } from '../../lib/http';
import { pageProgressMessage } from '../../stores/page';
import UploadProfilePicture from './UploadProfilePicture';
@ -16,7 +16,7 @@ export function UpdateProfileForm() {
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);
setError('');
@ -97,7 +97,7 @@ export function UpdateProfileForm() {
<form className="mt-4 space-y-4" 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-["*"]'
>
Name
@ -115,7 +115,7 @@ export function UpdateProfileForm() {
</div>
<div className="flex w-full flex-col">
<label
for="email"
htmlFor="email"
className='text-sm leading-none text-slate-500 after:text-red-400 after:content-["*"]'
>
Email
@ -133,7 +133,10 @@ export function UpdateProfileForm() {
</div>
<div className="flex w-full flex-col">
<label for="github" className="text-sm leading-none text-slate-500">
<label
htmlFor="github"
className="text-sm leading-none text-slate-500"
>
Github
</label>
<input
@ -147,7 +150,10 @@ export function UpdateProfileForm() {
/>
</div>
<div className="flex w-full flex-col">
<label for="twitter" className="text-sm leading-none text-slate-500">
<label
htmlFor="twitter"
className="text-sm leading-none text-slate-500"
>
Twitter
</label>
<input
@ -162,7 +168,10 @@ export function UpdateProfileForm() {
</div>
<div className="flex w-full flex-col">
<label for="linkedin" className="text-sm leading-none text-slate-500">
<label
htmlFor="linkedin"
className="text-sm leading-none text-slate-500"
>
LinkedIn
</label>
<input
@ -177,7 +186,10 @@ export function UpdateProfileForm() {
</div>
<div className="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"
>
Website
</label>
<input

@ -1,5 +1,5 @@
import Cookies from 'js-cookie';
import { useEffect, useRef, useState } from 'preact/hooks';
import { ChangeEvent, FormEvent, useEffect, useRef, useState } from 'react';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
interface PreviewFile extends File {
@ -60,7 +60,7 @@ export default function UploadProfilePicture(props: UploadProfilePictureProps) {
const inputRef = useRef<HTMLInputElement>(null);
const onImageChange = async (e: Event) => {
const onImageChange = async (e: ChangeEvent<HTMLInputElement>) => {
setError('');
const file = (e.target as HTMLInputElement).files?.[0];
@ -81,7 +81,7 @@ export default function UploadProfilePicture(props: UploadProfilePictureProps) {
);
};
const handleSubmit = async (e: Event) => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError('');
setIsLoading(true);

@ -3,6 +3,7 @@ import { useCopyText } from '../../hooks/use-copy-text';
import type { ResourceType } from '../../lib/resource-progress';
import { CheckIcon } from '../ReactIcons/CheckIcon';
import { ShareIcon } from '../ReactIcons/ShareIcon';
import { isLoggedIn } from '../../lib/jwt';
type ProgressShareButtonProps = {
resourceId: string;

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'react';
import { wireframeJSONToSVG } from 'roadmap-renderer';
import '../FrameRenderer/FrameRenderer.css';
import { useOutsideClick } from '../../hooks/use-outside-click';
@ -43,12 +43,7 @@ export function UserProgressModal(props: ProgressMapProps) {
const resourceSvgEl = useRef<HTMLDivElement>(null);
const popupBodyEl = useRef<HTMLDivElement>(null);
const currentUser = useAuth();
if (!userId || currentUser?.id === userId) {
deleteUrlParam('s');
return null;
}
const [showModal, setShowModal] = useState(!!userId);
const [resourceSvg, setResourceSvg] = useState<SVGElement | null>(null);
@ -114,7 +109,7 @@ export function UserProgressModal(props: ProgressMapProps) {
});
useEffect(() => {
if (!resourceJsonUrl || !resourceId || !resourceType) {
if (!resourceJsonUrl || !resourceId || !resourceType || !userId) {
return;
}
@ -182,14 +177,19 @@ export function UserProgressModal(props: ProgressMapProps) {
const userLearning = progress?.learning?.length || 0;
const userSkipped = progress?.skipped?.length || 0;
if (!userId || currentUser?.id === userId) {
deleteUrlParam('s');
return null;
}
if (!showModal) {
return null;
}
if (isLoading || error) {
return (
<div class="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
<div class="relative mx-auto flex h-full w-full items-center justify-center">
<div className="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
<div className="relative mx-auto flex h-full w-full items-center justify-center">
<div className="popup-body relative rounded-lg bg-white p-5 shadow">
<div className="flex items-center">
{isLoading && (
@ -217,12 +217,12 @@ export function UserProgressModal(props: ProgressMapProps) {
return (
<div
id={'user-progress-modal'}
class="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50"
className="fixed left-0 right-0 top-0 z-50 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50"
>
<div class="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto">
<div className="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto">
<div
ref={popupBodyEl}
class={`popup-body relative rounded-lg bg-white pt-[1px] shadow`}
className={`popup-body relative rounded-lg bg-white pt-[1px] shadow`}
>
<div className="p-4">
<div className="mb-5 mt-0 min-h-[28px] text-left sm:text-center md:mt-4 md:h-[60px]">
@ -238,9 +238,9 @@ export function UserProgressModal(props: ProgressMapProps) {
</p>
</div>
<p
class={`-mx-4 mb-3 flex items-center justify-start border-b border-t px-4 py-2 text-sm sm:hidden`}
className={`-mx-4 mb-3 flex items-center justify-start border-b border-t px-4 py-2 text-sm sm:hidden`}
>
<span class="mr-2.5 block rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900">
<span className="mr-2.5 block rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900">
<span>{progressPercentage}</span>% Done
</span>
@ -249,32 +249,32 @@ export function UserProgressModal(props: ProgressMapProps) {
</span>
</p>
<p
class={`-mx-4 mb-3 hidden items-center justify-center border-b border-t py-2 text-sm sm:flex ${
className={`-mx-4 mb-3 hidden items-center justify-center border-b border-t py-2 text-sm sm:flex ${
isLoading ? 'striped-loader' : ''
}`}
>
<span class="mr-2.5 block rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900">
<span className="mr-2.5 block rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900">
<span>{progressPercentage}</span>% Done
</span>
<span>
<span>{userDone}</span> completed
</span>
<span class="mx-1.5 text-gray-400">·</span>
<span className="mx-1.5 text-gray-400">·</span>
<span>
<span>{userLearning}</span> in progress
</span>
{userSkipped > 0 && (
<>
<span class="mx-1.5 text-gray-400">·</span>
<span className="mx-1.5 text-gray-400">·</span>
<span>
<span>{userSkipped}</span> skipped
</span>
</>
)}
<span class="mx-1.5 text-gray-400">·</span>
<span className="mx-1.5 text-gray-400">·</span>
<span>
<span>{userProgressTotal}</span> Total
</span>
@ -292,8 +292,8 @@ 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} className="h-4 w-4" />
<span class="sr-only">Close modal</span>
<img alt={'close'} src={CloseIcon as any} className="h-4 w-4" />
<span className="sr-only">Close modal</span>
</button>
</div>
</div>

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
export function useCopyText() {
const [isCopied, setIsCopied] = useState(false);

@ -1,4 +1,4 @@
import { useEffect } from 'preact/hooks';
import { useEffect } from 'react';
export function useKeydown(keyName: string, callback: any, deps: any[] = []) {
useEffect(() => {

@ -1,4 +1,4 @@
import { useEffect } from 'preact/hooks';
import { useEffect } from 'react';
import type { ResourceType } from '../lib/resource-progress';
type CallbackType = (data: {

@ -1,4 +1,4 @@
import { useEffect } from 'preact/hooks';
import { useEffect } from 'react';
export function useOutsideClick(ref: any, callback: any) {
useEffect(() => {

@ -1,4 +1,4 @@
import { useEffect, useState } from "preact/hooks";
import { useEffect, useState } from "react";
export function useParams<T = Record<string, any>>(): T {
const [params, setParams] = useState<T>({} as T);

@ -1,4 +1,4 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useState } from 'react';
export function useTeamId() {
const [teamId, setTeamId] = useState<string | null>(null);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save