diff --git a/packages/docusaurus-playground/src/pages/index.mdx b/packages/docusaurus-playground/src/pages/index.mdx index a3b3b24..6b22eb5 100644 --- a/packages/docusaurus-playground/src/pages/index.mdx +++ b/packages/docusaurus-playground/src/pages/index.mdx @@ -18,6 +18,7 @@ import { SectionHeader, TimelineItem, ShowcaseCard, + JobsPerDepartment, } from '../components/mdx' @@ -352,3 +353,4 @@ import { Build on Waku + diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/JobsPerDepartment.scss b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/JobsPerDepartment.scss new file mode 100644 index 0000000..c2e7d88 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/JobsPerDepartment.scss @@ -0,0 +1,52 @@ +@use '../../../css/utils'; +@use '../../../css/lsd'; + +.mdx-jpd__header { + margin-bottom: 40px; + margin-top: 16px; +} + +.mdx-jpd__single-job-department-container { + border-top: 1px solid rgb(var(--lsd-border-primary)); + padding-top: 8px; +} + +.mdx-jpd__department-title { + font-size: 12px !important; + line-height: 16px !important; +} + +.mdx-jpd__job-title-container { + display: flex; + align-items: center; + + padding-bottom: 8px; +} + +.mdx-jpd__external-link-icon { + margin-left: 8px; +} + +.mdx-jpd__job-list { + list-style-type: none; + padding: 8px 0 24px 0; + margin: 0; +} + +.mdx-jpd__job-list-item { + padding: 14px 0; +} + +.mdx-jpd__job-link { + display: block; + width: fit-content; + text-decoration: none; + + &:hover { + text-decoration: none; + + .mdx-jpd__job-title { + text-decoration: underline; + } + } +} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/JobsPerDepartment.tsx b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/JobsPerDepartment.tsx new file mode 100644 index 0000000..e503998 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/JobsPerDepartment.tsx @@ -0,0 +1,79 @@ +import { Typography } from '@acid-info/lsd-react' +import { SingleDepartmentJobs } from './SingleDepartmentJobs' +import useFetchJobs, { JobDepartmentArray } from './useFetchJobs' +import React from 'react' +import { jobsPerDepartmentDummyData } from './jobsPerDepartmentDummyData' + +type JobsPerDepartmentHeaderProps = { + message?: string +} + +const JobsPerDepartmentHeader: React.FC = ({ + message, +}) => { + return ( + <> + + Current job openings + + + {!!message && {message}} + + ) +} + +const hasJobs = (jobsPerDepartment: JobDepartmentArray): boolean => { + if (!jobsPerDepartment) return false + + // Check if there's any department that has at least one job + let jobFound = jobsPerDepartment.some((department) => { + return department.jobs && department.jobs.length > 0 + }) + + return jobFound +} + +type JobsPerDepartmentProps = React.HTMLAttributes & { + jobBoard: string + titleFilter?: string + fetchAll?: boolean + useDummyData?: boolean +} + +export const JobsPerDepartment: React.FC = ({ + jobBoard, + titleFilter = '', + fetchAll = false, + useDummyData = false, + ...props +}) => { + const { data, error, isLoading } = useFetchJobs( + jobBoard, + titleFilter, + useDummyData ? jobsPerDepartmentDummyData : null, + ) + + if (isLoading) { + // Skipping loading state for now, as per the designer's request. + return + } + + if (error) { + return + } + + if (!data || !hasJobs(data)) { + return + } + + return ( +
+ + {data.map((department) => { + return ( + + ) + })} +
+ ) +} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/SingleDepartmentJobs.tsx b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/SingleDepartmentJobs.tsx new file mode 100644 index 0000000..2a26e61 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/SingleDepartmentJobs.tsx @@ -0,0 +1,50 @@ +import { Typography } from '@acid-info/lsd-react' +import { JobDepartmentData } from './useFetchJobs' +import React from 'react' +import { IconExternalLink } from '@logos-theme/components/Icon' +import './JobsPerDepartment.scss' +import Link from '@docusaurus/Link' + +type SingleDepartmentJobsProps = { + department: JobDepartmentData +} + +export const SingleDepartmentJobs: React.FC = ({ + department, +}) => { + if (!department.jobs || department.jobs.length === 0) { + return null + } + + return ( +
+ + {department.name} + +
    + {department.jobs.map((job, index) => ( +
  • + +
    + + {job.title} + + +
    + + {!!job.location?.name && ( + + {job.location.name} + + )} + +
  • + ))} +
+
+ ) +} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/index.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/index.ts new file mode 100644 index 0000000..68907d2 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/index.ts @@ -0,0 +1 @@ +export * from './JobsPerDepartment' diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/jobsPerDepartmentDummyData.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/jobsPerDepartmentDummyData.ts new file mode 100644 index 0000000..ee967da --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/jobsPerDepartmentDummyData.ts @@ -0,0 +1,555 @@ +import { JobDepartmentArray } from './useFetchJobs' + +// Only for dev purposes - prevents making multiple requests to the API while developing. +export const jobsPerDepartmentDummyData: JobDepartmentArray = [ + { + id: 87842, + name: 'App', + parent_id: 43806, + child_ids: [87847, 87852, 87850, 87848, 45530, 87849], + jobs: [], + }, + { + id: 54504, + name: 'Brand Design Studio', + parent_id: null, + child_ids: [], + jobs: [], + }, + { + id: 45532, + name: 'Business Development', + parent_id: null, + child_ids: [], + jobs: [], + }, + { + id: 87841, + name: 'Codex', + parent_id: 43806, + child_ids: [], + jobs: [ + { + absolute_url: 'https://jobs.status.im/?gh_jid=5329400', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2662332, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5329400, + updated_at: '2023-10-13T09:40:03-04:00', + requisition_id: 'Cod-6', + title: 'Technical Business Development Lead [Codex]', + }, + ], + }, + { + id: 84549, + name: 'Communications', + parent_id: null, + child_ids: [], + jobs: [ + { + absolute_url: 'https://jobs.status.im/?gh_jid=5276254', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2645076, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5276254, + updated_at: '2023-10-19T03:08:59-04:00', + requisition_id: 'PROV-Com-16', + title: 'Motion Designer', + }, + ], + }, + { + id: 45531, + name: 'Design', + parent_id: null, + child_ids: [], + jobs: [], + }, + { + id: 87847, + name: 'Desktop', + parent_id: 87842, + child_ids: [], + jobs: [], + }, + { + id: 87852, + name: 'Documentation', + parent_id: 87842, + child_ids: [], + jobs: [], + }, + { + id: 45547, + name: 'Engineering ', + parent_id: null, + child_ids: [], + jobs: [ + { + absolute_url: 'https://jobs.status.im/?gh_jid=5419957', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2693761, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5419957, + updated_at: '2023-10-24T07:30:00-04:00', + requisition_id: 'APP-QA-2', + title: 'Desktop QA Engineer ', + }, + { + absolute_url: 'https://jobs.status.im/?gh_jid=3694379', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2055210, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 3694379, + updated_at: '2023-10-24T16:23:12-04:00', + requisition_id: 'BACK-1050', + title: 'Senior C++ Qt/QML developer for blockchain app', + }, + { + absolute_url: 'https://jobs.status.im/?gh_jid=3702173', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2058858, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 3702173, + updated_at: '2023-10-24T16:21:54-04:00', + requisition_id: '93', + title: 'Senior Mobile ClojureScript UI Developer ', + }, + ], + }, + { + id: 49925, + name: 'Finance', + parent_id: 87845, + child_ids: [], + jobs: [], + }, + { + id: 87854, + name: 'Infrastructure', + parent_id: 43806, + child_ids: [], + jobs: [], + }, + { + id: 87853, + name: 'Insights', + parent_id: 87845, + child_ids: [], + jobs: [], + }, + { + id: 87850, + name: 'Keycard', + parent_id: 87842, + child_ids: [], + jobs: [], + }, + { + id: 145838, + name: 'Leadership', + parent_id: null, + child_ids: [], + jobs: [ + { + absolute_url: 'https://jobs.status.im/?gh_jid=5447463', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2704948, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5447463, + updated_at: '2023-10-24T09:07:22-04:00', + requisition_id: 'LEAD-1', + title: 'Chief of Staff [whole ecosystem]', + }, + ], + }, + { + id: 74156, + name: 'Legal', + parent_id: 87845, + child_ids: [], + jobs: [], + }, + { + id: 91698, + name: 'Logos', + parent_id: null, + child_ids: [], + jobs: [], + }, + { + id: 43807, + name: 'Marketing', + parent_id: null, + child_ids: [], + jobs: [], + }, + { + id: 87848, + name: 'Mobile', + parent_id: 87842, + child_ids: [], + jobs: [], + }, + { + id: 87843, + name: 'Nimbus', + parent_id: 43806, + child_ids: [], + jobs: [ + { + absolute_url: 'https://jobs.status.im/?gh_jid=5370820', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2679527, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5370820, + updated_at: '2023-10-19T03:05:07-04:00', + requisition_id: 'LIDO-1', + title: 'Senior DevOps Engineer (Blockchain)', + }, + ], + }, + { + id: 144866, + name: 'Nomos', + parent_id: 43806, + child_ids: [], + jobs: [ + { + absolute_url: 'https://jobs.status.im/?gh_jid=5433423', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2315608, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5433423, + updated_at: '2023-10-12T11:20:52-04:00', + requisition_id: 'PROV-Nom-5', + title: 'Applied Network Researcher', + }, + ], + }, + { + id: 45548, + name: 'People Operations', + parent_id: 87845, + child_ids: [], + jobs: [], + }, + { + id: 45530, + name: 'Product Design', + parent_id: 87842, + child_ids: [], + jobs: [], + }, + { + id: 90941, + name: 'Program Management', + parent_id: null, + child_ids: [], + jobs: [], + }, + { + id: 43806, + name: 'Research & Development', + parent_id: null, + child_ids: [ + 87842, 87841, 87854, 87843, 144866, 87846, 87981, 87847, 87852, 87850, + 87848, 45530, 87849, + ], + jobs: [], + }, + { + id: 87851, + name: 'Security', + parent_id: 87845, + child_ids: [], + jobs: [], + }, + { + id: 87845, + name: 'Services', + parent_id: null, + child_ids: [49925, 87853, 74156, 45548, 87851], + jobs: [], + }, + { + id: 91697, + name: 'Status App ', + parent_id: null, + child_ids: [], + jobs: [], + }, + { + id: 54783, + name: 'Technical Writing ', + parent_id: null, + child_ids: [], + jobs: [], + }, + { + id: 87846, + name: 'Vac', + parent_id: 43806, + child_ids: [], + jobs: [ + { + absolute_url: 'https://jobs.status.im/?gh_jid=4460860', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2331302, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 4460860, + updated_at: '2023-10-04T05:13:53-04:00', + requisition_id: 'PROV-zkV-1', + title: 'Zero Knowledge Research Engineer ', + }, + ], + }, + { + id: 87981, + name: 'Waku', + parent_id: 43806, + child_ids: [], + jobs: [ + { + absolute_url: 'https://jobs.status.im/?gh_jid=5456032', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2707470, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5456032, + updated_at: '2023-10-23T11:40:19-04:00', + requisition_id: 'WAK-GL-1', + title: 'Growth Lead', + }, + { + absolute_url: 'https://jobs.status.im/?gh_jid=3693623', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2055187, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 3693623, + updated_at: '2023-10-04T05:13:53-04:00', + requisition_id: 'PROV-Sec-2', + title: 'Protocol Engineer', + }, + { + absolute_url: 'https://jobs.status.im/?gh_jid=3157908', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 1830496, + location: { + name: 'Remote, Worldwide', + }, + metadata: null, + id: 3157908, + updated_at: '2023-10-04T05:13:53-04:00', + requisition_id: 'PROV-Sec-3', + title: 'Protocol Researcher (Distributed Systems)', + }, + { + absolute_url: 'https://jobs.status.im/?gh_jid=5175038', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2584916, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5175038, + updated_at: '2023-10-09T05:53:53-04:00', + requisition_id: 'SDK-2', + title: 'Software Engineer (Chat SDK)', + }, + { + absolute_url: 'https://jobs.status.im/?gh_jid=5310503', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2656108, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5310503, + updated_at: '2023-10-19T03:12:53-04:00', + requisition_id: 'PROV-Wak-13', + title: 'Software Engineer Distributed Systems Testing', + }, + { + absolute_url: 'https://jobs.status.im/?gh_jid=5423094', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2694724, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5423094, + updated_at: '2023-10-24T12:39:05-04:00', + requisition_id: 'WAK-BD-1', + title: 'Technical Business Development Lead ', + }, + ], + }, + { + id: 87849, + name: 'Web', + parent_id: 87842, + child_ids: [], + jobs: [], + }, + { + id: 0, + name: 'No Department', + parent_id: null, + child_ids: [], + jobs: [], + }, +] diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/useFetchJobs.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/useFetchJobs.ts new file mode 100644 index 0000000..570ef79 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobsPerDepartment/useFetchJobs.ts @@ -0,0 +1,161 @@ +import { useEffect, useRef, useState } from 'react' + +/* +// Here's an array with some possible job boards: +const allJobBoards = [ + 'testacidinfo', // Acid Test job board + 'logos', + 'nimbus', + 'codex', + 'status72', // Note: this job board has all the jobs from the other job boards + 'nomos', + 'thestatusnetwork', + 'instituteoffreetechnologies', // aka IFT + 'vac', + 'waku', +] as const +*/ + +export type JobDepartmentName = string + +export type JobDepartmentData = { + id: number + name: string + parent_id: number | null + child_ids: number[] + jobs: Job[] +} + +export type Job = { + absolute_url: string + data_compliance?: { + type: string + requires_consent: boolean + requires_processing_consent: boolean + requires_retention_consent: boolean + retention_period: null | string + }[] + internal_job_id: number + location: { + name: string + } + metadata?: any + id: number + updated_at: string + requisition_id: string + title: string +} + +export type JobDepartmentArray = JobDepartmentData[] | null + +type UseFetchJobsReturn = { + data: JobDepartmentArray + error: Error | null + isLoading: boolean +} + +const parseJobDepartments = ( + rawJobDepartmentData: any, + titleFilter?: string, +): JobDepartmentArray => { + const jobDepartmentArray = rawJobDepartmentData?.departments + + if (!jobDepartmentArray) { + throw Error('No departments found in rawJobDepartmentData') + } + + if (!Array.isArray(jobDepartmentArray)) { + throw Error('The received jobDepartmentArray is not an array') + } + + // Map through each department and filter its jobs based on titleFilter + const processedJobDepartmentArray: JobDepartmentData[] = + jobDepartmentArray.map((department: JobDepartmentData) => { + let filteredJobs = department.jobs + + if (titleFilter) { + filteredJobs = department.jobs.filter((job) => + job.title.includes(titleFilter), + ) + } + + return { + ...department, + jobs: filteredJobs, + } + }) + + return processedJobDepartmentArray +} + +// Fetches jobs from greenhouse via their department endpoint: +// https://developers.greenhouse.io/job-board.html#list-departments + +export const useFetchJobs = ( + jobBoardToFetch: string, + titleFilter: string = '', + dummyResponse: JobDepartmentArray = null, +): UseFetchJobsReturn => { + const [data, setData] = useState(null) + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const isMounted = useRef(true) + + const fetchJobs = async (): Promise<{ + jobsPerDepartment: JobDepartmentArray + errorData: Error | null + }> => { + try { + if (dummyResponse) { + return { jobsPerDepartment: dummyResponse, errorData: null } + } + + const response = await fetch( + `https://boards-api.greenhouse.io/v1/boards/${jobBoardToFetch}/departments`, + ) + const jobDepartmentData = await response.json() + + const jobsPerDepartment: JobDepartmentArray = parseJobDepartments( + jobDepartmentData, + titleFilter, + ) + + return { + jobsPerDepartment, + errorData: null, + } + } catch (err) { + const errorData = + err instanceof Error + ? err + : typeof err === 'string' + ? new Error(err) + : new Error('Unknown error') + + console.error(err) + return { jobsPerDepartment: null, errorData } + } + } + + useEffect(() => { + if (isLoading) return + + setIsLoading(true) + fetchJobs().then(({ jobsPerDepartment, errorData }) => { + // We should not update state if the component is unmounted. + if (!isMounted.current) return + + if (jobsPerDepartment) setData(jobsPerDepartment) + if (errorData) setError(errorData) + setIsLoading(false) + }) + + return () => { + isMounted.current = false + } + }, []) + + return { data, error, isLoading } +} + +export default useFetchJobs diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts index 24e703e..04e421d 100644 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts @@ -24,3 +24,4 @@ export * from './Showcase' export * from './ShowcaseCard' export * from './SocialCard' export * from './TimelineItem' +export * from './JobsPerDepartment'