It's a site

This commit is contained in:
Wes Bos 2020-01-07 15:23:53 -05:00
parent 8dde1405ba
commit 14c9b52729
20 changed files with 18350 additions and 1 deletions

71
.gitignore vendored
View file

@ -1,2 +1,71 @@
.idea
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# dotenv environment variable files
.env*
# gatsby files
.cache/
public
# Mac files
.DS_Store
# Yarn
yarn-error.log
.pnp/
.pnp.js
# Yarn Integrity file
.yarn-integrity
haters/

3
README.md Normal file
View file

@ -0,0 +1,3 @@
## /uses website
Add yourself [here](./src/data.js)

6
gatsby-browser.js Normal file
View file

@ -0,0 +1,6 @@
import React from 'react';
import { FilterProvider } from './src/context/FilterContext';
export const wrapRootElement = ({ element }) => (
<FilterProvider>{element}</FilterProvider>
)

30
gatsby-config.js Normal file
View file

@ -0,0 +1,30 @@
module.exports = {
siteMetadata: {
title: `/uses`,
description: `A list of /uses pages detailing developer setups.`,
author: `@wesbos`,
},
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
],
};

60
gatsby-node.js Normal file
View file

@ -0,0 +1,60 @@
import people from './src/data.js';
import { tags, countries } from './src/util/stats';
function sourceNodes({ actions, createNodeId, createContentDigest }) {
// Add People to the GraphQL API, we randomize the data on each build so no one gets their feelings hurt
people
.sort(() => Math.random() - 0.5)
.forEach(person => {
const nodeMeta = {
id: createNodeId(`person-${person.name}`),
parent: null,
children: [],
internal: {
type: `Person`,
mediaType: `text/html`,
content: JSON.stringify(person),
contentDigest: createContentDigest(person),
},
};
actions.createNode({ ...person, ...nodeMeta });
});
// Add tags to GraphQL API
tags().forEach(tag => {
const nodeMeta = {
id: createNodeId(`tag-${tag.name}`),
parent: null,
children: [],
internal: {
type: `Tag`,
mediaType: `text/html`,
content: JSON.stringify(tag),
contentDigest: createContentDigest(tag),
},
};
actions.createNode({ ...tag, ...nodeMeta });
});
console.log(countries());
// Add Countries to GraphQL API
countries().forEach(country => {
const nodeMeta = {
id: createNodeId(`country-${country.name}`),
parent: null,
children: [],
internal: {
type: `Country`,
mediaType: `text/html`,
content: JSON.stringify(country),
contentDigest: createContentDigest(country),
},
};
actions.createNode({ ...country, ...nodeMeta });
});
}
export { sourceNodes };

17250
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

50
package.json Normal file
View file

@ -0,0 +1,50 @@
{
"name": "uses",
"description": "What do you uses",
"version": "7.7.7",
"author": "Wes Bos",
"eslintConfig": {
"extends": [
"wesbos"
]
},
"dependencies": {
"country-emoji": "^1.5.0",
"esm": "^3.2.25",
"gatsby": "^2.18.12",
"gatsby-image": "^2.2.34",
"gatsby-plugin-manifest": "^2.2.31",
"gatsby-plugin-offline": "^3.0.27",
"gatsby-plugin-react-helmet": "^3.1.16",
"gatsby-plugin-sharp": "^2.3.5",
"gatsby-source-filesystem": "^2.1.40",
"gatsby-transformer-sharp": "^2.3.7",
"normalize.css": "^8.0.1",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-helmet": "^5.2.1",
"styled-components": "^4.4.1"
},
"scripts": {
"build": "npx --node-arg '-r esm' gatsby build",
"develop": "npx --node-arg '-r esm' gatsby develop",
"start": "npm run develop",
"serve": "npx --node-arg '-r esm' gatsby serve",
"clean": "gatsby clean"
},
"devDependencies": {
"babel-eslint": "^9.0.0",
"eslint": "^5.16.0",
"eslint-config-airbnb": "^17.1.1",
"eslint-config-prettier": "^4.3.0",
"eslint-config-wesbos": "0.0.19",
"eslint-plugin-html": "^5.0.5",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-react-hooks": "^1.7.0",
"prettier": "^1.19.1"
}
}

69
src/components/Person.js Normal file
View file

@ -0,0 +1,69 @@
import React from 'react';
import { name } from 'country-emoji';
import iphone from '../../public/icons/iphone.png';
import android from '../../public/icons/android.png';
import windows from '../../public/icons/windows.svg';
import apple from '../../public/icons/apple.svg';
const icons = { iphone, android, windows, apple };
export default function Person({ person, currentTag }) {
const url = new URL(person.url);
const img = `https://logo.clearbit.com/${url.host}`;
return (
<div className="person">
<div className="personInner">
<img width="50" src={img} alt={person.name} />
<h3>
<a href={person.url} target="_blank" rel="noopener noreferrer">
{person.name} {person.emoji}
</a>
</h3>
<a
className="displayLink"
href={person.url}
>{`${url.host}${url.pathname}`}</a>
<p>{person.description}</p>
<ul className="tags">
{person.tags.map(tag => (
<li
key={tag}
className={`tag ${tag === currentTag ? 'currentTag' : ''}`}
>
{tag}
</li>
))}
</ul>
</div>
<div className="deets">
<span className="country" title={name(person.country)}>
{person.country}
</span>
{person.computer && (
<span title={`Computer: ${person.computer}`}>
<img
height="40"
src={icons[person.computer]}
alt={person.computer}
/>
</span>
)}
{person.phone && (
<span title={`Uses an ${person.phone}`}>
<img height="50" src={icons[person.phone]} alt={person.phone} />
</span>
)}
{person.twitter && (
<span>
<a href={`https://twitter.com/${person.twitter}`}>
<span className="at">@</span>
{person.twitter.replace('@', '')}
</a>
</span>
)}
{person.github && <span>{person.github}</span>}
</div>
</div>
);
}

51
src/components/Topics.js Normal file
View file

@ -0,0 +1,51 @@
import React, { useContext } from 'react';
import FilterContext from '../context/FilterContext';
export default function Topics() {
const { countries, tags, currentTag, setCurrentTag } = useContext(
FilterContext
);
console.log(countries);
return (
<div className="tags">
{tags.map(tag => (
<label
className={`tag ${tag.name === currentTag ? 'currentTag' : ''}`}
htmlFor={`filter-${tag.name}`}
key={`filter-${tag.name}`}
>
<input
type="radio"
name="tag"
id={`filter-${tag.name}`}
value={tag.name}
checked={tag.name === currentTag}
onChange={e => setCurrentTag(e.currentTarget.value)}
/>
{tag.name}
<span className="count">{tag.count}</span>
</label>
))}
{countries.map(tag => (
<label
className={`tag ${tag.emoji === currentTag ? 'currentTag' : ''}`}
htmlFor={`filter-${tag.name}`}
key={`filter-${tag.name}`}
title={tag.name}
>
<input
type="radio"
name="tag"
id={`filter-${tag.name}`}
value={tag.emoji}
checked={tag.emoji === currentTag}
onChange={e => setCurrentTag(e.currentTarget.value)}
/>
<span className="emoji">{tag.emoji}</span>
<span className="count">{tag.count}</span>
</label>
))}
</div>
);
}

26
src/components/header.js Normal file
View file

@ -0,0 +1,26 @@
import { Link } from 'gatsby';
import PropTypes from 'prop-types';
import React from 'react';
const Header = ({ siteTitle }) => (
<header className="header">
<div>
<h1>
<Link to="/">/uses</Link>
</h1>
<p>
A list of <code>/uses</code> pages detailing developer setups.
</p>
</div>
</header>
);
Header.propTypes = {
siteTitle: PropTypes.string,
};
Header.defaultProps = {
siteTitle: ``,
};
export default Header;

32
src/components/image.js Normal file
View file

@ -0,0 +1,32 @@
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
/*
* This component is built using `gatsby-image` to automatically serve optimized
* images with lazy loading and reduced file sizes. The image is loaded using a
* `useStaticQuery`, which allows us to load the image from directly within this
* component, rather than having to pass the image data down from pages.
*
* For more information, see the docs:
* - `gatsby-image`: https://gatsby.dev/gatsby-image
* - `useStaticQuery`: https://www.gatsbyjs.org/docs/use-static-query/
*/
const Image = () => {
const data = useStaticQuery(graphql`
query {
placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
childImageSharp {
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
}
`)
return <Img fluid={data.placeholderImage.childImageSharp.fluid} />
}
export default Image

184
src/components/layout.css Normal file
View file

@ -0,0 +1,184 @@
html {
--purple: #1e1f5c;
--blue: #203447;
--lightblue: #1f4662;
--blue2: #1C2F40;
--yellow: #ffc600;
--pink: #EB4471;
--vape: #d7d7d7;
background: var(--blue);
color: var(--vape);
font-family: 'Fira Mono';
font-weight: 100;
font-size: 10px;
}
::selection {
background: var(--yellow);
color: var(--blue);
}
body {
font-size: 2rem;
}
h1,h2,h3,h4,h5,h6 {
font-weight: 500;
}
h1 a {
text-align: center;
}
a {
color: var(--yellow);
text-decoration-color: var(--pink);
font-style: italic;
}
.site {
max-width: 1900px;
margin: 5rem;
}
.header {
text-align: center;
}
.header h1 {
font-size: 6rem;
}
main {
display: grid;
grid-gap: 3rem;
max-width: 1900px;
padding: 0 3rem;
margin: 5rem auto;
}
.people {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
grid-gap: 5rem;
}
.person {
border: 1px solid var(--vape);
border-radius: 5.34334px;
box-shadow: 10px -10px 0 var(--blue2);
display: grid;
grid-template-rows: 1fr auto auto;
}
.person h3 {
margin: 0;
}
.personInner {
padding: 2rem;
}
.person .tag {
font-size: 1.2rem;
}
.name {
display: block;
}
.name.last {
font-size: 5rem;
}
.deets {
display: flex;
border-block-start: 1px solid var(--vape);
}
.deets > * {
flex: 1;
border-inline-start: 1px solid var(--vape);
text-align: center;
padding: 1rem;
display: grid;
align-items: center;
justify-content: center;
grid-template-columns: auto auto;
}
.deets a {
color: var(--vape);
}
.deets .at {
color: var(--yellow);
margin-right: 2px;
}
.deets :first-child {
border-inline-start: none;
}
.deets .country {
font-size: 3rem;
}
.deets .phone {
padding: 0;
}
.displayLink {
text-decoration: none;
color: var(--vape);
letter-spacing: 1px;
font-size: 1.2rem;
}
.displayLink:hover {
color: var(--pink);
}
.tags {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
}
.tag {
background: var(--pink);
margin: 2px;
border-radius: 3px;
font-size: 1.7rem;
padding: 5px;
color: hsla(0, 100%, 100%, 0.8);
transition: background-color 0.2s;
cursor: pointer;
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
}
.tag input { display: none; }
.tag.currentTag {
background: var(--yellow);
color: hsla(0, 100%, 0%, 0.8);
}
.tag .emoji {
text-shadow: 0 0 1px 2px rgba(0,0,0,0.3);
}
.tag .count {
background: var(--blue);
font-size: 1rem;
color: white;
padding: 2px;
border-radius: 2px;
margin-left: 5px;
}

46
src/components/layout.js Normal file
View file

@ -0,0 +1,46 @@
/**
* Layout component that queries for data
* with Gatsby's useStaticQuery component
*
* See: https://www.gatsbyjs.org/docs/use-static-query/
*/
import React from 'react';
import PropTypes from 'prop-types';
import { useStaticQuery, graphql } from 'gatsby';
import Header from './header';
import 'normalize.css';
import './layout.css';
const Layout = ({ children }) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`);
return (
<>
<Header siteTitle={data.site.siteMetadata.title} />
<main>
{children}
<footer>
© {new Date().getFullYear() - Math.floor(Math.random() * 777)} Made by{' '}
<a href="https://wesbos.com">Wes Bos</a> with{' '}
<a href="https://www.gatsbyjs.org">Gatsby</a>. Icons from icons8.com.
</footer>
</main>
</>
);
};
Layout.propTypes = {
children: PropTypes.node.isRequired,
};
export default Layout;

View file

@ -0,0 +1,41 @@
import React, { createContext, useState } from 'react';
import { useStaticQuery, graphql } from 'gatsby';
const FilterContext = createContext();
const FilterProvider = function({ children }) {
const [currentTag, setCurrentTag] = useState('all');
const { allTag, allCountry } = useStaticQuery(graphql`
query FilterQuery {
allTag {
nodes {
name
count
}
}
allCountry {
nodes {
count
emoji
name
}
}
}
`);
return (
<FilterContext.Provider
value={{
tags: allTag.nodes,
countries: allCountry.nodes,
currentTag,
setCurrentTag,
}}
>
{children}
</FilterContext.Provider>
);
};
export default FilterContext;
export { FilterProvider };

323
src/data.js Normal file
View file

@ -0,0 +1,323 @@
// Add yourself. Insert an object at any point - it doesn't matter if you go before someone else as results are randomized.
// please remove the comments before You PR
const pages = [
{
name: 'Wes Bos',
// Short description
description:
'Maker of this site. Web Developer, Tutorial Maker, Podcaster, BBQ Lover',
// URL to your /uses page
url: 'https://wesbos.com/uses',
twitter: '@wesbos',
// An emoji that describes you
emoji: '🔥',
// emoji of your country's flag
country: '🇨🇦',
// ONE of: apple, windows or linux
computer: 'apple',
// iphone or android
phone: 'iphone',
// Tags - You can add your own, but please keep it to one word. "Social vape entrepreneur influencer denver" isn't a tag.
// Dev Tags: Engineer, Developer, Designer, Front End, Back End, Full Stack,
// Other: Tags: Entrepreneur, Teacher, Podcaster, YouTuber, Blogger, Speaker,
// Language Tags: JavaScript, PHP, Rails, Ruby, TypeScript...
tags: [
'Developer',
'Full Stack',
'Entrepreneur',
'Teacher',
'YouTuber',
'JavaScript',
],
},
{
name: 'Troy Forster',
description: 'Consulting Technology Director and CTO for Hire',
url: 'https://tforster.com/uses',
twitter: '@tforster',
emoji: '',
country: '🇨🇦',
computer: 'windows',
phone: 'android',
tags: [
'Engineer',
'Back End',
'Front End',
'Consultant',
'Entrepreneur',
'JavaScript',
'C#',
'PHP',
'Serverless',
'SOA',
'Enterprise',
],
},
{
name: 'Kent C. Dodds',
description: 'JavaScript Software Engineer, speaker, and trainer',
url: 'https://kentcdodds.com/uses',
emoji: '🙌',
country: '🇺🇸',
computer: 'apple',
phone: 'android',
tags: [
'Developer',
'Full Stack',
'Entrepreneur',
'Teacher',
'YouTuber',
'JavaScript',
'Testing',
'React',
'Speaker',
'Blogger',
],
},
{
name: 'Glenn Reyes',
description:
'Independent Software Engineer, trainer & speaker. Into sports & music.',
url: 'https://glennreyes.com/uses',
emoji: '💃',
country: '🇺🇸',
computer: 'apple',
phone: 'iphone',
tags: [
'Developer',
'Front End',
'Entrepreneur',
'Teacher',
'JavaScript',
'React',
'GraphQL',
'TypeScript',
'Speaker',
],
},
{
name: 'Adam Jahnke',
description:
'Caffiend, motorcyclist, climber, recovering perfectionist. I love to make the complex simple.',
url: 'https://adamyonk.com/uses',
emoji: '',
country: '🇺🇸',
computer: 'apple',
phone: 'iphone',
tags: ['Engineer', 'Full Stack', 'JavaScript', 'Ruby'],
},
{
name: 'Andrew Healey',
description: 'Software Engineer, Writer, Learner!',
url: 'https://healeycodes.com/uses',
emoji: '🦉',
country: '🇬🇧',
computer: 'apple',
phone: 'iphone',
tags: ['Software Engineer', 'Full Stack', 'JavaScript', 'Python', 'Writer'],
},
{
name: 'Scott Tolinski',
description: 'Web Developer, Tutorial Maker, Podcaster, Bboy',
url: 'https://kit.com/leveluptutorials/podcasting-screencasting-gear',
emoji: '💪🏻',
country: '🇺🇸',
computer: 'apple',
phone: 'iphone',
tags: ['Developer', 'FrontEnd', 'Entrepreneur', 'Teacher', 'JavaScript'],
},
{
name: 'Josiah Wiebe',
description: 'Designer & developer, lifelong learner.',
url: 'https://jwie.be/uses/',
emoji: '🚴',
country: '🇨🇦',
computer: 'apple',
phone: 'iphone',
tags: [
'Full Stack',
'Developer',
'Designer',
'Python',
'JavaScript',
'TypeScript',
],
},
{
name: 'Benjamin Lannon',
description: 'Web Developer, Open Source Contributor, Livestreamer',
url: 'https://lannonbr.com/uses/',
emoji: '🎤',
country: '🇺🇸',
computer: 'apple',
phone: 'iphone',
tags: [
'Developer',
'Full Stack',
'Blogger',
'Teacher',
'JavaScript',
'GraphQL',
],
},
{
name: 'Nuno Maduro',
description: 'Software engineer, Open Source contributor, Speaker',
url: 'https://nunomaduro.com/uses/',
emoji: '🏄‍♂️',
country: '🇵🇹',
computer: 'apple',
phone: 'iphone',
tags: [
'Engineer',
'Developer',
'Speaker',
'PHP',
'JavaScript',
'TypeScript',
],
},
{
name: 'Adrian Marin',
description:
'Product-Minded Software Engineer, Digital nomad, no-nonsense enjoyer of life, friends and family.',
url: 'https://adrianthedev.com/uses',
twitter: '@adrianthedev',
emoji: '🥑',
country: '🇷🇴',
computer: 'apple',
phone: 'iphone',
tags: ['Developer', 'Full Stack', 'Entrepreneur', 'Rails', 'TypeScript'],
},
{
name: 'Jahir Fiquitiva',
description: 'Passionate and Creative Full Stack Developer',
url: 'https://jahir.dev/uses',
twitter: '@jahirfiquitiva',
emoji: '💎',
country: '🇨🇴',
computer: 'apple',
phone: 'iphone',
tags: [
'Developer',
'Full Stack',
'JavaScript',
'Kotlin',
'Python',
'Kotlin',
'React',
],
},
{
name: 'Brad Garropy',
description:
'Self taught frontender at Adobe, into lifting and country music.',
url: 'https://bradgarropy.com/uses',
twitter: '@bradgarropy',
emoji: '🤠',
country: '🇺🇸',
computer: 'windows',
phone: 'android',
tags: [
'Developer',
'Front End',
'Full Stack',
'Streamer',
'YouTuber',
'Blogger',
'JavaScript',
'Python',
],
},
{
name: 'Josh Barker',
description:
'Front end engineer at Red Ventures. Soccer enthusiast. Lover of stories.',
url: 'https://joshuabarker.com/uses',
twitter: '@joshuafbarker',
emoji: '⚽️',
country: '🇺🇸',
computer: 'apple',
phone: 'iphone',
tags: ['Developer', 'Front End', 'JavaScript'],
},
{
name: 'Aaron Dunphy',
description: 'Full Stack Developer, Coffee Lover and Photo Taker',
url: 'https://aarondunphy.com/uses',
twitter: '@aaronjohndunphy',
emoji: '📷',
country: '🇬🇧',
computer: 'apple',
phone: 'iphone',
tags: [
'Developer',
'Full Stack',
'Back End',
'Laravel',
'PHP',
'JavaScript',
],
},
{
name: 'Mohamed Benhida',
description: 'Web Developer, Open source contributor.',
url: 'http://mohamedbenhida.com/uses',
twitter: '@simo_benhida',
emoji: '🔥',
country: '🇲🇦',
computer: 'apple',
phone: 'iphone',
tags: [
'Developer',
'Full Stack',
'Entrepreneur',
'Teacher',
'Back End',
'Laravel',
'Vuejs',
'Tailwindcss',
'PHP',
'JavaScript',
],
},
{
name: 'Andrew McCombe',
// Short description
description:
'Experienced full stack web developer with a passion for testing.',
url: 'https://www.euperia.com/uses',
twitter: '@euperia',
emoji: '🏁',
country: '🇬🇧',
computer: 'apple',
phone: 'android',
tags: [
'Developer',
'Full Stack',
'Back End',
'Laravel',
'PHP',
'JavaScript',
'Vue',
'LAMP',
'ElasticSearch',
'AWS',
],
},
{
name: 'Smakosh',
description: 'Full stack JavaScript Developer, blogger and speaker.',
url: 'https://smakosh.com/the-tech-tools-I-use',
twitter: '@smakosh',
emoji: '👌',
country: '🇲🇦',
computer: 'apple',
phone: 'android & iphone',
tags: ['Developer', 'Full Stack', 'Entrepreneur', 'Blogger', 'JavaScript'],
},
];
export default pages;

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
src/images/gatsby-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

11
src/pages/404.js Normal file
View file

@ -0,0 +1,11 @@
import React from 'react';
import Layout from '../components/layout';
const NotFoundPage = () => (
<Layout>
<p>WHAT R U DOING HERE</p>
</Layout>
);
export default NotFoundPage;

47
src/pages/index.js Normal file
View file

@ -0,0 +1,47 @@
import React, { useContext } from 'react';
import { useStaticQuery, graphql } from 'gatsby';
import FilterContext from '../context/FilterContext';
import Layout from '../components/layout';
import Person from '../components/Person';
import Topics from '../components/Topics';
function IndexPage() {
const { currentTag } = useContext(FilterContext);
const { allPerson } = useStaticQuery(graphql`
query People {
allPerson {
nodes {
computer
country
description
emoji
id
name
phone
tags
twitter
url
}
}
}
`);
const people = allPerson.nodes.filter(
person =>
currentTag === 'all' ||
person.tags.includes(currentTag) ||
currentTag === person.country
);
return (
<Layout>
<Topics />
<div className="people">
{people.map(person => (
<Person key={person.name} person={person} currentTag={currentTag} />
))}
</div>
</Layout>
);
}
export default IndexPage;

51
src/util/stats.js Normal file
View file

@ -0,0 +1,51 @@
import { name } from 'country-emoji';
import people from '../data.js';
function merge(prop) {
return function(acc, obj) {
return [...obj[prop], ...acc];
};
}
function countInstances(acc, tag) {
acc[tag] = acc[tag] ? acc[tag] + 1 : 1;
return acc;
}
export function countries() {
const data = people
.map(person => ({
name: name(person.country),
emoji: person.country,
}))
.reduce((acc, country) => {
if (acc[country.name]) {
// exists, update
acc[country.name].count = acc[country.name].count + 1;
} else {
acc[country.name] = {
...country,
count: 1,
};
}
return acc;
}, {});
const sorted = Object.entries(data)
.map(([, country]) => country)
.sort((a, b) => b.count - a.count);
return sorted;
}
export function tags() {
const allTags = people.reduce(merge('tags'), []);
const counts = allTags.reduce(countInstances, {});
// sort and filter for any tags that only have 1
const tags = Object.entries(counts)
.sort(([, countA], [, countB]) => countB - countA)
.filter(([, count]) => count > 1)
.map(([name, count]) => ({ name, count }));
return [{ name: 'all', count: people.length }, ...tags];
}