diff --git a/src/components/Projects/SubmissionRequirement.tsx b/src/components/Projects/SubmissionRequirement.tsx index a8946aaff..455f7d084 100644 --- a/src/components/Projects/SubmissionRequirement.tsx +++ b/src/components/Projects/SubmissionRequirement.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' ? ( ) : ( - + )} {children} diff --git a/src/components/Projects/SubmitProjectModal.tsx b/src/components/Projects/SubmitProjectModal.tsx index c9f271eb4..1bcece5da 100644 --- a/src/components/Projects/SubmitProjectModal.tsx +++ b/src/components/Projects/SubmitProjectModal.tsx @@ -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({ + 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 (

@@ -141,21 +196,48 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {

- + URL must point to a public GitHub repository - + Repository must contain a README file - - README file must contain the project URL + + README file must contain the{' '} +
setRepoUrl(e.target.value)}