mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-07-31 22:40:19 +02:00
Add progress nudge on roadmap
This commit is contained in:
@@ -13,6 +13,7 @@ import type { Node } from 'reactflow';
|
|||||||
import { useCallback, type MouseEvent, useMemo, useState, useRef } from 'react';
|
import { useCallback, type MouseEvent, useMemo, useState, useRef } from 'react';
|
||||||
import { EmptyRoadmap } from './EmptyRoadmap';
|
import { EmptyRoadmap } from './EmptyRoadmap';
|
||||||
import { cn } from '../../lib/classname';
|
import { cn } from '../../lib/classname';
|
||||||
|
import { totalRoadmapNodes } from '../../stores/roadmap.ts';
|
||||||
|
|
||||||
type FlowRoadmapRendererProps = {
|
type FlowRoadmapRendererProps = {
|
||||||
roadmap: RoadmapDocument;
|
roadmap: RoadmapDocument;
|
||||||
@@ -138,6 +139,12 @@ export function FlowRoadmapRenderer(props: FlowRoadmapRendererProps) {
|
|||||||
)}
|
)}
|
||||||
onRendered={() => {
|
onRendered={() => {
|
||||||
renderResourceProgress('roadmap', roadmapId).then(() => {
|
renderResourceProgress('roadmap', roadmapId).then(() => {
|
||||||
|
totalRoadmapNodes.set(
|
||||||
|
roadmap?.nodes?.filter((node) => {
|
||||||
|
return ['topic', 'subtopic'].includes(node.type);
|
||||||
|
}).length || 0,
|
||||||
|
);
|
||||||
|
|
||||||
if (roadmap?.nodes?.length === 0) {
|
if (roadmap?.nodes?.length === 0) {
|
||||||
setHideRenderer(true);
|
setHideRenderer(true);
|
||||||
editorWrapperRef?.current?.classList.add('hidden');
|
editorWrapperRef?.current?.classList.add('hidden');
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Loader from '../Loader.astro';
|
import Loader from '../Loader.astro';
|
||||||
import './FrameRenderer.css';
|
import './FrameRenderer.css';
|
||||||
|
import { ProgressNudge } from "./ProgressNudge";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
resourceType: 'roadmap' | 'best-practice';
|
resourceType: 'roadmap' | 'best-practice';
|
||||||
@@ -27,4 +28,6 @@ const { resourceId, resourceType, dimensions = null } = Astro.props;
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ProgressNudge resourceId={resourceId} resourceType={resourceType} client:only="react" />
|
||||||
|
|
||||||
<script src='./renderer.ts'></script>
|
<script src='./renderer.ts'></script>
|
||||||
|
83
src/components/FrameRenderer/ProgressNudge.tsx
Normal file
83
src/components/FrameRenderer/ProgressNudge.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { Spinner } from '../ReactIcons/Spinner.tsx';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { cn } from '../../lib/classname.ts';
|
||||||
|
import { getUser } from '../../lib/jwt.ts';
|
||||||
|
import { roadmapProgress, totalRoadmapNodes } from '../../stores/roadmap.ts';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
|
import {HelpCircle} from "lucide-react";
|
||||||
|
|
||||||
|
type ProgressNudgeProps = {
|
||||||
|
resourceType: 'roadmap' | 'best-practice';
|
||||||
|
resourceId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ProgressNudge(props: ProgressNudgeProps) {
|
||||||
|
const { resourceId, resourceType } = props;
|
||||||
|
|
||||||
|
const $totalRoadmapNodes = useStore(totalRoadmapNodes);
|
||||||
|
const $roadmapProgress = useStore(roadmapProgress);
|
||||||
|
|
||||||
|
const done = $roadmapProgress?.done?.length || 0;
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const { id: userId } = getUser() || {};
|
||||||
|
|
||||||
|
const hasProgress = done > 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
}, 500);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!$totalRoadmapNodes) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'fixed hidden sm:block -bottom-full left-1/2 z-30 -translate-x-1/2 transform overflow-hidden rounded-full bg-stone-900 px-4 py-2 text-center text-white shadow-2xl transition-all ',
|
||||||
|
{
|
||||||
|
'bottom-5 opacity-100': !isLoading,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={cn('block', {
|
||||||
|
hidden: hasProgress,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span className="mr-2 text-sm font-semibold uppercase text-yellow-400">
|
||||||
|
Tip
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-gray-200">
|
||||||
|
Right-click on a topic to mark it as done.{' '}
|
||||||
|
<button
|
||||||
|
data-popup="progress-help"
|
||||||
|
className="cursor-pointer font-semibold text-yellow-500 underline"
|
||||||
|
>
|
||||||
|
Learn more.
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn('relative z-20 block text-sm', {
|
||||||
|
hidden: !hasProgress,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span className="relative -top-[0.45px] mr-2 text-xs font-medium uppercase text-yellow-400">
|
||||||
|
Progress
|
||||||
|
</span>
|
||||||
|
<span>{done}</span> of <span>{$totalRoadmapNodes}</span> Done
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className="absolute bottom-0 left-0 top-0 z-10 bg-stone-700"
|
||||||
|
style={{
|
||||||
|
width: `${(done / $totalRoadmapNodes) * 100}%`,
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -218,6 +218,11 @@ export class Renderer {
|
|||||||
|
|
||||||
const isCurrentStatusDone = targetGroup.classList.contains('done');
|
const isCurrentStatusDone = targetGroup.classList.contains('done');
|
||||||
const normalizedGroupId = groupId.replace(/^\d+-/, '');
|
const normalizedGroupId = groupId.replace(/^\d+-/, '');
|
||||||
|
|
||||||
|
if (normalizedGroupId.startsWith('ext_link:')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.updateTopicStatus(
|
this.updateTopicStatus(
|
||||||
normalizedGroupId,
|
normalizedGroupId,
|
||||||
!isCurrentStatusDone ? 'done' : 'pending',
|
!isCurrentStatusDone ? 'done' : 'pending',
|
||||||
|
@@ -3,6 +3,7 @@ import { httpGet, httpPost } from './http';
|
|||||||
import { TOKEN_COOKIE_NAME, getUser } from './jwt';
|
import { TOKEN_COOKIE_NAME, getUser } from './jwt';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Element = astroHTML.JSX.Element;
|
import Element = astroHTML.JSX.Element;
|
||||||
|
import { roadmapProgress, totalRoadmapNodes } from '../stores/roadmap.ts';
|
||||||
|
|
||||||
export type ResourceType = 'roadmap' | 'best-practice';
|
export type ResourceType = 'roadmap' | 'best-practice';
|
||||||
export type ResourceProgressType =
|
export type ResourceProgressType =
|
||||||
@@ -27,7 +28,7 @@ export async function isTopicDone(topic: TopicMeta): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getTopicStatus(
|
export async function getTopicStatus(
|
||||||
topic: TopicMeta
|
topic: TopicMeta,
|
||||||
): Promise<ResourceProgressType> {
|
): Promise<ResourceProgressType> {
|
||||||
const { topicId, resourceType, resourceId } = topic;
|
const { topicId, resourceType, resourceId } = topic;
|
||||||
const progressResult = await getResourceProgress(resourceType, resourceId);
|
const progressResult = await getResourceProgress(resourceType, resourceId);
|
||||||
@@ -49,7 +50,7 @@ export async function getTopicStatus(
|
|||||||
|
|
||||||
export async function updateResourceProgress(
|
export async function updateResourceProgress(
|
||||||
topic: TopicMeta,
|
topic: TopicMeta,
|
||||||
progressType: ResourceProgressType
|
progressType: ResourceProgressType,
|
||||||
) {
|
) {
|
||||||
const { topicId, resourceType, resourceId } = topic;
|
const { topicId, resourceType, resourceId } = topic;
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ export async function updateResourceProgress(
|
|||||||
resourceId,
|
resourceId,
|
||||||
response.done,
|
response.done,
|
||||||
response.learning,
|
response.learning,
|
||||||
response.skipped
|
response.skipped,
|
||||||
);
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@@ -82,7 +83,7 @@ export async function updateResourceProgress(
|
|||||||
|
|
||||||
export async function getResourceProgress(
|
export async function getResourceProgress(
|
||||||
resourceType: 'roadmap' | 'best-practice',
|
resourceType: 'roadmap' | 'best-practice',
|
||||||
resourceId: string
|
resourceId: string,
|
||||||
): Promise<{ done: string[]; learning: string[]; skipped: string[] }> {
|
): Promise<{ done: string[]; learning: string[]; skipped: string[] }> {
|
||||||
// No need to load progress if user is not logged in
|
// No need to load progress if user is not logged in
|
||||||
if (!Cookies.get(TOKEN_COOKIE_NAME)) {
|
if (!Cookies.get(TOKEN_COOKIE_NAME)) {
|
||||||
@@ -109,6 +110,14 @@ export async function getResourceProgress(
|
|||||||
|
|
||||||
if (!progress || isProgressExpired) {
|
if (!progress || isProgressExpired) {
|
||||||
return loadFreshProgress(resourceType, resourceId);
|
return loadFreshProgress(resourceType, resourceId);
|
||||||
|
} else {
|
||||||
|
setResourceProgress(
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
progress?.done || [],
|
||||||
|
progress?.learning || [],
|
||||||
|
progress?.skipped || [],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch event to update favorite status in the MarkFavorite component
|
// Dispatch event to update favorite status in the MarkFavorite component
|
||||||
@@ -119,7 +128,7 @@ export async function getResourceProgress(
|
|||||||
resourceId,
|
resourceId,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return progress;
|
return progress;
|
||||||
@@ -127,7 +136,7 @@ export async function getResourceProgress(
|
|||||||
|
|
||||||
async function loadFreshProgress(
|
async function loadFreshProgress(
|
||||||
resourceType: ResourceType,
|
resourceType: ResourceType,
|
||||||
resourceId: string
|
resourceId: string,
|
||||||
) {
|
) {
|
||||||
const { response, error } = await httpGet<{
|
const { response, error } = await httpGet<{
|
||||||
done: string[];
|
done: string[];
|
||||||
@@ -153,7 +162,7 @@ async function loadFreshProgress(
|
|||||||
resourceId,
|
resourceId,
|
||||||
response?.done || [],
|
response?.done || [],
|
||||||
response?.learning || [],
|
response?.learning || [],
|
||||||
response?.skipped || []
|
response?.skipped || [],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Dispatch event to update favorite status in the MarkFavorite component
|
// Dispatch event to update favorite status in the MarkFavorite component
|
||||||
@@ -164,7 +173,7 @@ async function loadFreshProgress(
|
|||||||
resourceId,
|
resourceId,
|
||||||
isFavorite: response.isFavorite,
|
isFavorite: response.isFavorite,
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@@ -175,8 +184,14 @@ export function setResourceProgress(
|
|||||||
resourceId: string,
|
resourceId: string,
|
||||||
done: string[],
|
done: string[],
|
||||||
learning: string[],
|
learning: string[],
|
||||||
skipped: string[]
|
skipped: string[],
|
||||||
): void {
|
): void {
|
||||||
|
roadmapProgress.set({
|
||||||
|
done,
|
||||||
|
learning,
|
||||||
|
skipped,
|
||||||
|
});
|
||||||
|
|
||||||
const userId = getUser()?.id;
|
const userId = getUser()?.id;
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
`${resourceType}-${resourceId}-${userId}-progress`,
|
`${resourceType}-${resourceId}-${userId}-progress`,
|
||||||
@@ -185,13 +200,13 @@ export function setResourceProgress(
|
|||||||
learning,
|
learning,
|
||||||
skipped,
|
skipped,
|
||||||
timestamp: new Date().getTime(),
|
timestamp: new Date().getTime(),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function topicSelectorAll(
|
export function topicSelectorAll(
|
||||||
topicId: string,
|
topicId: string,
|
||||||
parentElement: Document | SVGElement | HTMLDivElement = document
|
parentElement: Document | SVGElement | HTMLDivElement = document,
|
||||||
): Element[] {
|
): Element[] {
|
||||||
const matchingElements: Element[] = [];
|
const matchingElements: Element[] = [];
|
||||||
|
|
||||||
@@ -215,7 +230,7 @@ export function topicSelectorAll(
|
|||||||
`[data-node-id="${topicId}"]`, // Matching custom roadmap nodes
|
`[data-node-id="${topicId}"]`, // Matching custom roadmap nodes
|
||||||
`[data-id="${topicId}"]`, // Matching custom roadmap nodes
|
`[data-id="${topicId}"]`, // Matching custom roadmap nodes
|
||||||
],
|
],
|
||||||
parentElement
|
parentElement,
|
||||||
).forEach((element) => {
|
).forEach((element) => {
|
||||||
matchingElements.push(element);
|
matchingElements.push(element);
|
||||||
});
|
});
|
||||||
@@ -225,7 +240,7 @@ export function topicSelectorAll(
|
|||||||
|
|
||||||
export function renderTopicProgress(
|
export function renderTopicProgress(
|
||||||
topicId: string,
|
topicId: string,
|
||||||
topicProgress: ResourceProgressType
|
topicProgress: ResourceProgressType,
|
||||||
) {
|
) {
|
||||||
const isLearning = topicProgress === 'learning';
|
const isLearning = topicProgress === 'learning';
|
||||||
const isSkipped = topicProgress === 'skipped';
|
const isSkipped = topicProgress === 'skipped';
|
||||||
@@ -268,7 +283,7 @@ export function clearResourceProgress() {
|
|||||||
|
|
||||||
export async function renderResourceProgress(
|
export async function renderResourceProgress(
|
||||||
resourceType: ResourceType,
|
resourceType: ResourceType,
|
||||||
resourceId: string
|
resourceId: string,
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
done = [],
|
done = [],
|
||||||
@@ -293,7 +308,7 @@ export async function renderResourceProgress(
|
|||||||
|
|
||||||
function getMatchingElements(
|
function getMatchingElements(
|
||||||
quries: string[],
|
quries: string[],
|
||||||
parentElement: Document | SVGElement | HTMLDivElement = document
|
parentElement: Document | SVGElement | HTMLDivElement = document,
|
||||||
): Element[] {
|
): Element[] {
|
||||||
const matchingElements: Element[] = [];
|
const matchingElements: Element[] = [];
|
||||||
quries.forEach((query) => {
|
quries.forEach((query) => {
|
||||||
@@ -306,7 +321,7 @@ function getMatchingElements(
|
|||||||
|
|
||||||
export function refreshProgressCounters() {
|
export function refreshProgressCounters() {
|
||||||
const progressNumsContainers = document.querySelectorAll(
|
const progressNumsContainers = document.querySelectorAll(
|
||||||
'[data-progress-nums-container]'
|
'[data-progress-nums-container]',
|
||||||
);
|
);
|
||||||
const progressNums = document.querySelectorAll('[data-progress-nums]');
|
const progressNums = document.querySelectorAll('[data-progress-nums]');
|
||||||
if (progressNumsContainers.length === 0 || progressNums.length === 0) {
|
if (progressNumsContainers.length === 0 || progressNums.length === 0) {
|
||||||
@@ -322,27 +337,27 @@ export function refreshProgressCounters() {
|
|||||||
]).length;
|
]).length;
|
||||||
|
|
||||||
const externalLinks = document.querySelectorAll(
|
const externalLinks = document.querySelectorAll(
|
||||||
'[data-group-id^="ext_link:"]'
|
'[data-group-id^="ext_link:"]',
|
||||||
).length;
|
).length;
|
||||||
const roadmapSwitchers = document.querySelectorAll(
|
const roadmapSwitchers = document.querySelectorAll(
|
||||||
'[data-group-id^="json:"]'
|
'[data-group-id^="json:"]',
|
||||||
).length;
|
).length;
|
||||||
const checkBoxes = document.querySelectorAll(
|
const checkBoxes = document.querySelectorAll(
|
||||||
'[data-group-id^="check:"]'
|
'[data-group-id^="check:"]',
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const totalCheckBoxesDone = document.querySelectorAll(
|
const totalCheckBoxesDone = document.querySelectorAll(
|
||||||
'[data-group-id^="check:"].done'
|
'[data-group-id^="check:"].done',
|
||||||
).length;
|
).length;
|
||||||
const totalCheckBoxesLearning = document.querySelectorAll(
|
const totalCheckBoxesLearning = document.querySelectorAll(
|
||||||
'[data-group-id^="check:"].learning'
|
'[data-group-id^="check:"].learning',
|
||||||
).length;
|
).length;
|
||||||
const totalCheckBoxesSkipped = document.querySelectorAll(
|
const totalCheckBoxesSkipped = document.querySelectorAll(
|
||||||
'[data-group-id^="check:"].skipped'
|
'[data-group-id^="check:"].skipped',
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const totalRemoved = document.querySelectorAll(
|
const totalRemoved = document.querySelectorAll(
|
||||||
'.clickable-group.removed'
|
'.clickable-group.removed',
|
||||||
).length;
|
).length;
|
||||||
const totalItems =
|
const totalItems =
|
||||||
totalClickable -
|
totalClickable -
|
||||||
@@ -351,6 +366,8 @@ export function refreshProgressCounters() {
|
|||||||
checkBoxes -
|
checkBoxes -
|
||||||
totalRemoved;
|
totalRemoved;
|
||||||
|
|
||||||
|
totalRoadmapNodes.set(totalItems);
|
||||||
|
|
||||||
const totalDone =
|
const totalDone =
|
||||||
getMatchingElements([
|
getMatchingElements([
|
||||||
'.clickable-group.done:not([data-group-id^="ext_link:"])',
|
'.clickable-group.done:not([data-group-id^="ext_link:"])',
|
||||||
@@ -373,47 +390,47 @@ export function refreshProgressCounters() {
|
|||||||
const doneCountEls = document.querySelectorAll('[data-progress-done]');
|
const doneCountEls = document.querySelectorAll('[data-progress-done]');
|
||||||
if (doneCountEls.length > 0) {
|
if (doneCountEls.length > 0) {
|
||||||
doneCountEls.forEach(
|
doneCountEls.forEach(
|
||||||
(doneCountEl) => (doneCountEl.innerHTML = `${totalDone}`)
|
(doneCountEl) => (doneCountEl.innerHTML = `${totalDone}`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const learningCountEls = document.querySelectorAll(
|
const learningCountEls = document.querySelectorAll(
|
||||||
'[data-progress-learning]'
|
'[data-progress-learning]',
|
||||||
);
|
);
|
||||||
if (learningCountEls.length > 0) {
|
if (learningCountEls.length > 0) {
|
||||||
learningCountEls.forEach(
|
learningCountEls.forEach(
|
||||||
(learningCountEl) => (learningCountEl.innerHTML = `${totalLearning}`)
|
(learningCountEl) => (learningCountEl.innerHTML = `${totalLearning}`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const skippedCountEls = document.querySelectorAll('[data-progress-skipped]');
|
const skippedCountEls = document.querySelectorAll('[data-progress-skipped]');
|
||||||
if (skippedCountEls.length > 0) {
|
if (skippedCountEls.length > 0) {
|
||||||
skippedCountEls.forEach(
|
skippedCountEls.forEach(
|
||||||
(skippedCountEl) => (skippedCountEl.innerHTML = `${totalSkipped}`)
|
(skippedCountEl) => (skippedCountEl.innerHTML = `${totalSkipped}`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalCountEls = document.querySelectorAll('[data-progress-total]');
|
const totalCountEls = document.querySelectorAll('[data-progress-total]');
|
||||||
if (totalCountEls.length > 0) {
|
if (totalCountEls.length > 0) {
|
||||||
totalCountEls.forEach(
|
totalCountEls.forEach(
|
||||||
(totalCountEl) => (totalCountEl.innerHTML = `${totalItems}`)
|
(totalCountEl) => (totalCountEl.innerHTML = `${totalItems}`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const progressPercentage =
|
const progressPercentage =
|
||||||
Math.round(((totalDone + totalSkipped) / totalItems) * 100) || 0;
|
Math.round(((totalDone + totalSkipped) / totalItems) * 100) || 0;
|
||||||
const progressPercentageEls = document.querySelectorAll(
|
const progressPercentageEls = document.querySelectorAll(
|
||||||
'[data-progress-percentage]'
|
'[data-progress-percentage]',
|
||||||
);
|
);
|
||||||
if (progressPercentageEls.length > 0) {
|
if (progressPercentageEls.length > 0) {
|
||||||
progressPercentageEls.forEach(
|
progressPercentageEls.forEach(
|
||||||
(progressPercentageEl) =>
|
(progressPercentageEl) =>
|
||||||
(progressPercentageEl.innerHTML = `${progressPercentage}`)
|
(progressPercentageEl.innerHTML = `${progressPercentage}`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
progressNumsContainers.forEach((progressNumsContainer) =>
|
progressNumsContainers.forEach((progressNumsContainer) =>
|
||||||
progressNumsContainer.classList.remove('striped-loader')
|
progressNumsContainer.classList.remove('striped-loader'),
|
||||||
);
|
);
|
||||||
progressNums.forEach((progressNum) => {
|
progressNums.forEach((progressNum) => {
|
||||||
progressNum.classList.remove('opacity-0');
|
progressNum.classList.remove('opacity-0');
|
||||||
|
@@ -4,9 +4,14 @@ import type { GetRoadmapResponse } from '../components/CustomRoadmap/CustomRoadm
|
|||||||
export const currentRoadmap = atom<GetRoadmapResponse | undefined>(undefined);
|
export const currentRoadmap = atom<GetRoadmapResponse | undefined>(undefined);
|
||||||
export const isCurrentRoadmapPersonal = computed(
|
export const isCurrentRoadmapPersonal = computed(
|
||||||
currentRoadmap,
|
currentRoadmap,
|
||||||
(roadmap) => !roadmap?.teamId
|
(roadmap) => !roadmap?.teamId,
|
||||||
);
|
);
|
||||||
export const canManageCurrentRoadmap = computed(
|
export const canManageCurrentRoadmap = computed(
|
||||||
currentRoadmap,
|
currentRoadmap,
|
||||||
(roadmap) => roadmap?.canManage
|
(roadmap) => roadmap?.canManage,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const roadmapProgress = atom<
|
||||||
|
{ done: string[]; learning: string[]; skipped: string[] } | undefined
|
||||||
|
>();
|
||||||
|
export const totalRoadmapNodes = atom<number | undefined>();
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: [
|
||||||
'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue,svg}',
|
'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue,svg}',
|
||||||
'./editor/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue,svg}'
|
'./editor/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue,svg}',
|
||||||
],
|
],
|
||||||
future: {
|
future: {
|
||||||
hoverOnlyWhenSupported: true,
|
hoverOnlyWhenSupported: true,
|
||||||
|
Reference in New Issue
Block a user