Skip to content

Commit

Permalink
Merge branch 'newsletter_integration' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
Remus287 committed Dec 14, 2024
2 parents 6e60034 + 8abbdd7 commit f7dc3e0
Show file tree
Hide file tree
Showing 3 changed files with 423 additions and 98 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Collaborators:
* Eleanor Elkus; Github: @eelkus01
* Jason Jiang; Github: @jasonjiang9142
* Remus Harris; Github: @remus287
* Celine Boudaie; Github: @celineboudaie

# Description
The Boston Voter App addresses the lack of accessible information about voting logistics and candidates in municipal elections in Boston. This progressive web application centralizes all vital voting information to increase voter turnout, particularly focusing on BIPOC (Black, Indigenous, People of color) voters who face significant barriers to voting in local elections.
Expand Down Expand Up @@ -78,17 +79,21 @@ Note: Using the keywork local uses the local Express or Strapi server. Leaving t

This API key is for enabling Mailchimp, a service that allows a site admin to collect email addresses input by site users

## Candidate Info

Code is linked to strapi so when a candidate is updated on strapi the website is updated

# Deployment

Access our deployed website [here](https://bostonvoter.com/) or https://bostonvoter.com/.

[![Watch the video](https://img.youtube.com/vi/AIte6hS3cCc/0.jpg)](https://www.youtube.com/watch?v=AIte6hS3cCc&feature=youtu.be)

# Collaborators
* Arshnoor Kaur Chadha - Github: @arshnoorKC13
* Elenaor Elkus - Github: @eelkus01
* Jason Jiang - Github: @jasonjiang9142
* Remus Harris - Github: @remus287
* Celine Boudaie - Github: @celineboudaie

# For more detailed technical documentation visit https://github.com/BU-Spark/pitne-voter-app/wiki

Expand Down
250 changes: 250 additions & 0 deletions client/src/pages/candidateInfo/[candidate].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/* candidate profiles that appear when their icon is clicked on candidate info page.
Pulls data from strapi "Candidates" content.
*/

import React, { use, useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { CandidateAPI } from '@/common';
import { all } from 'axios';
import ButtonFillEx from '@/components/button/ButtonFillEx';
import { Accordion, AccordionDetails, AccordionSummary, Link, Typography } from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';

interface CandidateAttributes {
CampaignSiteLink: string | null;
District: string;
ElectionName: string;
LinkedinLink: string | null;
Name: string;
Party: string;
Role: string;
createdAt: string;
publishedAt: string;
updatedAt: string;
Question1: string | null;
Answer1: string | null;
Question2: string | null;
Answer2: string | null;
Question3: string | null;
Answer3: string | null;
Question4: string | null;
Answer4: string | null;
Question5: string | null;
Answer5: string | null;
Headshot: {
data: {
attributes: {
url: string
}
}

}
}

interface CandidateDataObject {
id: number;
attributes: CandidateAttributes;
}

interface Candidate {
attributes: CandidateAttributes;
}

interface QuestionsAndAnswers {
[key: string]: { question: string | null, answer: string | null };
}

export default function Candidate() {
const router = useRouter();
const [candidateName, setCandidateName] = useState<string>('');
const [allCandidateData, setAllCandidateData] = useState<CandidateDataObject[]>([])
const [candidateData, setCandidateData] = useState<CandidateAttributes | null>(null);
const [questionsAndAnswers, setQuestionsAndAnswers] = useState<QuestionsAndAnswers>({});


// Get candidate name from URL
useEffect(() => {
if (!router.isReady) return;

const { candidate } = router.query;
candidate && setCandidateName(candidate as string);

}, [router.isReady, router.query]);


// Get candidate data from strapi
useEffect(() => {
const getData = async () => {
try {
const response = await fetch(CandidateAPI + '?populate=*', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
const data = (await response.json()).data;
setAllCandidateData(data)
console.log(data)
}


} catch (e) {
console.log(e);
}
};

getData();

}, []);


// Set the candidate data
useEffect(() => {
if (candidateName && allCandidateData) {
const normalizedInput = (input: string) => input.replace(/\s+/g, '').toLowerCase();
const foundCandidateData = allCandidateData.find((candidateData: any) =>
normalizedInput(candidateData.attributes.Name) === normalizedInput(candidateName)
);
if (foundCandidateData) {
setCandidateData(foundCandidateData.attributes);
} else {
setCandidateData(null);
}
}
}, [allCandidateData, candidateName]);

useEffect(() => {
console.log(candidateData);
console.log(candidateData?.Headshot.data.attributes.url)
}, [candidateData])


// Get filled out questions and answers from strapi and populate map
useEffect(() => {
if (candidateData) {
const qaMap = Object.entries(candidateData)
.filter(([key, value]) => key.startsWith('Question') || key.startsWith('Answer'))
.reduce<QuestionsAndAnswers>((acc, [key, value]) => {
const questionIndex = key.match(/\d+/)?.[0];
if (questionIndex) {
if (!acc[questionIndex]) {
acc[questionIndex] = { question: null, answer: null };
}
acc[questionIndex][key.includes('Question') ? 'question' : 'answer'] = value;
}
return acc;
}, {});
setQuestionsAndAnswers(qaMap);
}



}, [candidateData]);


return (
<>
<header className="flex border-b border-solid border-b-white px-10 py-3"></header>
{/* Actual candidate data */}
<div className="relative flex min-h-screen flex-col bg-[#d1e4fa] overflow-x-hidden justify-center bg-sky-100">
<div className="mt-20 m-10">
{/* Go Back button */}
<button
type="button"
onClick={() => router.back()}
className="rounded-full bg-[#d1e4fa] text-blue-700 flex min-w-[84px] cursor-pointer items-center justify-center overflow-hidden h-10 px-4 text-sm font-bold leading-normal tracking-[0.015em] max-w-[480px] lg:w-auto bg-transparent hover:bg-blue-200"
>
<ArrowBackIcon className="mr-4" />

</button>
</div>
{candidateData ? (
<div className="lg:px-36 md:px-10 sm:px-10 px-6 flex flex-1 justify-center pb-5 ">
<div className="layout-content-container flex flex-col max-w-[960px] flex-1 bg-sky-50 rounded">
<div className="grid grid-cols-1 py-4">
<div className="flex justify-center lg:p-4 md:p-4 sm:p-4 md:col-span-3">

<div className="flex w-full flex-col gap-4 md:flex-row md:justify-center md:items-center">
<div className="flex gap-4 flex-col md:flex-row">
{/* Candidate image */}
<div className="flex justify-center items-center h-full">
<div
className="bg-center bg-no-repeat bg-cover rounded-full h-64 w-64 lg:h-80 lg:w-80 mx-6"
style={{
backgroundImage: `url(https://pitne-voter-app-production.up.railway.app${candidateData?.Headshot.data.attributes.url})`,
}}
></div>
</div>
{/* Name, role, party */}
<div className="flex flex-col justify-center text-center lg:text-left md:text-left">
<h1 className="pb-2 text-3xl md:text-4xl lg:text-6xl font-bold mb-2 bg-blue-950 bg-clip-text text-transparent">
{candidateData?.Name}
</h1>
<p className="text-xl md:text-2xl leading-normal">
{candidateData?.Role}
</p>
<p className=" text-xl md:text-2xl leading-normal">
{candidateData?.Party}
</p>


{/* Links */}
<div className="flex flex-row items-center justify-center lg:justify-normal md:justify-normal pt-4 text-center md:col-span-1">
{candidateData.CampaignSiteLink && (
<ButtonFillEx
name="Campaign Site"
link={candidateData.CampaignSiteLink}
className="p-4 mr-2 xl:my-2 rounded-full bg-white text-blue-700 border-blue-800 hover:bg-gray-200"
/>
)}
{candidateData.LinkedinLink && (
<ButtonFillEx
name="Linkedin"
link={candidateData.LinkedinLink}
className="p-4 ml-2 xl:my-2 rounded-full bg-white text-blue-700 border-blue-800 hover:bg-gray-200"
/>
)}
</div>

</div>
</div>
</div>
</div>
</div>


{/* Questions and Answers if filled out */}
{Object.entries(questionsAndAnswers) &&
<div className="flex flex-col justify-center items-center py-8 my-2">

{Object.entries(questionsAndAnswers).map(([index, qa]) => (
qa.question && qa.answer ? (
<Accordion key={index} className='bg-white w-full lg:w-3/4 md:w-3/4 mb-3 rounded-md'>

{/* Question */}
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={`panel${index}-content`} id={`panel${index}-header`}>
<Typography className='text-blue-700 text-xl'>{qa.question}</Typography>
</AccordionSummary>

{/* Answer */}
<AccordionDetails>
<Typography className='mb-4 text-xl text-center'>{qa.answer}</Typography>
</AccordionDetails>
</Accordion>

) : null
))}
<p className="font-semibold mb-4 text-center mt-10">Questions curated by the founder, journalist Yawu Miller.</p>
</div>

}
</div>

</div>
) : (null)}
</div>
</>
);
}
Loading

0 comments on commit f7dc3e0

Please sign in to comment.