Skip to content

Commit

Permalink
Wrap calls in /positions (#718)
Browse files Browse the repository at this point in the history
* Adding positions to the API. Deleting filter by country

* adding position/id to the api

* removing unused components

* Fix code style issues with ESLint
  • Loading branch information
camposcristian authored Sep 8, 2020
1 parent 36d9780 commit 1161a8a
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 373 deletions.
4 changes: 4 additions & 0 deletions utils/airtableLoader.js → api/airtableAPI.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const Airtable = require('airtable');

export default function handler(req, res) {
res.status(200).json();
}

export const airtableFetchRecords = async (config, filter = null, fields = null) => {
Airtable.configure({
endpointUrl: 'https://api.airtable.com',
Expand Down
28 changes: 11 additions & 17 deletions services/positions.js → api/position/[id].js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { airtableFetchRecords, airtableFetchRecord } from '../utils/airtableLoader';
import { airtableFetchRecord } from '../airtableAPI';

const config = {
baseName: process.env.REACT_APP_AIRTABLE_STAFF_RECRUITMENT_BASE,
Expand Down Expand Up @@ -28,19 +28,14 @@ const config = {
}),
};

const sitesConfig = {
baseName: 'app0cOinHqPnUCxv8',
table: 'Sites',
gridView: 'Grid view',
recordBuilder: (record) => (
{
text: record.get('Name'),
value: record.get('Name'),
}),
export default async ({ query: { id } }, res) => {
console.log(id);
const position = await findJob(id);
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(position));
};

export const loadPositions = async (filter, fields) => airtableFetchRecords(config, filter, fields);

export const findJob = async (id, currentSite = 'global') => {
const configPosition = config;
const job = await airtableFetchRecord(configPosition, id);
Expand All @@ -50,10 +45,9 @@ export const findJob = async (id, currentSite = 'global') => {

if (today < job.publish) throw new Error('This position is not available');

if (!job.availableIn.find((site) => site === currentSite.toLowerCase())) {
throw new Error('This position is not available in this location');
}
// Recruiting globally
// if (!job.availableIn.find((site) => site === currentSite.toLowerCase())) {
// throw new Error('This position is not available in this location');
// }
return job;
};

export const loadUniversities = () => airtableFetchRecords(sitesConfig).then((options) => options);
70 changes: 70 additions & 0 deletions api/positions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import compact from 'lodash/compact';
import { airtableFetchRecords, airtableFetchRecord } from './airtableAPI';
import { formatJobs } from '../utils/positions';

const config = {
baseName: process.env.REACT_APP_AIRTABLE_STAFF_RECRUITMENT_BASE,
table: 'Positions',
gridView: 'Main View',
recordBuilder: (record) => ({
id: record.id,
requiredExperience: record.get('Required Experience'),
name: record.get('Name'),
expire: record.get('Expire') ? new Date(record.get('Expire')) : false,
publish: new Date(record.get('Publish')),
embedVideo: record.get('Video Embed'),
description: record.get('Description'),
applying: record.get('Applying for position'),
availableIn: record.get('AvailableIn'),
requiredDocuments: record.get('Required Documents') || [],
jobPacks: record.get('Job Pack') || [],
type: record.get('Type'),
city: record.get('City') || '',
state: record.get('State/Province') || '',
country: record.get('Country') || '',
term: record.get('Term') || '',
messageQuestion: record.get('Message Question'),
isThereVideoLink: record.get('Video Link?'),
displayCampusSelect: record.get('University Campus?'),
salaryRange: record.get('Salary Range'),
}),
};

const buildFilter = () => {
const isExpiredIfExpire = "IF({Expire}='', TRUE(), IS_AFTER({Expire}, NOW()))";
const isPublishedIfPublish = "OR({Publish}!='', IS_BEFORE({Publish}, NOW()))";
// const countryAvailable = `OR((FIND("${state.countrySelected.toLowerCase()}", AvailableIn) > 0), (FIND("global", AvailableIn) > 0))`;
return `AND(${compact([
// state.currentFilter,
isExpiredIfExpire,
isPublishedIfPublish,
// countryAvailable,
]).join(', ')})`;
};

const fetchPositions = async () => {
const filters = buildFilter();
const loadedJobs = await loadPositions(filters, [
'Name',
'Expire',
'Description',
'City',
'State/Province',
'Country',
'Term',
'Salary Range',
'Publish',
'Type',
]);

return formatJobs(loadedJobs);
};

export default async (req, res) => {
const positions = await fetchPositions();
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(positions));
};

export const loadPositions = async (filter, fields) => airtableFetchRecords(config, filter, fields);
103 changes: 4 additions & 99 deletions components/jobs/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import React, { useState, useEffect, useRef } from 'react';
import PropType from 'prop-types';
import dynamic from 'next/dynamic';
import compact from 'lodash/compact';
import Paragraph from 'aime-blueprint/lib/components/paragraph';
import Title from 'aime-blueprint/lib/components/title';
import { loadPositions } from '../../services/positions';
import { formatJobs } from '../../utils/positions';
import { COUNTRIES_WHERE_AIME_ACCEPT_JOBS } from '../../constants';
import { getAllCountries } from '../../utils/country';
import { sortArrayOfObjectByField } from '../../utils/sorting';
import isClientSide from '../../utils/isClientSide';

Expand All @@ -28,45 +23,12 @@ const Jobs = ({
filtersType: {},
currentFilter: undefined,
jobsLoaded: false,
countrySelected: 'global',
countries: getAllCountries().map((country) => ({
value: country.code,
label: country.name,
})).filter(
(country) => COUNTRIES_WHERE_AIME_ACCEPT_JOBS.indexOf(country.value) !== -1,
),
});

const redirectRef = useRef(null);

const buildFilter = () => {
const isExpiredIfExpire = "IF({Expire}='', TRUE(), IS_AFTER({Expire}, NOW()))";
const isPublishedIfPublish = "OR({Publish}!='', IS_BEFORE({Publish}, NOW()))";
const countryAvailable = `OR((FIND("${state.countrySelected.toLowerCase()}", AvailableIn) > 0), (FIND("global", AvailableIn) > 0))`;
return `AND(${compact([
state.currentFilter,
isExpiredIfExpire,
isPublishedIfPublish,
countryAvailable,
]).join(', ')})`;
};

const fetchPositions = async () => {
const filters = buildFilter();
const loadedJobs = await loadPositions(filters, [
'Name',
'Expire',
'Description',
'City',
'State/Province',
'Country',
'Term',
'Salary Range',
'Publish',
'Type',
]);
const jobs = formatJobs(loadedJobs);

const jobs = await fetch('/api/positions').then((res) => res.json());
setState({
...state,
jobs,
Expand All @@ -91,62 +53,17 @@ const Jobs = ({
}
}, [state.jobsLoaded, isRedirect]);

const handleCountryChange = (propertyName, propertyValue) => {
setState({
...state,
[propertyName]: propertyValue,
});
};

const getFilteredJobs = () => {
const jobsWithCurrentFilter = state.currentFilter
? state.jobs.filter((job) => job.type === state.currentFilter)
: state.jobs;
if (state.countrySelected !== 'global') {
const countryObject = state.countries.find(
(country) => state.countrySelected === country.value,
);
if (countryObject) {
return jobsWithCurrentFilter.filter(
(job) => job.country === countryObject.label || !job.country,
);
}
}
let sortedJobs = jobsWithCurrentFilter;
if (jobsWithCurrentFilter.length > 0) {
sortedJobs = sortArrayOfObjectByField(jobsWithCurrentFilter, 'publish');
}
return sortedJobs;
};

const getStylesForCountrySelection = () => ({
control: {
background: backgroundColor,
minWidth: '232px',
},
input: {
color: '#7603DB',
},
singleValue: {
color: '#7603DB',
position: 'initial',
overflow: 'inherit',
top: 'inherit',
transform: 'inherit',
maxWidth: 'inherit',
},
menu: {
borderRadius: 0,
marginTop: '0px',
width: '100%',
zIndex: 10000,
},
menuList: {
zIndex: 10000,
padding: 0,
},
});

const setFilter = (currentFilter) => {
setState({
...state,
Expand All @@ -173,18 +90,6 @@ const Jobs = ({
filterBy={setFilter}
filtersType={state.filtersType}
/>
<div>
<Select
placeholder="Select your country"
name="countrySelected"
onChangeFunction={handleCountryChange}
value={state.countrySelected}
backgroundColor="#FFFF"
borderColor="#FFFF"
options={state.countries}
styles={getStylesForCountrySelection()}
/>
</div>
</div>
<div className={styles.jobGrid}>
{isRedirect && (
Expand All @@ -202,11 +107,11 @@ const Jobs = ({
) : (
<div className={styles.noJobContainer}>
<Title type="h4Title" theme={process.env.REACT_APP_THEME}>
We are not hiring.
We are not hiring.
</Title>
<span className={styles.titleNoJobContainer} />
<Paragraph className={styles.paragraphNoJobContainer}>
Sorry, there are no positions available at the moment.
Sorry, there are no positions available at the moment.
</Paragraph>
<Paragraph>
{`You can sign up to be an AIME Friend at the bottom of this page though –
Expand All @@ -231,7 +136,7 @@ Jobs.propTypes = {
Jobs.defaultProps = {
backgroundColor: '#FFF',
isRedirect: false,
handleRedirectHide: () => {},
handleRedirectHide: () => { },
};

export default Jobs;
94 changes: 0 additions & 94 deletions components/positions/jobUniAndContactFrom.js

This file was deleted.

Loading

2 comments on commit 1161a8a

@vercel
Copy link

@vercel vercel bot commented on 1161a8a Sep 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@camposcristian is attempting to deploy this commit to the AIME Mentoring Team on Vercel.

In order for a Deployment to be created, @camposcristian needs to request access to the Team.

Afterwards, an owner of the Team is required to accept their membership request.

@aime-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size Checks (0 failures)

Size Checks

Label Value Threshold Success
out/favicon.ico 2.09 KB 4 KB
out/index.html 60.44 KB 80 KB
out/service-worker.js 13.25 KB 15 KB
out 8.98 MB 70 MB
.next/service-worker.js 13.25 KB 15 KB

Heap Snapshot Checks (0 failures)

Heap Snapshot Checks

Label Value Threshold Success
Documents 3 11
Frames 1 5
JSHeapTotalSize 3657728 21889024
JSHeapUsedSize 2294952 13685832
LayoutCount 9 35
Nodes 718 1250
RecalcStyleCount 3 129

Axe Audits (0 failures)

Axe Audits

Label Value Threshold Success
aria-allowed-attr critical
aria-allowed-role none
aria-hidden-body none
aria-required-attr none
aria-required-children none
aria-required-parent none
aria-roles none
aria-valid-attr-value none
aria-valid-attr none
avoid-inline-spacing none
button-name none
bypass none
color-contrast serious serious
document-title none
duplicate-id-active none
duplicate-id minor minor
empty-heading none
form-field-multiple-labels none
heading-order moderate moderate
html-has-lang none
html-lang-valid none
image-alt none
image-redundant-alt none
label-title-only none
landmark-banner-is-top-level none
landmark-contentinfo-is-top-level none
landmark-main-is-top-level none
landmark-no-duplicate-banner none
landmark-no-duplicate-contentinfo none
landmark-one-main moderate
landmark-unique moderate moderate
link-name serious
list serious serious
listitem none
meta-viewport-large none
meta-viewport critical
page-has-heading-one moderate
duplicate-id minor minor
heading-order moderate moderate
label critical critical
landmark-unique moderate moderate
list serious serious
region moderate moderate

Lighthouse Audits (0 failures)

Lighthouse Audits

Label Value Threshold Success
Performance 84 80
Accessibility 90 80
Best Practices 85 80
SEO 94 85
Progressive Web App 68 60

Please sign in to comment.