Requirement verification functionality

pull/6513/head
Kamran Ahmed 3 months ago
parent d7e81bd13a
commit d0e76c85ce
  1. 4
      src/components/Projects/SubmissionRequirement.tsx
  2. 128
      src/components/Projects/SubmitProjectModal.tsx

@ -1,6 +1,6 @@
import type { ReactNode } from 'react';
import { cn } from '../../lib/classname.ts';
import { CheckIcon, CircleDashed } from 'lucide-react';
import {CheckIcon, CircleDashed, X} from 'lucide-react';
type SubmissionRequirementProps = {
status: 'pending' | 'success' | 'error';
@ -23,7 +23,7 @@ export function SubmissionRequirement(props: SubmissionRequirementProps) {
) : status === 'success' ? (
<CheckIcon className="h-4 w-4 text-green-800" />
) : (
<CheckIcon className="h-4 w-4 text-yellow-800" />
<X className="h-4 w-4 text-yellow-800" />
)}
<span className="ml-2">{children}</span>
</div>

@ -1,4 +1,4 @@
import { CheckIcon, CircleDashed, X } from 'lucide-react';
import { CheckIcon, CircleDashed, CopyIcon, X } from 'lucide-react';
import { Modal } from '../Modal';
import { useState, type FormEvent, type ReactNode } from 'react';
import { useToast } from '../../hooks/use-toast';
@ -6,12 +6,19 @@ import { httpPost } from '../../lib/http';
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
import { cn } from '../../lib/classname.ts';
import { SubmissionRequirement } from './SubmissionRequirement.tsx';
import { useCopyText } from '../../hooks/use-copy-text.ts';
type SubmitProjectResponse = {
repositoryUrl: string;
submittedAt: Date;
};
type VerificationChecksType = {
repositoryExists: 'pending' | 'success' | 'error';
readmeExists: 'pending' | 'success' | 'error';
projectUrlExists: 'pending' | 'success' | 'error';
};
type SubmitProjectModalProps = {
onClose: () => void;
projectId: string;
@ -27,19 +34,40 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
repositoryUrl: defaultRepositoryUrl = '',
} = props;
const { isCopied, copyText } = useCopyText();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const [repoUrl, setRepoUrl] = useState(defaultRepositoryUrl);
const [verificationChecks, setVerificationChecks] =
useState<VerificationChecksType>({
repositoryExists: 'pending',
readmeExists: 'pending',
projectUrlExists: 'pending',
});
const projectUrl = `${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/projects/${projectId}`;
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
try {
setVerificationChecks({
repositoryExists: 'pending',
readmeExists: 'pending',
projectUrlExists: 'pending',
});
setIsLoading(true);
setError('');
setSuccessMessage('');
if (!repoUrl) {
setVerificationChecks({
repositoryExists: 'error',
readmeExists: 'pending',
projectUrlExists: 'pending',
});
throw new Error('Repository URL is required');
}
@ -50,6 +78,12 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
const repoName = repoUrlParts[2];
if (!username || !repoName) {
setVerificationChecks({
repositoryExists: 'error',
readmeExists: 'pending',
projectUrlExists: 'pending',
});
throw new Error('Invalid GitHub repository URL');
}
@ -58,6 +92,12 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
const allContentsUrl = `${mainApiUrl}/contents`;
const allContentsResponse = await fetch(allContentsUrl);
if (!allContentsResponse.ok) {
setVerificationChecks({
repositoryExists: 'error',
readmeExists: 'pending',
projectUrlExists: 'pending',
});
if (allContentsResponse?.status === 404) {
throw new Error(
'Repository not found. Make sure it exists and is public.',
@ -69,6 +109,12 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
const allContentsData = await allContentsResponse.json();
if (!Array.isArray(allContentsData)) {
setVerificationChecks({
repositoryExists: 'error',
readmeExists: 'pending',
projectUrlExists: 'pending',
});
throw new Error('Failed to fetch repository contents');
}
@ -76,24 +122,47 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
(file) => file.name.toLowerCase() === 'readme.md',
);
if (!readmeFile || !readmeFile.url) {
setVerificationChecks({
repositoryExists: 'success',
readmeExists: 'error',
projectUrlExists: 'pending',
});
throw new Error('Readme file not found');
}
const readmeUrl = readmeFile.url;
const response = await fetch(readmeUrl);
if (!response.ok || response.status === 404) {
setVerificationChecks({
repositoryExists: 'success',
readmeExists: 'error',
projectUrlExists: 'pending',
});
throw new Error('Readme file not found');
}
const data = await response.json();
if (!data.content) {
setVerificationChecks({
repositoryExists: 'success',
readmeExists: 'error',
projectUrlExists: 'pending',
});
throw new Error('Readme file not found');
}
const readmeContent = window.atob(data.content);
const projectUrl = `${window.location.origin}/projects/${projectId}`;
if (!readmeContent.includes(projectUrl)) {
throw new Error('Project URL not found in the readme file');
setVerificationChecks({
repositoryExists: 'success',
readmeExists: 'success',
projectUrlExists: 'error',
});
throw new Error('Add the project page URL to the readme file');
}
const submitProjectUrl = `${import.meta.env.PUBLIC_API_URL}/v1-submit-project/${projectId}`;
@ -108,6 +177,7 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
setSuccessMessage('Repository verified successfully');
setIsLoading(false);
onSubmit(submitResponse);
} catch (error: any) {
console.error(error);
@ -116,21 +186,6 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
}
};
const verificationChecks = {
valid_url: {
status: 'pending',
message: 'URL must point to a public GitHub repository',
},
valid_repo: {
status: 'pending',
message: 'Repository must contain a readme file',
},
valid_readme: {
status: 'pending',
message: 'Readme file must contain the project URL',
},
};
return (
<Modal onClose={onClose} bodyClassName="h-auto p-4">
<h2 className="mb-2 flex items-center gap-2.5 text-2xl font-semibold">
@ -141,21 +196,48 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
</p>
<div className="my-4 flex flex-col gap-1">
<SubmissionRequirement status="pending">
<SubmissionRequirement status={verificationChecks.repositoryExists}>
URL must point to a public GitHub repository
</SubmissionRequirement>
<SubmissionRequirement status="pending">
<SubmissionRequirement status={verificationChecks.readmeExists}>
Repository must contain a README file
</SubmissionRequirement>
<SubmissionRequirement status="pending">
README file must contain the project URL
<SubmissionRequirement status={verificationChecks.projectUrlExists}>
README file must contain the{' '}
<button
className={
'font-medium underline underline-offset-2 hover:text-purple-700'
}
onClick={() => {
copyText(projectUrl);
}}
>
{!isCopied && (
<>
project URL{' '}
<CopyIcon
className="relative -top-0.5 inline-block h-3 w-3"
strokeWidth={2.5}
/>
</>
)}
{isCopied && (
<>
copied URL{' '}
<CheckIcon
className="relative -top-0.5 inline-block h-3 w-3"
strokeWidth={2.5}
/>
</>
)}
</button>
</SubmissionRequirement>
</div>
<form className="mt-4" onSubmit={handleSubmit}>
<input
type="text"
className="w-full rounded-lg border border-gray-300 p-2 focus:border-gray-500 focus:outline-none text-sm"
className="w-full rounded-lg border border-gray-300 p-2 text-sm focus:border-gray-500 focus:outline-none"
placeholder="https://github.com/you/solution-repo"
value={repoUrl}
onChange={(e) => setRepoUrl(e.target.value)}

Loading…
Cancel
Save