Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/truss #23

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,47 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Hi!
Thank you for taking time to look at this work sample. This app is my personal site with the work sample added as a page at /truss.


## Getting Started
First, clone the repo and navigate to the root of the project.

Then, install the project dependencies:

```bash
npm install
# or
yarn
```

First, run the development server:
Next, run the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
Checkout the branch that this feature is being developed on:
```bash
git checkout feature/truss
```


Finally, navigate to [http://localhost:3000/truss](http://localhost:3000/truss) to see the work sample. There is a navigation link in the header.

You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
## Files to look at
The files to be concerned with are `pages/truss.js` (displaying the table of planet data) and `hooks/useFetch.js` (abstracting the fetch into a custom hook to help with loading and error states).

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
(Please note the other pages in the app will not work without the api key environment variables. See briandridge.com if you are curious).

## Learn More
## Next.js resources

The app is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

## Weird stuff to know

In Husky v5 git pre-commit hooks do not get created on install. Downgraded specifically to version 4.2.3 to make them work.
See this issue: https://github.com/typicode/husky/issues/326#issuecomment-769309475
27 changes: 0 additions & 27 deletions career-goals.md

This file was deleted.

4 changes: 2 additions & 2 deletions components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ const Header = () => {
Resume
</a>
</Link>
<Link href="/blog">
<Link href="/truss">
<a sx={{ px: 3, cursor: 'pointer', fontFamily: 'Montserrat' }}>
Blog
Truss Work Sample
</a>
</Link>
<a
Expand Down
40 changes: 0 additions & 40 deletions design-systems.md

This file was deleted.

61 changes: 61 additions & 0 deletions hooks/useFetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useEffect, useState } from 'react'

// recursive function to get all paginated swapi planet data
async function getAllPlanetData() {
// Get the a page of results
const getPlanetPage = url => fetch(url).then(res => res.json())

// Recursively get planet pages until data.next is null and all the pages are fetched, collect pages into a single array
const getAllPlanetPages = async (url, planetCollection = []) => {
const { results, next } = await getPlanetPage(url)
planetCollection = [...planetCollection, ...results]
if (next !== null) {
return getAllPlanetPages(next, planetCollection)
}
return planetCollection
}

// all planet data
const rawPlanets = await getAllPlanetPages('https://swapi.dev/api/planets/')

// alphabetically sort the collection of all planet data
const sortedPlanets = rawPlanets.sort((first, second) => {
const firstPlanet = first.name.toUpperCase()
const secondPlanet = second.name.toUpperCase()
return firstPlanet < secondPlanet ? -1 : firstPlanet > secondPlanet ? 1 : 0
})
return sortedPlanets
}

// custom hook to fetch data and provide loading, data, and error
export const useFetch = url => {
const [loading, setLoading] = useState(false)
const [data, setData] = useState([])
const [error, setError] = useState(null)

useEffect(() => {
let isStale = false
setLoading(true)

fetch(url)
.then(res => res.json())
.then(data => {
if (!isStale) {
setData(data)
setError(null)
setLoading(false)
}
})
.catch(error => {
console.warn(error.message)
setError(error)
setLoading(false)
})

return () => {
isStale = true
}
}, [url])

return { loading, data, error }
}
137 changes: 137 additions & 0 deletions pages/truss.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/** @jsxRuntime classic /
/* @jsx jsx */
import { jsx, Flex, Spinner } from 'theme-ui'
import Layout from '@components/Layout'
import { useFetch } from '../hooks/useFetch'

// take an integer and format with a space at every group of 3 (thousand)
const formatNumber = number =>
number?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ') // https://stackoverflow.com/questions/16637051/adding-space-between-numbers

// display a "?" in table if values are unknown
const formatUnknown = value => (value === 'unknown' ? '?' : value)

// calculate the surface area of a planet that is covered by water
const calculateSurfaceAreaCoveredByWater = (
planetDiameter,
percentageCoveredByWater
) => {
const planetRadius = planetDiameter / 2
const totalSurfaceArea = 4 * Math.PI * Math.pow(planetRadius, 2) // use this calc to confirm: https://www.calculatorsoup.com/calculators/geometry-solids/sphere.php
const areaCoveredByWater = totalSurfaceArea * (percentageCoveredByWater / 100)
return areaCoveredByWater ? formatNumber(Math.round(areaCoveredByWater)) : '?'
}

// display planet data in a table (with loading and error states)
const Planets = () => {
const { loading, data, error } = useFetch('https://swapi.dev/api/planets/')

// alphabetically sort the planet data
const sortedPlanets = data.results?.sort((first, second) => {
const firstPlanet = first.name.toUpperCase()
const secondPlanet = second.name.toUpperCase()
return firstPlanet < secondPlanet ? -1 : firstPlanet > secondPlanet ? 1 : 0
})

// if data is loading show a spinner
if (loading) {
return <Spinner sx={{ width: '100%' }} />
}

// if error, show the error message
if (error) {
return (
<div sx={{ m: '24px auto' }}>
<div>There was a problem getting the data.</div>
<div>
Error: {error.name} - {error.message}
</div>
</div>
)
}

// otherwise return the table with the data
return (
<table
sx={{
borderCollapse: 'collapse',
border: '1px solid gray',
mb: 4,
td: { border: '1px solid gray', p: 1 },
th: { border: '1px solid gray', p: 1 },
}}
>
<thead>
<tr>
<th>Planet</th>
<th>Climate</th>
<th>Residents</th>
<th>Terrain</th>
<th>Population</th>
<th>Surface area water</th>
</tr>
</thead>
<tbody>
{sortedPlanets?.map(planet => (
<tr key={planet.name}>
<td>
<a
href={planet.url}
rel="noreferrer"
sx={{
px: 3,
cursor: 'pointer',
':hover': {
color: 'rebeccapurple',
},
}}
target="_blank"
>
{formatUnknown(planet.name)}
</a>
</td>
<td>{formatUnknown(planet.climate)}</td>
<td>{planet.residents?.length}</td>
<td>{formatUnknown(planet.terrain)}</td>
<td>{formatUnknown(formatNumber(planet.population))}</td>
<td>
{calculateSurfaceAreaCoveredByWater(
planet.diameter,
planet.surface_water
)}
</td>
</tr>
))}
</tbody>
</table>
)
}

// page component that provides header, nav, and layout
const TrussWorkSample = () => (
<Layout pageHeading="Truss work sample" title="Truss">
<Flex sx={{ pb: 3 }}>
This is my solution to the requested
<a
href={
'https://github.com/trussworks/truss-interview/blob/main/BROWSER_README.md'
}
rel="noreferrer"
sx={{
px: 1,
cursor: 'pointer',
':hover': {
color: 'rebeccapurple',
},
}}
target="_blank"
>
work sample.
</a>
Displaying planet data from the Star Wars API.
</Flex>
<Planets />
</Layout>
)

export default TrussWorkSample