Skip to content

Commit

Permalink
Merge pull request #31 from BU-Spark/districtFromAPI
Browse files Browse the repository at this point in the history
Boston Voter App: District number from google civic api
  • Loading branch information
eelkus01 authored Jun 18, 2024
2 parents 239c47e + 5780fe2 commit 13b32a9
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 44 deletions.
123 changes: 123 additions & 0 deletions client/src/pages/ballotInfo/districtForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* Form asking for user address and getting council district from Google Civic
* API. Note: API key is in .env file
*/

import React, { useState } from 'react';
import axios from 'axios';
import { Button, Grid, TextField } from '@mui/material';


// Set base URL for Axios
// const api = axios.create({
// baseURL: 'https://pitne-voter-app-express-production.up.railway.app/', // Point this to server URL
// });
const api = axios.create({
baseURL: 'http://localhost:3001', // Point this to server URL
});


const DistrictForm: React.FC = () => {
// Functions and variables to set district data
const [street, setStreet] = useState('');
const [city, setCity] = useState('');
const [state, setState] = useState('');
const [zip, setZip] = useState('');
const [districtNum, setDistrictNum] = useState<string | null>(null);

// Call API when address is submitted
const handleSubmit = async (event: React.FormEvent) => {

// Reset past data
event.preventDefault();
setDistrictNum(null);

// Set address
const address = `${street} ${city}, ${state} ${zip}`;

// Call API
try {
const response = await api.get('/api/district', {
params: { address },
});

const data = response.data;

// Set district number or error if no district number
if (data) {
console.log(data);
setDistrictNum(data);
} else {
console.log("ERROR FETCHING DISTRICT - ensure address is within Boston bounds")
}
} catch {
console.log("ERROR FETCHING DISTRICT - ensure address is within Boston bounds");
}
};


// Address form
return (
<div className='flex flex-col justify-center items-center p-4 flex-wrap'>

{/* Address form */}
<form onSubmit={handleSubmit} style={{ width: '100%', maxWidth: 600 }}>
<Grid container spacing={2} >
<Grid item xs={12} sm={6} >
<TextField
label="Street Number and Name"
variant="outlined"
fullWidth
value={street}
onChange={(e) => setStreet(e.target.value)}
required
sx={{ mb: 2 }}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
label="City"
variant="outlined"
fullWidth
value={city}
onChange={(e) => setCity(e.target.value)}
required
sx={{ mb: 2 }}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
label="State"
variant="outlined"
fullWidth
value={state}
onChange={(e) => setState(e.target.value)}
required
sx={{ mb: 2 }}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
label="Zipcode"
variant="outlined"
fullWidth
value={zip}
onChange={(e) => setZip(e.target.value)}
required
sx={{ mb: 2 }}
/>
</Grid>
</Grid>
<div className="flex justify-center">
<Button type="submit" variant="outlined" className='p-3 mt-4 rounded-full bg-white text-blue-700 border-blue-800 hover:bg-blue-100'>
Submit Address
</Button>
</div>
</form>

{/* NOTE: REMOVE BELOW PRINT, JUST FOR CHECKING WHILE BALLOT INFO IS IN PROGRESS */}
<p>District Num: {districtNum}</p>
</div>
);
};

export default DistrictForm;
12 changes: 7 additions & 5 deletions client/src/pages/ballotInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import ButtonFill from "@/components/button/ButtonFill"
import { Box, TextField } from "@mui/material";
import Checkbox from '@mui/material/Checkbox';
import * as React from 'react';
import DropDown from './dropDown';
import HelpIcon from '@mui/icons-material/Help';
import BoxAddress from "../../components/button/boxAddress";
import NavBar from "@/components/nav/NavBar";
import BallotInitDropDown from "./ballotInitDropDown";
import ButtonFillEx from "@/components/button/ButtonFillEx";
import DistrictForm from "./districtForm";



Expand All @@ -29,12 +28,13 @@ export default function BallotInfo() {
<h1 className='font-semibold text-xl p-3 mt-2'>Explore the elections, candidates, and crucial issues personalized to your community.</h1>
</div>


{/* Address form */}
<div className='flex flex-col justify-center items-center p-2'>
<BoxAddress />
<ButtonFill name='Submit Address' link='/submitAddress' variant='outlined' className='p-4 mt-4 rounded-full bg-white text-blue-700 border-blue-800 hover:bg-blue-100' />
<div className='flex flex-col justify-center items-center'>
<DistrictForm />
</div>


{/* Election checkbox card */}
<div className='grid grid-cols-4'>
<div className='md:col-span-1 hidden md:block'>
Expand Down Expand Up @@ -69,6 +69,7 @@ export default function BallotInfo() {
</div>
</div>


{/* What's on the Ballot dropdown */}
<div className='flex flex-col justify-center items-center p-8 my-6'>
<h1 className='font-bold text-center mx-6 my-4 text-4xl text-blue-700' style={{ fontFamily: 'Arial, sans-serif' }}><strong>What&apos;s on the Ballot?</strong></h1>
Expand All @@ -82,6 +83,7 @@ export default function BallotInfo() {
<BallotInitDropDown />
</div>


{/* Footer */}
<div className='flex flex-col justify-center items-center p-4 text-center my-6'>
<h1 className='font-semibold text-l'>You may be wondering ...</h1>
Expand Down
6 changes: 3 additions & 3 deletions client/src/pages/voterInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export default function VoterInfo() {
{/* County (fixed for all Boston voters) */}
<div className='flex flex-col justify-center items-center p-4 my-6'>
<h1 className='font-semibold text-center my-4 text-2xl'>Basic Voter Info</h1>
<div className='grid grid-cols-4 mt-1'>
<div className='md:col-span-1 hidden md:block'>
</div>
<div className='grid grid-cols-4 mt-1'>
<div className='md:col-span-1 hidden md:block'>
</div>
<div className="space-y-4 mx-10 my-1 p-8 rounded-2xl shadow-xl border border-gray-200 col-span-4 lg:col-span-2 bg-blue-100">
<div className="space-y-4 w-full px-4">
<div className="w-full px-4 text-center">
Expand Down
49 changes: 47 additions & 2 deletions server/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import cors from 'cors';
dotenv.config(); // Load environment variables

const app = express();
const port = process.env.PORT || 3001
// process.env.PORT || 3001
// const port = process.env.PORT || 3001;
const port = 3001;

app.use(cors()); // Needed to send data back to frontend


// API call for polling location
app.get('/api/lookup', async (req: Request, res: Response) => {
const { address } = req.query;

Expand All @@ -37,6 +39,49 @@ app.get('/api/lookup', async (req: Request, res: Response) => {
}
});


/* API call for district number (1-9)
* NOTE: Google Civic API is turning down the Representatives API in April 2025,
* so the below API call will need to be updated to their new system after
*/
app.get('/api/district', async (req: Request, res: Response) => {
const { address } = req.query;

// No address check (should be unnecessary b/c form validation)
if (!address) {
return res.status(400).json({ error: 'Address is required' });
}

try {
// Get data from API
const response = await axios.get('https://civicinfo.googleapis.com/civicinfo/v2/representatives', {
params: {
address,
key: process.env.GOOGLE_CIVIC_API_KEY,
},
});

const divisions = response.data.divisions;

// Extract the council district number
const councilDistrictKey = Object.keys(divisions).find(key => key.includes('council_district'));
let councilDistrictNumber = null;

if (councilDistrictKey) {
const match = councilDistrictKey.match(/council_district:(\d+)/);
if (match && match[1]) {
councilDistrictNumber = match[1];
console.log(`Council District Number: ${councilDistrictNumber}`);
}
}

// Send data back to frontend
res.status(200).json(councilDistrictNumber);
} catch (error) {
res.status(500).json({ error: 'Error fetching district' });
}
});

app.get('', (req: Request, res: Response) => {
res.send('Hello from the Voter Info API!');
});
Expand Down
34 changes: 34 additions & 0 deletions strapi/src/api/candidate/content-types/candidate/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"kind": "collectionType",
"collectionName": "candidates",
"info": {
"singularName": "candidate",
"pluralName": "candidates",
"displayName": "Candidates",
"description": ""
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {},
"attributes": {
"District": {
"type": "enumeration",
"enum": [
"District 1",
"District 2",
"District 3",
"District 4",
"District 5",
"District 6",
"District 7",
"District 8",
"District 9"
],
"required": true
},
"Name": {
"type": "string"
}
}
}
9 changes: 9 additions & 0 deletions strapi/src/api/candidate/controllers/candidate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

/**
* candidate controller
*/

const { createCoreController } = require('@strapi/strapi').factories;

module.exports = createCoreController('api::candidate.candidate');
9 changes: 9 additions & 0 deletions strapi/src/api/candidate/routes/candidate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

/**
* candidate router
*/

const { createCoreRouter } = require('@strapi/strapi').factories;

module.exports = createCoreRouter('api::candidate.candidate');
9 changes: 9 additions & 0 deletions strapi/src/api/candidate/services/candidate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

/**
* candidate service
*/

const { createCoreService } = require('@strapi/strapi').factories;

module.exports = createCoreService('api::candidate.candidate');
Loading

0 comments on commit 13b32a9

Please sign in to comment.