├── .env ├── src ├── components │ ├── AdDetail │ │ ├── AdDetail.module.css │ │ └── index.jsx │ ├── Tools │ │ ├── Tools.module.css │ │ └── index.jsx │ ├── Login │ │ ├── Login.module.css │ │ └── index.jsx │ ├── Ad │ │ ├── Ad.module.css │ │ ├── AdDetails │ │ │ ├── AdDetails.module.css │ │ │ └── index.jsx │ │ ├── index.jsx │ │ └── fb_ad.scss │ ├── AdSearch │ │ ├── AdSearch.module.css │ │ ├── index.jsx │ │ └── sample_ad.json │ ├── Targets │ │ ├── Targets.module.css │ │ └── index.jsx │ ├── Credits │ │ └── index.jsx │ ├── Layout │ │ ├── Layout.module.css │ │ └── index.jsx │ ├── AdWrapper │ │ └── index.jsx │ ├── Advertiser │ │ ├── Advertiser.module.css │ │ └── index.jsx │ ├── Payer │ │ ├── Payer.module.css │ │ └── index.jsx │ ├── Search │ │ └── index.jsx │ ├── Topics │ │ └── index.jsx │ └── constants.js ├── utils │ ├── index.js │ └── withURLSearchParams.js ├── setupTests.js ├── App.js ├── index.js ├── routes │ └── index.jsx ├── api │ └── index.jsx └── serviceWorker.js ├── public ├── robots.txt ├── favicon.ico ├── manifest.json └── index.html ├── deploy.sh ├── .gitignore ├── LICENSE ├── package.json ├── .eslintrc └── README.md /.env: -------------------------------------------------------------------------------- 1 | NODE_PATH = src/ -------------------------------------------------------------------------------- /src/components/AdDetail/AdDetail.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Tools/Tools.module.css: -------------------------------------------------------------------------------- 1 | .tools { 2 | margin-top: 1.5em; 3 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: / -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/pol-ad-dashboard/master/public/favicon.ico -------------------------------------------------------------------------------- /src/components/Login/Login.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 10% auto; 3 | max-width: 500px; 4 | } -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | npm run build 2 | aws s3 --region us-east-2 rm s3://pol-ad-dashboard 3 | aws s3 --region us-east-2 sync build/ s3://pol-ad-dashboard 4 | -------------------------------------------------------------------------------- /src/components/Ad/Ad.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: inline-block; 3 | vertical-align: top; 4 | margin: 6px; 5 | box-shadow: -3px 6px 14px 0px rgba(0,0,0,0.1) 6 | } 7 | -------------------------------------------------------------------------------- /src/components/AdSearch/AdSearch.module.css: -------------------------------------------------------------------------------- 1 | .meta-container { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | 6 | } 7 | 8 | .meta-title { 9 | margin: 0; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { default as withURLSearchParams } from './withURLSearchParams'; 2 | 3 | export const compose = ( ...functions ) => functions.reduce( ( accum, curr ) => ( ...args ) => accum( curr( ...args ) ), arg => arg ); 4 | -------------------------------------------------------------------------------- /src/components/Targets/Targets.module.css: -------------------------------------------------------------------------------- 1 | .button-group { 2 | margin: 4px 12px; 3 | } 4 | 5 | .search-targets > .container { 6 | margin: 0px -12px; 7 | } 8 | 9 | .inad .redx { 10 | display: none; 11 | } 12 | .inad:hover .redx { 13 | display: block; 14 | } -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Dashboard", 3 | "name": "QZ Political Ad Dashboard", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "background_color": "#ffffff" 13 | } 14 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router } from 'react-router-dom'; 3 | import Routes from './routes'; 4 | // eslint-disable-next-line 5 | import API from 'api'; 6 | 7 | function App() { 8 | return ( 9 |
Targeting data and images provieded by participants in the Political Ad Collector project by Quartz. Other ad data via Facebook, provided by the Online Political Ads Transparency Project at the NYU Tandon School of Engineering.
7 |{text}
63 |{ad_creative_link_caption}
64 |{ad_creative_link_title}
65 |{ad_creative_link_description}
66 | 67 | { 68 | impressions 69 | ? ( 70 |71 | {`${currency}`} • {`${impressions} ${impressions > 1 ? 'impressions' : 'impression'}`} 72 |
73 | ) : null 74 | } 75 |76 | First seen: {`${createdAt.toLocaleDateString( 'en-US', { dateStyle: 'full', timeStyle: 'long' } )}`} 77 |
78 | { 79 | updated_at 80 | ? ( 81 |82 | Last updated: {`${updatedAt.toLocaleDateString( 'en-US', { dateStyle: 'full', timeStyle: 'long' } )}`} 83 |
84 | ) : null 85 | } 86 | {/*Placeholder content (awaiting further ad data)
98 |{error}
81 |72 | {topicKey}{Math.round( targetPercent * 100 )}% 73 |
74 | 75 |83 | { 84 | payers && 85 | payers 86 | .map( payer => {payer.name} ) 87 | .reduce( ( accum, payer, idx ) => { 88 | // add commas 89 | const next = [ payer, ( , ) ]; 90 | if ( idx === payers.length - 1 ) { 91 | next.pop(); 92 | } 93 | return accum.concat( next ); 94 | }, [] ) 95 | } 96 |
97 |71 | {topicKey}{Math.round( targetPercent * 100 )}% 72 |
73 | 74 |82 | { 83 | advertisers && 84 | advertisers 85 | .map( page => {page.page_name} ) 86 | .reduce( ( accum, page, idx ) => { 87 | // add commas 88 | const next = [ page, ( , ) ]; 89 | if ( idx === advertisers.length - 1 ) { 90 | next.pop(); 91 | } 92 | return accum.concat( next ); 93 | }, [] ) 94 | } 95 |
96 |