mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-12 17:51:53 +08:00
Compare commits
1 Commits
1fbc167494
...
feat/proje
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
330aa6263b |
74
src/components/Projects/CompleteProjectConfirmation.tsx
Normal file
74
src/components/Projects/CompleteProjectConfirmation.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { queryClient } from '../../stores/query-client';
|
||||
import { Modal } from '../Modal';
|
||||
import { httpPost } from '../../lib/query-http';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { Loader2Icon } from 'lucide-react';
|
||||
import { projectStatusOptions } from '../../queries/project';
|
||||
|
||||
type CompleteProjectConfirmationProps = {
|
||||
projectId: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function CompleteProjectConfirmation(
|
||||
props: CompleteProjectConfirmationProps,
|
||||
) {
|
||||
const { onClose, projectId } = props;
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const { mutate: completeProject, isPending: isCompletingProject } =
|
||||
useMutation(
|
||||
{
|
||||
mutationFn: () => {
|
||||
return httpPost<{
|
||||
startedAt: Date;
|
||||
}>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-mark-as-done-project/${projectId}`,
|
||||
{},
|
||||
);
|
||||
},
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries(projectStatusOptions(projectId));
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error?.message || 'Failed to start project');
|
||||
},
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
},
|
||||
},
|
||||
queryClient,
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} bodyClassName="h-auto p-4">
|
||||
<h2 className="mb-2 flex items-center gap-2.5 text-xl font-semibold">
|
||||
Complete Project
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
Are you sure you want to mark this project as completed?
|
||||
</p>
|
||||
|
||||
<div className="mt-4 grid grid-cols-2 gap-2">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-200"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => completeProject()}
|
||||
className="flex h-9 items-center justify-center gap-2 rounded-lg bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700"
|
||||
>
|
||||
{isCompletingProject ? (
|
||||
<Loader2Icon className="h-4 w-4 animate-spin stroke-[2.5]" />
|
||||
) : (
|
||||
'Complete Project'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,5 @@
|
||||
import { cn } from '../../lib/classname';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Blocks,
|
||||
BoxSelect,
|
||||
type LucideIcon,
|
||||
StepBackIcon,
|
||||
StickyNote,
|
||||
Text,
|
||||
} from 'lucide-react';
|
||||
import { ArrowLeft, Blocks, type LucideIcon, Text } from 'lucide-react';
|
||||
|
||||
export const allowedProjectTabs = ['details', 'solutions'] as const;
|
||||
export type AllowedProjectTab = (typeof allowedProjectTabs)[number];
|
||||
@@ -36,7 +28,7 @@ function TabButton(props: TabButtonProps) {
|
||||
{smText && <span className="sm:hidden">{smText}</span>}
|
||||
|
||||
{isActive && (
|
||||
<span className="absolute bottom-0 left-0 right-0 h-0.5 translate-y-1/2 rounded-t-md bg-black"></span>
|
||||
<span className="absolute right-0 bottom-0 left-0 h-0.5 translate-y-1/2 rounded-t-md bg-black"></span>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
@@ -46,10 +38,16 @@ type ProjectTabsProps = {
|
||||
activeTab: AllowedProjectTab;
|
||||
projectId: string;
|
||||
parentRoadmapId?: string;
|
||||
hasNoSubmission?: boolean;
|
||||
};
|
||||
|
||||
export function ProjectTabs(props: ProjectTabsProps) {
|
||||
const { activeTab, parentRoadmapId, projectId } = props;
|
||||
const {
|
||||
activeTab,
|
||||
parentRoadmapId,
|
||||
projectId,
|
||||
hasNoSubmission = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="my-3 flex flex-row flex-wrap items-center gap-1.5 overflow-hidden rounded-md border bg-white px-2.5 text-sm">
|
||||
@@ -69,13 +67,15 @@ export function ProjectTabs(props: ProjectTabsProps) {
|
||||
isActive={activeTab === 'details'}
|
||||
href={`/projects/${projectId}`}
|
||||
/>
|
||||
<TabButton
|
||||
text={'Community Solutions'}
|
||||
icon={Blocks}
|
||||
smText={'Solutions'}
|
||||
isActive={activeTab === 'solutions'}
|
||||
href={`/projects/${projectId}/solutions`}
|
||||
/>
|
||||
{!hasNoSubmission && (
|
||||
<TabButton
|
||||
text={'Community Solutions'}
|
||||
icon={Blocks}
|
||||
smText={'Solutions'}
|
||||
isActive={activeTab === 'solutions'}
|
||||
href={`/projects/${projectId}/solutions`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
71
src/components/Projects/StartProjectConfirmation.tsx
Normal file
71
src/components/Projects/StartProjectConfirmation.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { queryClient } from '../../stores/query-client';
|
||||
import { Modal } from '../Modal';
|
||||
import { httpPost } from '../../lib/query-http';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { Loader2Icon } from 'lucide-react';
|
||||
import { projectStatusOptions } from '../../queries/project';
|
||||
|
||||
type StartProjectConfirmationProps = {
|
||||
projectId: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function StartProjectConfirmation(props: StartProjectConfirmationProps) {
|
||||
const { onClose, projectId } = props;
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const { mutate: startProject, isPending: isStartingProject } = useMutation(
|
||||
{
|
||||
mutationFn: () => {
|
||||
return httpPost<{
|
||||
startedAt: Date;
|
||||
}>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-start-project/${projectId}`,
|
||||
{},
|
||||
);
|
||||
},
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries(projectStatusOptions(projectId));
|
||||
},
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error?.message || 'Failed to start project');
|
||||
},
|
||||
},
|
||||
queryClient,
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} bodyClassName="h-auto p-4">
|
||||
<h2 className="mb-2 flex items-center gap-2.5 text-xl font-semibold">
|
||||
Start Project
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
Are you sure you want to start this project?
|
||||
</p>
|
||||
|
||||
<div className="mt-4 grid grid-cols-2 gap-2">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-200"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => startProject()}
|
||||
className="flex h-9 items-center justify-center gap-2 rounded-lg bg-purple-600 px-4 py-2 text-sm font-medium text-white hover:bg-purple-700"
|
||||
>
|
||||
{isStartingProject ? (
|
||||
<Loader2Icon className="h-4 w-4 animate-spin stroke-[2.5]" />
|
||||
) : (
|
||||
'Start Project'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -7,20 +7,6 @@ import { httpPost } from '../../lib/http.ts';
|
||||
import { CheckIcon } from '../ReactIcons/CheckIcon.tsx';
|
||||
import { useCopyText } from '../../hooks/use-copy-text.ts';
|
||||
|
||||
type StepLabelProps = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
function StepLabel(props: StepLabelProps) {
|
||||
const { label } = props;
|
||||
|
||||
return (
|
||||
<span className="shrink-0 rounded-full bg-gray-200 px-2 py-1 text-xs text-gray-600">
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
type StartProjectModalProps = {
|
||||
projectId: string;
|
||||
onClose: () => void;
|
||||
@@ -66,7 +52,7 @@ export function StartProjectModal(props: StartProjectModalProps) {
|
||||
if (error) {
|
||||
return (
|
||||
<Modal onClose={onClose} bodyClassName="h-auto text-red-500">
|
||||
<div className="flex flex-col items-center justify-center gap-2 pb-10 pt-12">
|
||||
<div className="flex flex-col items-center justify-center gap-2 pt-12 pb-10">
|
||||
<ServerCrash className={'h-6 w-6'} />
|
||||
<p className="font-medium">{error}</p>
|
||||
</div>
|
||||
@@ -77,7 +63,7 @@ export function StartProjectModal(props: StartProjectModalProps) {
|
||||
if (isStartingProject) {
|
||||
return (
|
||||
<Modal onClose={onClose} bodyClassName="h-auto">
|
||||
<div className="flex flex-col items-center justify-center gap-4 pb-10 pt-12">
|
||||
<div className="flex flex-col items-center justify-center gap-4 pt-12 pb-10">
|
||||
<Spinner className={'h-6 w-6'} isDualRing={false} />
|
||||
<p className="font-medium">Starting project ..</p>
|
||||
</div>
|
||||
@@ -96,7 +82,7 @@ export function StartProjectModal(props: StartProjectModalProps) {
|
||||
<span className="mr-1.5 font-normal">Project started</span>{' '}
|
||||
<span className="font-semibold">{formattedStartedAt}</span>
|
||||
</p>
|
||||
<h2 className="mb-1 mt-5 text-2xl font-semibold text-gray-800">
|
||||
<h2 className="mt-5 mb-1 text-2xl font-semibold text-gray-800">
|
||||
Start Building
|
||||
</h2>
|
||||
<p className="text-gray-700">
|
||||
@@ -109,8 +95,8 @@ export function StartProjectModal(props: StartProjectModalProps) {
|
||||
</p>
|
||||
|
||||
<p className="mt-1 rounded-lg bg-gray-200 p-2 text-sm text-gray-900">
|
||||
2. Complete the project according to the requirements and push your code
|
||||
to the GitHub repository.
|
||||
2. Complete the project according to the requirements and push your
|
||||
code to the GitHub repository.
|
||||
</p>
|
||||
|
||||
<p className="mt-1 rounded-lg bg-gray-200 p-2 text-sm text-gray-900">
|
||||
@@ -139,13 +125,13 @@ export function StartProjectModal(props: StartProjectModalProps) {
|
||||
</button>
|
||||
</p>
|
||||
<p className="mt-1 rounded-lg bg-gray-200 p-2 text-sm text-gray-900">
|
||||
4. Once done, submit your solution to help the others learn and get feedback
|
||||
from the community.
|
||||
4. Once done, submit your solution to help the others learn and get
|
||||
feedback from the community.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<p className='text-sm'>
|
||||
<p className="text-sm">
|
||||
If you get stuck, you can always ask for help in the community{' '}
|
||||
<a
|
||||
href="https://roadmap.sh/discord"
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Flag, Play, Send, Share, Square, StopCircle, X } from 'lucide-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { cn } from '../../../lib/classname.ts';
|
||||
import { useStickyStuck } from '../../../hooks/use-sticky-stuck.tsx';
|
||||
import { StepperAction } from './StepperAction.tsx';
|
||||
import { StepperStepSeparator } from './StepperStepSeparator.tsx';
|
||||
import { MilestoneStep } from './MilestoneStep.tsx';
|
||||
import { httpGet, httpPost } from '../../../lib/http.ts';
|
||||
import { StartProjectModal } from '../StartProjectModal.tsx';
|
||||
import { getRelativeTimeString } from '../../../lib/date.ts';
|
||||
import { getUser, isLoggedIn } from '../../../lib/jwt.ts';
|
||||
import { showLoginPopup } from '../../../lib/popup.ts';
|
||||
import { SubmitProjectModal } from '../SubmitProjectModal.tsx';
|
||||
import { useCopyText } from '../../../hooks/use-copy-text.ts';
|
||||
import { CheckIcon } from '../../ReactIcons/CheckIcon.tsx';
|
||||
import { pageProgressMessage } from '../../../stores/page.ts';
|
||||
import { cn } from '../../../lib/classname';
|
||||
import { useStickyStuck } from '../../../hooks/use-sticky-stuck';
|
||||
import { StepperAction } from './StepperAction';
|
||||
import { StepperStepSeparator } from './StepperStepSeparator';
|
||||
import { MilestoneStep } from './MilestoneStep';
|
||||
import { httpGet, httpPost } from '../../../lib/http';
|
||||
import { StartProjectModal } from '../StartProjectModal';
|
||||
import { getRelativeTimeString } from '../../../lib/date';
|
||||
import { getUser, isLoggedIn } from '../../../lib/jwt';
|
||||
import { showLoginPopup } from '../../../lib/popup';
|
||||
import { SubmitProjectModal } from '../SubmitProjectModal';
|
||||
import { useCopyText } from '../../../hooks/use-copy-text';
|
||||
import { CheckIcon } from '../../ReactIcons/CheckIcon';
|
||||
import { pageProgressMessage } from '../../../stores/page';
|
||||
|
||||
type ProjectStatusResponse = {
|
||||
id?: string;
|
||||
|
||||
123
src/components/Projects/StatusStepper/ProjectTrackingActions.tsx
Normal file
123
src/components/Projects/StatusStepper/ProjectTrackingActions.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import { CheckIcon, PlayIcon } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { StartProjectConfirmation } from '../StartProjectConfirmation';
|
||||
import { projectStatusOptions } from '../../../queries/project';
|
||||
import { queryClient } from '../../../stores/query-client';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { cn } from '../../../lib/classname';
|
||||
import { isLoggedIn } from '../../../lib/jwt';
|
||||
import { showLoginPopup } from '../../../lib/popup';
|
||||
import { getRelativeTimeString } from '../../../lib/date';
|
||||
import { CompleteProjectConfirmation } from '../CompleteProjectConfirmation';
|
||||
|
||||
type ProjectTrackingActionsProps = {
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export function ProjectTrackingActions(props: ProjectTrackingActionsProps) {
|
||||
const { projectId } = props;
|
||||
|
||||
const { data: projectStatus } = useQuery(
|
||||
projectStatusOptions(projectId),
|
||||
queryClient,
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isStartingProject, setIsStartingProject] = useState(false);
|
||||
const [isCompletingProject, setIsCompletingProject] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}, [projectStatus]);
|
||||
|
||||
const { startedAt, submittedAt } = projectStatus || {};
|
||||
const formattedStartedAt = startedAt ? getRelativeTimeString(startedAt) : '';
|
||||
const formattedSubmittedAt = submittedAt
|
||||
? getRelativeTimeString(submittedAt)
|
||||
: '';
|
||||
const isCompleted = !!submittedAt;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isStartingProject && (
|
||||
<StartProjectConfirmation
|
||||
onClose={() => setIsStartingProject(false)}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isCompletingProject && (
|
||||
<CompleteProjectConfirmation
|
||||
onClose={() => setIsCompletingProject(false)}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!startedAt && (
|
||||
<button
|
||||
onClick={() => {
|
||||
if (!isLoggedIn()) {
|
||||
showLoginPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
setIsStartingProject(true);
|
||||
}}
|
||||
className={cn(
|
||||
'relative flex items-center gap-1.5 overflow-hidden rounded-full bg-purple-600 py-1 pr-2.5 pl-2 text-sm text-white hover:bg-purple-700',
|
||||
isLoading && 'bg-white text-gray-500',
|
||||
)}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<PlayIcon size={13} />
|
||||
<span>Start Working</span>
|
||||
|
||||
{isLoading && (
|
||||
<div
|
||||
className={cn('striped-loader absolute inset-0 z-10 bg-white')}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{startedAt && !isLoading && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<button
|
||||
onClick={() => setIsCompletingProject(true)}
|
||||
className={cn(
|
||||
'relative flex items-center gap-1.5 overflow-hidden rounded-full bg-green-600 py-1 pr-2.5 pl-2 text-sm text-white hover:bg-green-700',
|
||||
isCompleted &&
|
||||
'cursor-default bg-gray-200 text-gray-500 hover:bg-gray-200',
|
||||
)}
|
||||
disabled={isCompleted}
|
||||
>
|
||||
<CheckIcon size={13} className="stroke-[2.5]" />
|
||||
{isCompleted ? (
|
||||
<span>Completed</span>
|
||||
) : (
|
||||
<span>Mark as Completed</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="text-end text-xs text-gray-500">
|
||||
{isCompleted ? (
|
||||
<>
|
||||
Completed{' '}
|
||||
<span className="font-medium">{formattedSubmittedAt}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Started working{' '}
|
||||
<span className="font-medium">{formattedStartedAt}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { MarkdownFileType } from './file';
|
||||
import { getRoadmapById, type RoadmapFileType } from './roadmap.ts';
|
||||
import { getRoadmapById, type RoadmapFileType } from './roadmap';
|
||||
|
||||
export const projectDifficulties = [
|
||||
'beginner',
|
||||
@@ -22,6 +22,7 @@ export interface ProjectFrontmatter {
|
||||
keywords: string[];
|
||||
ogImageUrl: string;
|
||||
};
|
||||
hasNoSubmission: boolean;
|
||||
roadmapIds: string[];
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../../../lib/project';
|
||||
import AstroIcon from '../../../components/AstroIcon.astro';
|
||||
import { ProjectStepper } from '../../../components/Projects/StatusStepper/ProjectStepper';
|
||||
import { ProjectTrackingActions } from '../../../components/Projects/StatusStepper/ProjectTrackingActions';
|
||||
import { ProjectTabs } from '../../../components/Projects/ProjectTabs';
|
||||
|
||||
export const prerender = true;
|
||||
@@ -51,7 +52,12 @@ const parentRoadmapId = projectData?.roadmapIds?.[0] || '';
|
||||
>
|
||||
<div class='bg-gray-50'>
|
||||
<div class='container'>
|
||||
<ProjectTabs parentRoadmapId={parentRoadmapId} projectId={projectId} activeTab='details' />
|
||||
<ProjectTabs
|
||||
parentRoadmapId={parentRoadmapId}
|
||||
projectId={projectId}
|
||||
activeTab='details'
|
||||
hasNoSubmission={projectData?.hasNoSubmission}
|
||||
/>
|
||||
|
||||
<div
|
||||
class='mb-4 rounded-lg border bg-linear-to-b from-gray-100 to-white to-10% p-4 py-2 sm:p-5'
|
||||
@@ -67,20 +73,31 @@ const parentRoadmapId = projectData?.roadmapIds?.[0] || '';
|
||||
</div>
|
||||
<Badge variant='yellow' text={projectData.difficulty} />
|
||||
</div>
|
||||
<div class='my-2 sm:my-7'>
|
||||
<h1 class='mb-1 text-xl font-semibold sm:mb-2 sm:text-3xl'>
|
||||
{projectData.title}
|
||||
</h1>
|
||||
<p class='text-balance text-sm text-gray-500'>
|
||||
{projectData.description}
|
||||
</p>
|
||||
<div class='my-2 flex items-center justify-between gap-2 sm:my-7'>
|
||||
<div class=''>
|
||||
<h1 class='mb-1 text-xl font-semibold sm:mb-2 sm:text-3xl'>
|
||||
{projectData.title}
|
||||
</h1>
|
||||
<p class='text-sm text-balance text-gray-500'>
|
||||
{projectData.description}
|
||||
</p>
|
||||
</div>
|
||||
{
|
||||
projectData?.hasNoSubmission && (
|
||||
<ProjectTrackingActions projectId={projectId} client:load />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProjectStepper projectId={projectId} client:load />
|
||||
{
|
||||
!projectData?.hasNoSubmission && (
|
||||
<ProjectStepper projectId={projectId} client:load />
|
||||
)
|
||||
}
|
||||
|
||||
<div
|
||||
class='prose max-w-full prose-h2:mb-3 prose-h2:mt-5 prose-h3:mb-1 prose-h3:mt-5 prose-p:mb-2 prose-blockquote:font-normal prose-blockquote:text-gray-500 prose-pre:my-3 prose-ul:my-3.5 prose-hr:my-5 [&>ul>li]:my-1'
|
||||
class='prose prose-h2:mb-3 prose-h2:mt-5 prose-h3:mb-1 prose-h3:mt-5 prose-p:mb-2 prose-blockquote:font-normal prose-blockquote:text-gray-500 prose-pre:my-3 prose-ul:my-3.5 prose-hr:my-5 max-w-full [&>ul>li]:my-1'
|
||||
>
|
||||
<project.Content />
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,7 @@ export async function getStaticPaths() {
|
||||
const projects = await getAllProjects();
|
||||
|
||||
return projects
|
||||
.filter((project) => !(project?.frontmatter?.hasNoSubmission || false))
|
||||
.map((project) => project.id)
|
||||
.map((projectId) => ({
|
||||
params: { projectId },
|
||||
|
||||
26
src/queries/project.ts
Normal file
26
src/queries/project.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { httpGet } from '../lib/query-http';
|
||||
import { isLoggedIn } from '../lib/jwt';
|
||||
|
||||
type ProjectStatusResponse = {
|
||||
id?: string;
|
||||
|
||||
startedAt?: Date;
|
||||
submittedAt?: Date;
|
||||
repositoryUrl?: string;
|
||||
|
||||
upvotes: number;
|
||||
downvotes: number;
|
||||
};
|
||||
|
||||
export function projectStatusOptions(projectId: string) {
|
||||
return queryOptions({
|
||||
queryKey: ['project-status', projectId],
|
||||
queryFn: () => {
|
||||
return httpGet<ProjectStatusResponse>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-project-status/${projectId}`,
|
||||
{},
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user