diff --git a/frontend/src/app/about/page.tsx b/frontend/src/app/about/page.tsx index f447e70caa..bb163474c4 100644 --- a/frontend/src/app/about/page.tsx +++ b/frontend/src/app/about/page.tsx @@ -351,9 +351,9 @@ const useLeadersData = () => { }, [error1, error2, error3]) const leadersData = [leader1Data?.user, leader2Data?.user, leader3Data?.user] - .filter(Boolean) + .filter((user): user is NonNullable => !!user) .map((user) => ({ - description: leaders[user.login], + description: leaders[user.login as keyof typeof leaders], memberName: user.name || user.login, member: user, })) diff --git a/frontend/src/app/api/auth/[...nextauth]/route.ts b/frontend/src/app/api/auth/[...nextauth]/route.ts index 990b0e2617..edb47a9d52 100644 --- a/frontend/src/app/api/auth/[...nextauth]/route.ts +++ b/frontend/src/app/api/auth/[...nextauth]/route.ts @@ -45,10 +45,15 @@ async function checkIfMentor(login: string): Promise { const providers = [] if (IS_GITHUB_AUTH_ENABLED) { + const clientId = process.env.NEXT_SERVER_GITHUB_CLIENT_ID + const clientSecret = process.env.NEXT_SERVER_GITHUB_CLIENT_SECRET + if (!clientId || !clientSecret) { + throw new Error('GitHub OAuth credentials are required when IS_GITHUB_AUTH_ENABLED is true') + } providers.push( GitHubProvider({ - clientId: process.env.NEXT_SERVER_GITHUB_CLIENT_ID, - clientSecret: process.env.NEXT_SERVER_GITHUB_CLIENT_SECRET, + clientId, + clientSecret, profile(profile) { return { email: profile.email, @@ -88,7 +93,7 @@ const authOptions: AuthOptions = { } if (trigger === 'update' && session) { - token.isOwaspStaff = (session as ExtendedSession).user.isOwaspStaff || false + token.isOwaspStaff = (session as ExtendedSession).user?.isOwaspStaff || false } return token }, @@ -97,10 +102,13 @@ const authOptions: AuthOptions = { ;(session as ExtendedSession).accessToken = token.accessToken as string if (session.user) { - ;(session as ExtendedSession).user.login = token.login as string - ;(session as ExtendedSession).user.isMentor = token.isMentor as boolean - ;(session as ExtendedSession).user.isLeader = token.isLeader as boolean - ;(session as ExtendedSession).user.isOwaspStaff = token.isOwaspStaff as boolean + const extendedUser = (session as ExtendedSession).user + if (extendedUser) { + extendedUser.login = token.login as string + extendedUser.isMentor = token.isMentor as boolean + extendedUser.isLeader = token.isLeader as boolean + extendedUser.isOwaspStaff = token.isOwaspStaff as boolean + } } return session }, diff --git a/frontend/src/app/board/[year]/candidates/page.tsx b/frontend/src/app/board/[year]/candidates/page.tsx index 56ac410a29..491658f88d 100644 --- a/frontend/src/app/board/[year]/candidates/page.tsx +++ b/frontend/src/app/board/[year]/candidates/page.tsx @@ -33,7 +33,7 @@ type Candidate = { avatarUrl: string bio?: string createdAt?: number - firstOwaspContributionAt?: number + firstOwaspContributionAt?: number | null id: string isFormerOwaspStaff?: boolean isGsocMentor?: boolean @@ -41,7 +41,7 @@ type Candidate = { linkedinPageId?: string login: string name: string - } + } | null } type MemberSnapshot = { @@ -333,7 +333,7 @@ const BoardCandidatesPage = () => { e.stopPropagation() e.preventDefault() window.open( - `https://linkedin.com/in/${candidate.member.linkedinPageId}`, + `https://linkedin.com/in/${candidate.member?.linkedinPageId}`, '_blank', 'noopener,noreferrer' ) diff --git a/frontend/src/app/chapters/[chapterKey]/layout.tsx b/frontend/src/app/chapters/[chapterKey]/layout.tsx index 194c749d19..79f5e057c4 100644 --- a/frontend/src/app/chapters/[chapterKey]/layout.tsx +++ b/frontend/src/app/chapters/[chapterKey]/layout.tsx @@ -33,7 +33,9 @@ export async function generateMetadata({ keywords: ['owasp', 'security', 'chapter', chapterKey, chapter.name], title: chapter.name, }) - : null + : { + title: 'Not Found', + } } export default async function ChapterDetailsLayout({ diff --git a/frontend/src/app/chapters/[chapterKey]/page.tsx b/frontend/src/app/chapters/[chapterKey]/page.tsx index eec856984f..633e2bf3e5 100644 --- a/frontend/src/app/chapters/[chapterKey]/page.tsx +++ b/frontend/src/app/chapters/[chapterKey]/page.tsx @@ -56,7 +56,7 @@ export default function ChapterDetailsPage() { const details = [ { label: 'Last Updated', value: formatDate(chapter.updatedAt) }, - { label: 'Location', value: chapter.suggestedLocation }, + { label: 'Location', value: chapter.suggestedLocation || '' }, { label: 'Region', value: chapter.region }, { label: 'URL', diff --git a/frontend/src/app/chapters/page.tsx b/frontend/src/app/chapters/page.tsx index 14f08833a6..f8e4158a91 100644 --- a/frontend/src/app/chapters/page.tsx +++ b/frontend/src/app/chapters/page.tsx @@ -49,7 +49,7 @@ const ChaptersPage = () => { const renderChapterCard = (chapter: Chapter) => { const params: string[] = ['updatedAt'] const filteredIcons = getFilteredIcons(chapter, params) - const formattedUrls = handleSocialUrls(chapter.relatedUrls) + const formattedUrls = handleSocialUrls(chapter.relatedUrls || []) const handleButtonClick = () => { router.push(`/chapters/${chapter.key}`) @@ -63,11 +63,11 @@ const ChaptersPage = () => { return ( { pageTitle: 'OWASP Committees', }) const router = useRouter() - const renderCommitteeCard = (committee: Committee) => { + const renderCommitteeCard = (committee: Committee, index: number) => { const params: string[] = ['updatedAt'] const filteredIcons = getFilteredIcons(committee, params) - const formattedUrls = handleSocialUrls(committee.relatedUrls) + const formattedUrls = handleSocialUrls(committee.relatedUrls || []) + + const uniqueKey = committee.objectID ?? committee.key ?? `committee-${index}` + const hasValidUrl = Boolean(committee.key || committee.objectID) + const urlKey = committee.key ?? committee.objectID ?? `committee-${index}` + + if (!hasValidUrl) { + // eslint-disable-next-line no-console + console.warn('Invalid committee data:', { index, committee, uniqueKey }) + } + const handleButtonClick = () => { - router.push(`/committees/${committee.key}`) + if (hasValidUrl) { + router.push(`/committees/${urlKey}`) + } } const submitButton = { @@ -37,11 +49,11 @@ const CommitteesPage = () => { return ( { return ( { const renderChapterCard = (chapter: Chapter) => { const params: string[] = ['updatedAt'] const filteredIcons = getFilteredIcons(chapter, params) - const formattedUrls = handleSocialUrls(chapter.relatedUrls) + const formattedUrls = handleSocialUrls(chapter.relatedUrls || []) const handleButtonClick = () => { router.push(`/chapters/${chapter.key}`) @@ -81,10 +81,10 @@ const SnapshotDetailsPage: React.FC = () => { return ( diff --git a/frontend/src/app/contribute/page.tsx b/frontend/src/app/contribute/page.tsx index a2b3c008f0..fd9b44cef0 100644 --- a/frontend/src/app/contribute/page.tsx +++ b/frontend/src/app/contribute/page.tsx @@ -41,26 +41,26 @@ const ContributePage = () => { } return ( - + setModalOpenIndex(null)} title={issue.title} - summary={issue.summary} - hint={issue.hint} + summary={issue.summary || ''} + hint={issue.hint || ''} button={viewIssueButton} description="The issue summary and the recommended steps to address it have been generated by AI" > diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 82c8c40abc..569e036966 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -84,7 +84,7 @@ export default function RootLayout({ - + ) } diff --git a/frontend/src/app/members/[memberKey]/layout.tsx b/frontend/src/app/members/[memberKey]/layout.tsx index b633c69a32..da54393250 100644 --- a/frontend/src/app/members/[memberKey]/layout.tsx +++ b/frontend/src/app/members/[memberKey]/layout.tsx @@ -30,9 +30,11 @@ export async function generateMetadata({ canonicalPath: `/members/${memberKey}`, description: user.bio ?? `${title} OWASP community member details.`, keywords: [user.login, user.name, 'owasp', 'owasp community member'], - title: title, + title: title || '', }) - : null + : { + title: 'Not Found', + } } export default async function UserDetailsLayout({ diff --git a/frontend/src/app/members/[memberKey]/page.tsx b/frontend/src/app/members/[memberKey]/page.tsx index 962008aed4..c02dfe46d1 100644 --- a/frontend/src/app/members/[memberKey]/page.tsx +++ b/frontend/src/app/members/[memberKey]/page.tsx @@ -186,7 +186,10 @@ const UserDetailsPage: React.FC = () => { recentIssues={issues} recentMilestones={milestones} recentReleases={releases} - repositories={topRepositories} + repositories={topRepositories?.map((repo) => ({ + ...repo, + organization: repo.organization || undefined, + }))} showAvatar={false} stats={userStats} title={user?.name || user?.login} diff --git a/frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx b/frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx index 4f02335261..9145bc6557 100644 --- a/frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx +++ b/frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx @@ -6,6 +6,7 @@ import { useParams } from 'next/navigation' import { useEffect } from 'react' import { ErrorDisplay, handleAppError } from 'app/global-error' import { GetProgramAdminsAndModulesDocument } from 'types/__generated__/moduleQueries.generated' +import { Contributor } from 'types/contributor' import { formatDate } from 'utils/dateFormatter' import DetailsCard from 'components/CardDetailsPage' import LoadingSpinner from 'components/LoadingSpinner' @@ -69,13 +70,20 @@ const ModuleDetailsPage = () => { return ( ({ + ...pr, + author: pr.author || undefined, + })) || [] + } summary={programModule.description} - tags={programModule.tags} + tags={(programModule.tags || undefined) as string[] | undefined} title={programModule.name} type="module" /> diff --git a/frontend/src/app/my/mentorship/page.tsx b/frontend/src/app/my/mentorship/page.tsx index b4fd332747..3e5a4e29fb 100644 --- a/frontend/src/app/my/mentorship/page.tsx +++ b/frontend/src/app/my/mentorship/page.tsx @@ -34,7 +34,7 @@ const MyMentorshipPage: React.FC = () => { const [programs, setPrograms] = useState([]) const [totalPages, setTotalPages] = useState(1) - const debounceSearch = useMemo(() => debounce((q) => setDebouncedQuery(q), 400), []) + const debounceSearch = useMemo(() => debounce((q: string) => setDebouncedQuery(q), 400), []) useEffect(() => { debounceSearch(searchQuery) @@ -64,7 +64,12 @@ const MyMentorshipPage: React.FC = () => { useEffect(() => { if (programData?.myPrograms) { - setPrograms(programData.myPrograms.programs) + setPrograms( + programData.myPrograms.programs.map((p) => ({ + ...p, + userRole: p.userRole || undefined, + })) + ) setTotalPages(programData.myPrograms.totalPages || 1) } }, [programData]) diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx index 940acde789..2f1a46e8db 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx @@ -33,7 +33,17 @@ const EditProgramPage = () => { skip: !programKey, fetchPolicy: 'network-only', }) - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState<{ + name: string + description: string + menteesLimit: number + startedAt: string + endedAt: string + tags: string + domains: string + adminLogins?: string + status?: string + }>({ name: '', description: '', menteesLimit: 0, @@ -104,8 +114,8 @@ const EditProgramPage = () => { endedAt: formData.endedAt, tags: parseCommaSeparated(formData.tags), domains: parseCommaSeparated(formData.domains), - adminLogins: parseCommaSeparated(formData.adminLogins), - status: formData.status, + adminLogins: parseCommaSeparated(formData.adminLogins || ''), + status: (formData.status as ProgramStatusEnum) || ProgramStatusEnum.Draft, } const result = await updateProgram({ diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx index a3f54807e9..7387f0b4c3 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx @@ -50,8 +50,16 @@ const MenteeProfilePage = () => { useEffect(() => { if (data) { - setMenteeDetails(data.getMenteeDetails ?? null) - setMenteeIssuesData(data.getMenteeModuleIssues ?? []) + setMenteeDetails( + data.getMenteeDetails + ? { + ...data.getMenteeDetails, + bio: data.getMenteeDetails.bio || undefined, + domains: (data.getMenteeDetails.domains || undefined) as string[] | undefined, + tags: (data.getMenteeDetails.tags || undefined) as string[] | undefined, + } + : null + ) } if (error) { handleAppError(error) @@ -153,7 +161,7 @@ const MenteeProfilePage = () => { {/* Domains and Skills */} - {(menteeDetails.domains?.length > 0 || menteeDetails.tags?.length > 0) && ( + {((menteeDetails.domains?.length ?? 0) > 0 || (menteeDetails.tags?.length ?? 0) > 0) && (
{menteeDetails.domains && menteeDetails.domains.length > 0 && ( diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx index 26ad68c9f4..04294c76ce 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx @@ -60,16 +60,16 @@ const ModuleDetailsPage = () => { return ( diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/page.tsx index 2ec882c417..2e79f783ce 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/page.tsx @@ -94,30 +94,30 @@ const ProgramDetailsPage = () => { } const programDetails = [ - { label: 'Status', value: titleCaseWord(program.status) }, - { label: 'Start Date', value: formatDate(program.startedAt) }, - { label: 'End Date', value: formatDate(program.endedAt) }, - { label: 'Mentees Limit', value: String(program.menteesLimit) }, + { label: 'Status', value: titleCaseWord(program?.status || '') }, + { label: 'Start Date', value: formatDate(program?.startedAt) }, + { label: 'End Date', value: formatDate(program?.endedAt) }, + { label: 'Mentees Limit', value: String(program?.menteesLimit ?? 'N/A') }, { label: 'Experience Levels', - value: program.experienceLevels?.map((level) => titleCaseWord(level)).join(', ') || 'N/A', + value: program?.experienceLevels?.map((level) => titleCaseWord(level)).join(', ') || 'N/A', }, ] return ( updateStatus(status as ProgramStatusEnum)} + status={program?.status} + summary={program?.description || ''} + tags={program?.tags || [] as string[]} + title={program?.name || 'Program'} type="program" /> ) diff --git a/frontend/src/app/my/mentorship/programs/create/page.tsx b/frontend/src/app/my/mentorship/programs/create/page.tsx index ef0cba9728..8ea70e3420 100644 --- a/frontend/src/app/my/mentorship/programs/create/page.tsx +++ b/frontend/src/app/my/mentorship/programs/create/page.tsx @@ -15,7 +15,8 @@ import ProgramForm from 'components/ProgramForm' const CreateProgramPage = () => { const router = useRouter() const { data: session, status } = useSession() - const isProjectLeader = (session as ExtendedSession)?.user.isLeader + const sessionUser = (session as ExtendedSession)?.user + const isProjectLeader = sessionUser?.isLeader const [redirected, setRedirected] = useState(false) @@ -79,8 +80,9 @@ const CreateProgramPage = () => { router.push('/my/mentorship') } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unable to complete the requested operation.' addToast({ - description: err?.message || 'Unable to complete the requested operation.', + description: errorMessage, title: 'GraphQL Request Failed', timeout: 3000, shouldShowTimeoutProgress: true, diff --git a/frontend/src/app/organizations/[organizationKey]/layout.tsx b/frontend/src/app/organizations/[organizationKey]/layout.tsx index d148c6c501..571b14723e 100644 --- a/frontend/src/app/organizations/[organizationKey]/layout.tsx +++ b/frontend/src/app/organizations/[organizationKey]/layout.tsx @@ -28,8 +28,8 @@ export async function generateMetadata({ return organization ? generateSeoMetadata({ canonicalPath: `/organizations/${organizationKey}`, - description: organization?.description ?? `${title} organization details`, - title: title, + description: organization?.description ?? `${title || ''} organization details`, + title: title || 'Organization', }) : null } diff --git a/frontend/src/app/organizations/[organizationKey]/page.tsx b/frontend/src/app/organizations/[organizationKey]/page.tsx index 95350011fa..0ffa8fe837 100644 --- a/frontend/src/app/organizations/[organizationKey]/page.tsx +++ b/frontend/src/app/organizations/[organizationKey]/page.tsx @@ -74,7 +74,7 @@ const OrganizationDetailsPage = () => { }, { label: 'Location', - value: organization.location, + value: organization.location || 'N/A', }, ] @@ -110,15 +110,15 @@ const OrganizationDetailsPage = () => { return ( ({ ...i, author: i.author || undefined }))} + recentReleases={recentReleases || undefined} + recentMilestones={recentMilestones || undefined} + pullRequests={recentPullRequests?.map((pr) => ({ ...pr, author: pr.author || undefined }))} + repositories={repositories?.map((r) => ({ ...r, organization: r.organization || undefined }))} stats={organizationStats} summary={organization.description} title={organization.name} - topContributors={topContributors} + topContributors={topContributors || undefined} type="organization" /> ) diff --git a/frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx b/frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx index ea5fc2f7c9..1bd6bf82e7 100644 --- a/frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx +++ b/frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx @@ -7,8 +7,10 @@ import { FaExclamationCircle } from 'react-icons/fa' import { FaCodeCommit, FaCodeFork, FaStar } from 'react-icons/fa6' import { HiUserGroup } from 'react-icons/hi' import { handleAppError, ErrorDisplay } from 'app/global-error' -import { GetRepositoryDataDocument } from 'types/__generated__/repositoryQueries.generated' import type { Contributor } from 'types/contributor' +import type { Issue } from 'types/issue' +import type { PullRequest } from 'types/pullRequest' +import { GetRepositoryDataDocument, GetRepositoryDataQuery } from 'types/__generated__/repositoryQueries.generated' import { formatDate } from 'utils/dateFormatter' import DetailsCard from 'components/CardDetailsPage' import LoadingSpinner from 'components/LoadingSpinner' @@ -18,9 +20,11 @@ const RepositoryDetailsPage = () => { repositoryKey: string organizationKey: string }>() - const [repository, setRepository] = useState(null) + const [repository, setRepository] = useState(null) const [topContributors, setTopContributors] = useState([]) - const [recentPullRequests, setRecentPullRequests] = useState(null) + const [recentPullRequests, setRecentPullRequests] = useState( + null + ) const { data, error: graphQLRequestError, @@ -31,7 +35,7 @@ const RepositoryDetailsPage = () => { useEffect(() => { if (data) { setRepository(data.repository) - setTopContributors(data.topContributors) + setTopContributors((data.topContributors as Contributor[]) || []) setRecentPullRequests(data.recentPullRequests) } if (graphQLRequestError) { @@ -56,69 +60,80 @@ const RepositoryDetailsPage = () => { const repositoryDetails = [ { label: 'Last Updated', - value: formatDate(repository.updatedAt), + value: formatDate(repository?.updatedAt), }, { label: 'License', - value: repository.license, + value: repository?.license, }, { label: 'Size', - value: `${repository.size} KB`, + value: repository?.size != null ? `${repository.size} KB` : '', }, { label: 'URL', - value: ( - + value: repository?.url ? ( + {repository.url} - ), + ) : '', }, ] const RepositoryStats = [ { icon: FaStar, - value: repository.starsCount, + value: repository?.starsCount, unit: 'Star', }, { icon: FaCodeFork, - value: repository.forksCount, + value: repository?.forksCount, unit: 'Fork', }, { icon: HiUserGroup, - value: repository.contributorsCount, + value: repository?.contributorsCount, unit: 'Contributor', }, { icon: FaExclamationCircle, - value: repository.openIssuesCount, + value: repository?.openIssuesCount, unit: 'Issue', }, { icon: FaCodeCommit, - value: repository.commitsCount, + value: repository?.commitsCount, unit: 'Commit', }, ] return ( ({ ...d, value: d.value || '' }))} + entityKey={repository?.project?.key} + isArchived={repository?.isArchived} + languages={repository?.languages || []} + projectName={repository?.project?.name} + pullRequests={ + recentPullRequests?.map((pr) => ({ + ...pr, + author: pr.author ? { ...pr.author } : undefined, + })) as PullRequest[] | undefined + } + recentIssues={ + repository?.issues?.map((issue) => ({ + ...issue, + url: issue.url || '', + author: issue.author ? { ...issue.author } : undefined, + })) as Issue[] | undefined + } + recentMilestones={repository?.recentMilestones || undefined} + recentReleases={repository?.releases || undefined} + stats={RepositoryStats.map((s) => ({ ...s, value: s.value || 0 }))} + summary={repository?.description} + title={repository?.name} topContributors={topContributors} - topics={repository.topics} + topics={repository?.topics || []} type="repository" /> ) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 944c13f435..d8f3fe3463 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -111,23 +111,23 @@ export default function Home() { const counterData = [ { label: 'Active Projects', - value: data.statsOverview.activeProjectsStats, + value: data?.statsOverview?.activeProjectsStats || 0, }, { label: 'Contributors', - value: data.statsOverview.contributorsStats, + value: data?.statsOverview?.contributorsStats || 0, }, { label: 'Local Chapters', - value: data.statsOverview.activeChaptersStats, + value: data?.statsOverview?.activeChaptersStats || 0, }, { label: 'Countries', - value: data.statsOverview.countriesStats, + value: data?.statsOverview?.countriesStats || 0, }, { label: 'Slack Community', - value: data.statsOverview.slackWorkspaceStats, + value: data?.statsOverview?.slackWorkspaceStats || 0, }, ] @@ -145,7 +145,7 @@ export default function Home() {
- {data.upcomingEvents.map((event: Event, index: number) => ( + {data?.upcomingEvents?.map((event: Event, index: number) => (
@@ -223,7 +223,7 @@ export default function Home() { className="overflow-hidden" >
- {data.recentChapters?.map((chapter) => ( + {data?.recentChapters?.map((chapter) => (

)} @@ -269,7 +269,7 @@ export default function Home() { className="overflow-hidden" >
- {data.recentProjects?.map((project) => ( + {data?.recentProjects?.map((project) => (

@@ -294,7 +294,7 @@ export default function Home() {

)} @@ -322,19 +322,19 @@ export default function Home() { />
- - + +
- - + +
- +
diff --git a/frontend/src/app/projects/[projectKey]/page.tsx b/frontend/src/app/projects/[projectKey]/page.tsx index dce36a4d2b..bebfd2415f 100644 --- a/frontend/src/app/projects/[projectKey]/page.tsx +++ b/frontend/src/app/projects/[projectKey]/page.tsx @@ -112,11 +112,31 @@ const ProjectDetailsPage = () => { healthMetricsData={project.healthMetricsList} isActive={project.isActive} languages={project.languages} - pullRequests={project.recentPullRequests} - recentIssues={project.recentIssues} - recentMilestones={project.recentMilestones} - recentReleases={project.recentReleases} - repositories={project.repositories} + pullRequests={ + project.recentPullRequests?.map((pr) => ({ + ...pr, + author: pr.author || undefined, + })) || [] + } + recentIssues={ + project.recentIssues?.map((issue) => ({ + ...issue, + author: issue.author || undefined, + })) || [] + } + recentMilestones={project.recentMilestones || undefined} + recentReleases={ + project.recentReleases?.map((release) => ({ + ...release, + author: release.author || undefined, + })) || [] + } + repositories={ + project.repositories?.map((repo) => ({ + ...repo, + organization: repo.organization || undefined, + })) || [] + } startDate={startDate} stats={projectStats} summary={project.summary} diff --git a/frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx b/frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx index 3f12991efc..66365a312f 100644 --- a/frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx +++ b/frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx @@ -53,7 +53,7 @@ const ProjectHealthMetricsDetails: FC = () => { const labels = metricsList?.map((m) => - new Date(m.createdAt).toLocaleString('default', { + new Date(m.createdAt ?? Date.now()).toLocaleString('default', { month: 'short', day: 'numeric', }) @@ -71,7 +71,7 @@ const ProjectHealthMetricsDetails: FC = () => { />
- + { : 'Funding Requirements Not Compliant' } icon={FaDollarSign} - compliant={metricsLatest.isFundingRequirementsCompliant} + compliant={metricsLatest.isFundingRequirementsCompliant ?? false} /> { : 'Leader Requirements Not Compliant' } icon={FaHandshake} - compliant={metricsLatest.isLeaderRequirementsCompliant} + compliant={metricsLatest.isLeaderRequirementsCompliant ?? false} />
@@ -99,7 +99,7 @@ const ProjectHealthMetricsDetails: FC = () => { series={[ { name: 'Stars', - data: metricsList.map((m) => m.starsCount), + data: metricsList.map((m) => m.starsCount ?? 0), }, ]} labels={labels} @@ -111,7 +111,7 @@ const ProjectHealthMetricsDetails: FC = () => { series={[ { name: 'Forks', - data: metricsList.map((m) => m.forksCount), + data: metricsList.map((m) => m.forksCount ?? 0), }, ]} labels={labels} @@ -125,19 +125,19 @@ const ProjectHealthMetricsDetails: FC = () => { series={[ { name: 'Open Issues', - data: metricsList.map((m) => m.openIssuesCount), + data: metricsList.map((m) => m.openIssuesCount ?? 0), }, { name: 'Unassigned Issues', - data: metricsList.map((m) => m.unassignedIssuesCount), + data: metricsList.map((m) => m.unassignedIssuesCount ?? 0), }, { name: 'Unanswered Issues', - data: metricsList.map((m) => m.unansweredIssuesCount), + data: metricsList.map((m) => m.unansweredIssuesCount ?? 0), }, { name: 'Total Issues', - data: metricsList.map((m) => m.totalIssuesCount), + data: metricsList.map((m) => m.totalIssuesCount ?? 0), }, ]} labels={labels} @@ -149,7 +149,7 @@ const ProjectHealthMetricsDetails: FC = () => { series={[ { name: 'Open Pull Requests', - data: metricsList.map((m) => m.openPullRequestsCount), + data: metricsList.map((m) => m.openPullRequestsCount ?? 0), }, ]} labels={labels} @@ -163,11 +163,11 @@ const ProjectHealthMetricsDetails: FC = () => { series={[ { name: 'Recent Releases', - data: metricsList.map((m) => m.recentReleasesCount), + data: metricsList.map((m) => m.recentReleasesCount ?? 0), }, { name: 'Total Releases', - data: metricsList.map((m) => m.totalReleasesCount), + data: metricsList.map((m) => m.totalReleasesCount ?? 0), }, ]} labels={labels} @@ -179,7 +179,7 @@ const ProjectHealthMetricsDetails: FC = () => { series={[ { name: 'Contributors', - data: metricsList.map((m) => m.contributorsCount), + data: metricsList.map((m) => m.contributorsCount ?? 0), }, ]} labels={labels} @@ -197,18 +197,18 @@ const ProjectHealthMetricsDetails: FC = () => { 'Days Since OWASP Page Last Update', ]} days={[ - metricsLatest.ageDays, - metricsLatest.lastCommitDays, - metricsLatest.lastReleaseDays, - metricsLatest.lastPullRequestDays, - metricsLatest.owaspPageLastUpdateDays, + metricsLatest.ageDays ?? 0, + metricsLatest.lastCommitDays ?? 0, + metricsLatest.lastReleaseDays ?? 0, + metricsLatest.lastPullRequestDays ?? 0, + metricsLatest.owaspPageLastUpdateDays ?? 0, ]} requirements={[ - metricsLatest.ageDaysRequirement, - metricsLatest.lastCommitDaysRequirement, - metricsLatest.lastReleaseDaysRequirement, - metricsLatest.lastPullRequestDaysRequirement, - metricsLatest.owaspPageLastUpdateDaysRequirement, + metricsLatest.ageDaysRequirement ?? 0, + metricsLatest.lastCommitDaysRequirement ?? 0, + metricsLatest.lastReleaseDaysRequirement ?? 0, + metricsLatest.lastPullRequestDaysRequirement ?? 0, + metricsLatest.owaspPageLastUpdateDaysRequirement ?? 0, ]} reverseColors={[true, false, false, false, false]} /> diff --git a/frontend/src/components/Card.tsx b/frontend/src/components/Card.tsx index 61f2770e85..288de81358 100644 --- a/frontend/src/components/Card.tsx +++ b/frontend/src/components/Card.tsx @@ -52,16 +52,24 @@ const Card = ({ )} {/* Project title and link */} - -

- {title} -

- + {url ? ( + +

+ {title} +

+ + ) : ( +
+

+ {title} +

+
+ )}
{/* Icons associated with the project */} diff --git a/frontend/src/components/CardDetailsPage.tsx b/frontend/src/components/CardDetailsPage.tsx index 2a8406007b..b5533fd072 100644 --- a/frontend/src/components/CardDetailsPage.tsx +++ b/frontend/src/components/CardDetailsPage.tsx @@ -84,34 +84,34 @@ const DetailsCard = ({ status, setStatus, canUpdateStatus, - tags, - domains, - entityLeaders, - labels, - modules, - mentors, - mentees, - admins, - entityKey, - geolocationData = null, - healthMetricsData, + tags = [], + domains = [], + entityLeaders = [], + labels = [], + modules = [], + mentors = [], + mentees = [], + admins = [], + entityKey = '', + geolocationData = [], + healthMetricsData = [], isActive = true, isArchived = false, - languages, - programKey, - projectName, - pullRequests, - recentIssues, - recentMilestones, - recentReleases, + languages = [], + programKey = '', + projectName = '', + pullRequests = [], + recentIssues = [], + recentMilestones = [], + recentReleases = [], repositories = [], showAvatar = true, - socialLinks, - stats, - summary, - title, - topContributors, - topics, + socialLinks = [], + stats = [], + summary = '', + title = '', + topContributors = [], + topics = [], type, userSummary, }: DetailsCardProps) => { @@ -120,7 +120,7 @@ const DetailsCard = ({ const [showAllMilestones, setShowAllMilestones] = useState(false) // compute styles based on type prop - const typeStylesMap = { + const typeStylesMap: Partial> = { chapter: 'gap-2 md:col-span-3', module: 'gap-2 md:col-span-7', program: 'gap-2 md:col-span-7', @@ -156,7 +156,7 @@ const DetailsCard = ({ {isArchived && type === 'repository' && } {IS_PROJECT_HEALTH_ENABLED && type === 'project' && healthMetricsData.length > 0 && ( scrollToAnchor('issues-trend')} /> @@ -460,7 +460,7 @@ const DetailsCard = ({
- {formatDate(milestone.createdAt)} + {formatDate(milestone.createdAt || '')}
@@ -509,7 +509,7 @@ const DetailsCard = ({ export default DetailsCard -export const SocialLinks = ({ urls }) => { +export const SocialLinks = ({ urls }: { urls: string[] | null }) => { if (!urls || urls.length === 0) return null return (
diff --git a/frontend/src/components/HealthMetrics.tsx b/frontend/src/components/HealthMetrics.tsx index 64dd3c8411..19aa4b6af5 100644 --- a/frontend/src/components/HealthMetrics.tsx +++ b/frontend/src/components/HealthMetrics.tsx @@ -6,9 +6,9 @@ import BarChart from 'components/BarChart' import LineChart from 'components/LineChart' const HealthMetrics: React.FC<{ data: HealthMetricsProps[] }> = ({ data }) => { - const openIssuesCountArray = data.map((item) => item.openIssuesCount) + const openIssuesCountArray = data.map((item) => item.openIssuesCount ?? 0) const labels = data.map((item) => { - return new Date(item.createdAt).toLocaleDateString('en-US', { + return new Date(item.createdAt ?? Date.now()).toLocaleDateString('en-US', { month: 'short', day: 'numeric', }) @@ -27,11 +27,11 @@ const HealthMetrics: React.FC<{ data: HealthMetricsProps[] }> = ({ data }) => { }, { name: 'Unassigned Issues', - data: data.map((item) => item.unassignedIssuesCount), + data: data.map((item) => item.unassignedIssuesCount ?? 0), }, { name: 'Unanswered Issues', - data: data.map((item) => item.unansweredIssuesCount), + data: data.map((item) => item.unansweredIssuesCount ?? 0), }, ]} labels={labels} @@ -43,7 +43,7 @@ const HealthMetrics: React.FC<{ data: HealthMetricsProps[] }> = ({ data }) => { series={[ { name: 'Open Pull Requests', - data: data.map((item) => item.openPullRequestsCount), + data: data.map((item) => item.openPullRequestsCount ?? 0), }, ]} labels={labels} @@ -57,7 +57,7 @@ const HealthMetrics: React.FC<{ data: HealthMetricsProps[] }> = ({ data }) => { series={[ { name: 'Stars', - data: data.map((item) => item.starsCount), + data: data.map((item) => item.starsCount ?? 0), }, ]} labels={labels} @@ -69,7 +69,7 @@ const HealthMetrics: React.FC<{ data: HealthMetricsProps[] }> = ({ data }) => { series={[ { name: 'Forks', - data: data.map((item) => item.forksCount), + data: data.map((item) => item.forksCount ?? 0), }, ]} labels={labels} diff --git a/frontend/src/components/ItemCardList.tsx b/frontend/src/components/ItemCardList.tsx index c8fd4c7403..97490a6be0 100644 --- a/frontend/src/components/ItemCardList.tsx +++ b/frontend/src/components/ItemCardList.tsx @@ -24,19 +24,19 @@ const ItemCardList = ({ showAvatar?: boolean showSingleColumn?: boolean renderDetails: (item: { - createdAt: string - commentsCount: number - organizationName: string - publishedAt: string - repositoryName: string - tagName: string - openIssuesCount: number - closedIssuesCount: number - author: { + createdAt?: string | number + commentsCount?: number + organizationName?: string | null + publishedAt?: number + repositoryName?: string | null + tagName?: string + openIssuesCount?: number + closedIssuesCount?: number + author?: { avatarUrl: string login: string - name: string - } + name?: string + } | null }) => JSX.Element }) => ( @@ -67,13 +67,14 @@ const ItemCardList = ({ { diff --git a/frontend/src/components/UserCard.tsx b/frontend/src/components/UserCard.tsx index 0ef856b39c..bdc478433b 100644 --- a/frontend/src/components/UserCard.tsx +++ b/frontend/src/components/UserCard.tsx @@ -59,21 +59,23 @@ const UserCard = ({ )}
- {(followersCount > 0 || repositoriesCount > 0 || badgeCount > 0) && ( + {((followersCount ?? 0) > 0 || + (repositoriesCount ?? 0) > 0 || + (badgeCount ?? 0) > 0) && (
- {followersCount > 0 && ( + {followersCount && followersCount > 0 && (
{millify(followersCount, { precision: 1 })}
)} - {repositoriesCount > 0 && ( + {repositoriesCount && repositoriesCount > 0 && (
{millify(repositoriesCount, { precision: 1 })}
)} - {badgeCount > 0 && ( + {badgeCount && badgeCount > 0 && (
{millify(badgeCount, { precision: 1 })} diff --git a/frontend/src/server/queries/repositoryQueries.ts b/frontend/src/server/queries/repositoryQueries.ts index 27f93ffc40..c7b8793254 100644 --- a/frontend/src/server/queries/repositoryQueries.ts +++ b/frontend/src/server/queries/repositoryQueries.ts @@ -23,6 +23,7 @@ export const GET_REPOSITORY_DATA = gql` repositoryName createdAt title + url } languages license diff --git a/frontend/src/types/__generated__/repositoryQueries.generated.ts b/frontend/src/types/__generated__/repositoryQueries.generated.ts index 3a5341098e..d571e68959 100644 --- a/frontend/src/types/__generated__/repositoryQueries.generated.ts +++ b/frontend/src/types/__generated__/repositoryQueries.generated.ts @@ -7,7 +7,7 @@ export type GetRepositoryDataQueryVariables = Types.Exact<{ }>; -export type GetRepositoryDataQuery = { repository: { __typename: 'RepositoryNode', id: string, commitsCount: number, contributorsCount: number, createdAt: any, description: string, forksCount: number, isArchived: boolean, key: string, languages: Array, license: string, name: string, openIssuesCount: number, size: number, starsCount: number, topics: Array, updatedAt: any, url: string, issues: Array<{ __typename: 'IssueNode', id: string, organizationName: string | null, repositoryName: string | null, createdAt: any, title: string, author: { __typename: 'UserNode', id: string, avatarUrl: string, login: string, name: string } | null }>, organization: { __typename: 'OrganizationNode', id: string, login: string, name: string } | null, project: { __typename: 'ProjectNode', id: string, key: string, name: string } | null, releases: Array<{ __typename: 'ReleaseNode', id: string, isPreRelease: boolean, name: string, organizationName: string | null, publishedAt: any | null, repositoryName: string | null, tagName: string, author: { __typename: 'UserNode', id: string, avatarUrl: string, name: string, login: string } | null }>, recentMilestones: Array<{ __typename: 'MilestoneNode', id: string, title: string, openIssuesCount: number, closedIssuesCount: number, repositoryName: string | null, organizationName: string | null, createdAt: any, url: string, author: { __typename: 'UserNode', id: string, avatarUrl: string, login: string, name: string } | null }> } | null, topContributors: Array<{ __typename: 'RepositoryContributorNode', id: string, avatarUrl: string, login: string, name: string }>, recentPullRequests: Array<{ __typename: 'PullRequestNode', id: string, createdAt: any, organizationName: string | null, repositoryName: string | null, title: string, url: string, author: { __typename: 'UserNode', id: string, avatarUrl: string, login: string, name: string } | null }> }; +export type GetRepositoryDataQuery = { repository: { __typename: 'RepositoryNode', id: string, commitsCount: number, contributorsCount: number, createdAt: any, description: string, forksCount: number, isArchived: boolean, key: string, languages: Array, license: string, name: string, openIssuesCount: number, size: number, starsCount: number, topics: Array, updatedAt: any, url: string, issues: Array<{ __typename: 'IssueNode', id: string, organizationName: string | null, repositoryName: string | null, createdAt: any, title: string, url: string, author: { __typename: 'UserNode', id: string, avatarUrl: string, login: string, name: string } | null }>, organization: { __typename: 'OrganizationNode', id: string, login: string, name: string } | null, project: { __typename: 'ProjectNode', id: string, key: string, name: string } | null, releases: Array<{ __typename: 'ReleaseNode', id: string, isPreRelease: boolean, name: string, organizationName: string | null, publishedAt: any | null, repositoryName: string | null, tagName: string, author: { __typename: 'UserNode', id: string, avatarUrl: string, name: string, login: string } | null }>, recentMilestones: Array<{ __typename: 'MilestoneNode', id: string, title: string, openIssuesCount: number, closedIssuesCount: number, repositoryName: string | null, organizationName: string | null, createdAt: any, url: string, author: { __typename: 'UserNode', id: string, avatarUrl: string, login: string, name: string } | null }> } | null, topContributors: Array<{ __typename: 'RepositoryContributorNode', id: string, avatarUrl: string, login: string, name: string }>, recentPullRequests: Array<{ __typename: 'PullRequestNode', id: string, createdAt: any, organizationName: string | null, repositoryName: string | null, title: string, url: string, author: { __typename: 'UserNode', id: string, avatarUrl: string, login: string, name: string } | null }> }; export type GetRepositoryMetadataQueryVariables = Types.Exact<{ repositoryKey: Types.Scalars['String']['input']; diff --git a/frontend/src/types/chapter.ts b/frontend/src/types/chapter.ts index 017488cbb1..fe1113ec17 100644 --- a/frontend/src/types/chapter.ts +++ b/frontend/src/types/chapter.ts @@ -13,7 +13,7 @@ export type Chapter = { } createdAt?: number entityLeaders?: Leader[] - geoLocation?: GeoLocation + geoLocation?: GeoLocation | null isActive?: boolean key: string leaders?: string[] @@ -21,7 +21,7 @@ export type Chapter = { objectID?: string region?: string relatedUrls?: string[] - suggestedLocation: string + suggestedLocation?: string | null summary?: string topContributors?: Contributor[] updatedAt?: number diff --git a/frontend/src/types/declarations.d.ts b/frontend/src/types/declarations.d.ts new file mode 100644 index 0000000000..69526c50f3 --- /dev/null +++ b/frontend/src/types/declarations.d.ts @@ -0,0 +1,29 @@ +declare module 'isomorphic-dompurify' { + const sanitize: (content: string) => string + export default { sanitize } +} + +declare module 'lodash'; + +declare module 'jest-axe' { + interface AxeResults { + violations: Array<{ + id: string + impact?: string + description: string + nodes: Array<{ html: string }> + }> + } + const axe: (html: Element | string) => Promise + const toHaveNoViolations: jest.CustomMatcher + export { axe, toHaveNoViolations } +} +declare global { + namespace jest { + interface Matchers { + toHaveNoViolations(): R + } + } +} + +export {} diff --git a/frontend/src/types/healthMetrics.ts b/frontend/src/types/healthMetrics.ts index e38f1180db..a618817e4a 100644 --- a/frontend/src/types/healthMetrics.ts +++ b/frontend/src/types/healthMetrics.ts @@ -6,10 +6,10 @@ export type ApexLineChartSeries = { export type ApexBarChartDataSeries = { x: string y: number - fill? + fill?: any // ApexFill fillColor?: string strokeColor?: string - meta? + meta?: any // eslint-disable-line @typescript-eslint/no-explicit-any goals?: { barHeightOffset?: number columnWidthOffset?: number @@ -45,7 +45,7 @@ export type HealthMetricsProps = { projectName?: string projectKey?: string recentReleasesCount?: number - score?: number + score?: number | null starsCount?: number totalIssuesCount?: number totalReleasesCount?: number diff --git a/frontend/src/types/milestone.ts b/frontend/src/types/milestone.ts index 9656c02b4c..978ab753f0 100644 --- a/frontend/src/types/milestone.ts +++ b/frontend/src/types/milestone.ts @@ -1,14 +1,14 @@ import type { User } from 'types/user' export type Milestone = { - author?: User + author?: User | null body?: string closedIssuesCount?: number createdAt?: string openIssuesCount?: number - organizationName?: string + organizationName?: string | null progress?: number - repositoryName?: string + repositoryName?: string | null state?: string title: string url?: string diff --git a/frontend/src/types/project.ts b/frontend/src/types/project.ts index e954cdd013..026ed4820d 100644 --- a/frontend/src/types/project.ts +++ b/frontend/src/types/project.ts @@ -28,7 +28,7 @@ export type Project = { releases: number total: number } - description?: string + description?: string | null entityLeaders?: Leader[] forksCount?: number healthMetricsList?: HealthMetricsProps[] @@ -37,18 +37,18 @@ export type Project = { key?: string languages?: string[] leaders?: string[] - level?: string + level?: string | null name: string openIssuesCount?: number - organizations?: string + organizations?: string | null repositoriesCount?: number starsCount?: number - summary?: string + summary?: string | null topics?: string[] topContributors?: Contributor[] type?: string updatedAt?: number - url?: string + url?: string | null recentIssues?: Issue[] recentPullRequests?: PullRequest[] recentReleases?: Release[] diff --git a/frontend/src/types/release.ts b/frontend/src/types/release.ts index c82d2225c2..ee051c3509 100644 --- a/frontend/src/types/release.ts +++ b/frontend/src/types/release.ts @@ -1,14 +1,14 @@ import type { RepositoryDetails, User } from 'types/user' export type Release = { - author?: User + author?: User | null id: string isPreRelease?: boolean name: string - organizationName?: string - projectName?: string + organizationName?: string | null + projectName?: string | null publishedAt: number repository?: RepositoryDetails - repositoryName: string + repositoryName: string | null tagName: string } diff --git a/frontend/src/utils/helpers/apolloClient.ts b/frontend/src/utils/helpers/apolloClient.ts index 42fdbeca87..49c15d1dc4 100644 --- a/frontend/src/utils/helpers/apolloClient.ts +++ b/frontend/src/utils/helpers/apolloClient.ts @@ -7,7 +7,7 @@ const createApolloClient = () => { if (!GRAPHQL_URL) { const error = new AppError(500, 'Missing GraphQL URL') handleAppError(error) - return null + throw error } const httpLink = new HttpLink({ diff --git a/frontend/src/utils/helpers/githubHeatmap.ts b/frontend/src/utils/helpers/githubHeatmap.ts index fd06f967c7..0ed8266668 100644 --- a/frontend/src/utils/helpers/githubHeatmap.ts +++ b/frontend/src/utils/helpers/githubHeatmap.ts @@ -52,7 +52,7 @@ export const fetchHeatmapData = async (username: string): Promise new Date(item.date) <= endDate && new Date(item.date) >= startDate + (item: HeatmapContribution) => new Date(item.date) <= endDate && new Date(item.date) >= startDate ) const allDates: string[] = [] @@ -61,7 +61,7 @@ export const fetchHeatmapData = async (username: string): Promise { - const contribution = heatmapData.contributions.find((c) => c.date === date) + const contribution = heatmapData.contributions.find((c: HeatmapContribution) => c.date === date) return contribution ? { date: contribution.date, diff --git a/frontend/src/utils/metaconfig.ts b/frontend/src/utils/metaconfig.ts index bfd34dda7c..40e85b7b4a 100644 --- a/frontend/src/utils/metaconfig.ts +++ b/frontend/src/utils/metaconfig.ts @@ -65,7 +65,10 @@ export function generateSeoMetadata({ } } -export function getStaticMetadata(pageKey, canonicalPath?: string): Metadata { +export function getStaticMetadata( + pageKey: keyof typeof METADATA_CONFIG, + canonicalPath?: string +): Metadata { if (!METADATA_CONFIG[pageKey]) { throw new Error(`No metadata configuration found for key: ${pageKey}`) } @@ -76,6 +79,6 @@ export function getStaticMetadata(pageKey, canonicalPath?: string): Metadata { description: config.description, keywords: config.keywords, title: config.pageTitle, - type: config.type, + type: config.type as 'website' | 'article' | 'profile', }) } diff --git a/frontend/src/utils/structuredData.ts b/frontend/src/utils/structuredData.ts index 319428994b..edbfb8b74b 100644 --- a/frontend/src/utils/structuredData.ts +++ b/frontend/src/utils/structuredData.ts @@ -1,7 +1,7 @@ import { ProfilePageStructuredData } from 'types/profilePageStructuredData' import type { User } from 'types/user' -export const formatISODate = (input?: number | string): string => { +export const formatISODate = (input?: number | string): string | undefined => { if (input == null) { return undefined } @@ -45,15 +45,15 @@ export function generateProfilePageStructuredData( ...(user.location && { address: user.location, }), - description: user.bio, + description: user.bio || '', identifier: user.login, image: user.avatarUrl, - ...(user.followersCount > 0 && { + ...((user.followersCount || 0) > 0 && { interactionStatistic: [ { '@type': 'InteractionCounter', interactionType: 'https://schema.org/FollowAction', - userInteractionCount: user.followersCount, + userInteractionCount: user.followersCount || 0, }, ], }), @@ -63,7 +63,7 @@ export function generateProfilePageStructuredData( url: 'https://nest.owasp.org/members', }, name: user.name || user.login, - sameAs: [user.url], + sameAs: user.url ? [user.url] : [], url: `${baseUrl}/members/${user.login}`, ...(user.company && { worksFor: {