From b78499d660d63600e694ba33007ed024d71dcd74 Mon Sep 17 00:00:00 2001 From: celine Date: Mon, 28 Oct 2024 19:02:04 -0400 Subject: [PATCH 01/17] candidate info page --- client/src/components/nav/NavBar.tsx | 3 +- client/src/pages/candidateInfo/index.tsx | 77 ++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 client/src/pages/candidateInfo/index.tsx diff --git a/client/src/components/nav/NavBar.tsx b/client/src/components/nav/NavBar.tsx index 8b3f1e25..24c650a5 100644 --- a/client/src/components/nav/NavBar.tsx +++ b/client/src/components/nav/NavBar.tsx @@ -22,11 +22,12 @@ const slideIn = keyframes` } `; -const pages = ['Upcoming Elections', 'Your Voter Info', 'Voting Options', 'Ballot Info', 'Drop Box Locations']; +const pages = ['Upcoming Elections', 'Your Voter Info', 'Voting Options', 'Candidate Info', 'Ballot Info', 'Drop Box Locations']; const links: Record = { 'Upcoming Elections': '/upcomingElections', 'Your Voter Info': '/voterInfo', 'Voting Options': '/votingOptions', + 'Candidate Info': '/candidateInfo', 'Ballot Info': '/ballotInfo', 'Drop Box Locations': '/dropBoxLocations' }; diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx new file mode 100644 index 00000000..ee6b6b7c --- /dev/null +++ b/client/src/pages/candidateInfo/index.tsx @@ -0,0 +1,77 @@ +'use client'; +import React, { useEffect, useState } from 'react'; +import { localCandidateAPI, deployedCandidateAPI } from '@/common'; + +interface Candidate { + id: number; + attributes: { + Name: string; + District: string; + Party: string; + ElectionName: string; + Bio: string; + CampaignSiteLink?: string; + LinkedInLink?: string; + [key: string]: string | undefined; + }; +} + +// Component for Candidate Information Page +export default function CandidateInfo() { + const [candidate, setCandidate] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchCandidateData = async () => { + try { + const response = await fetch(`${deployedCandidateAPI}/candidates?populate=*`); + if (response.ok) { + const data = await response.json(); + setCandidate(data.data[0]); + setIsLoading(false); + } else { + console.error('Failed to fetch candidate data'); + } + } catch (error) { + console.error('Error:', error); + setIsLoading(false); + } + }; + + fetchCandidateData(); + }, []); + + if (isLoading) return

Loading...

; + + return ( +
+ {candidate ? ( + <> +

{candidate.attributes.Name}

+

District: {candidate.attributes.District}

+

Party: {candidate.attributes.Party}

+

Office Running For: {candidate.attributes.ElectionName}

+

Bio: {candidate.attributes.Bio}

+ +
+

Questionnaire

+ {Array.from({ length: 10 }).map((_, i) => { + const question = candidate.attributes[`Question${i + 1}` as keyof Candidate['attributes']]; + const answer = candidate.attributes[`Answer${i + 1}` as keyof Candidate['attributes']]; + return ( + question && answer && ( +
+

{question}

+

{answer}

+
+ ) + ); + })} +
+ + ) : ( +

Candidate data not available.

+ )} +
+ ); +} From 150a335bb90ac33294327693745b8829c1bf6442 Mon Sep 17 00:00:00 2001 From: celine Date: Tue, 29 Oct 2024 19:06:38 -0400 Subject: [PATCH 02/17] basic implementation based on strapi link --- client/src/pages/candidateInfo/index.tsx | 90 +++++++++++++++--------- 1 file changed, 58 insertions(+), 32 deletions(-) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index ee6b6b7c..6322baba 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -1,6 +1,5 @@ 'use client'; import React, { useEffect, useState } from 'react'; -import { localCandidateAPI, deployedCandidateAPI } from '@/common'; interface Candidate { id: number; @@ -12,28 +11,44 @@ interface Candidate { Bio: string; CampaignSiteLink?: string; LinkedInLink?: string; + PhotoURL?: string; [key: string]: string | undefined; }; } // Component for Candidate Information Page export default function CandidateInfo() { - const [candidate, setCandidate] = useState(null); + const [candidates, setCandidates] = useState([]); const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); useEffect(() => { const fetchCandidateData = async () => { + console.log("Fetching candidate data..."); try { - const response = await fetch(`${deployedCandidateAPI}/candidates?populate=*`); + const response = await fetch(`https://pitne-voter-app-production.up.railway.app/api/candidates`); + console.log("API Response Status:", response.status); + if (response.ok) { const data = await response.json(); - setCandidate(data.data[0]); - setIsLoading(false); + console.log("Fetched data:", data); + + if (data.data && data.data.length > 0) { + const fetchedCandidates: Candidate[] = data.data; + console.log("Fetched Candidates:", fetchedCandidates); + setCandidates(fetchedCandidates); + } else { + console.warn("No candidate data available."); + setError("No candidate data available."); + } } else { - console.error('Failed to fetch candidate data'); + console.error('Failed to fetch candidate data', response.statusText); + setError('Failed to fetch candidate data'); } - } catch (error) { - console.error('Error:', error); + } catch (fetchError) { + console.error('Error:', fetchError); + setError('An error occurred while fetching candidate data'); + } finally { setIsLoading(false); } }; @@ -41,36 +56,47 @@ export default function CandidateInfo() { fetchCandidateData(); }, []); + console.log("Loading state:", isLoading); + console.log("Candidates data:", candidates); + if (isLoading) return

Loading...

; + if (error) return

{error}

; return (
- {candidate ? ( - <> -

{candidate.attributes.Name}

-

District: {candidate.attributes.District}

-

Party: {candidate.attributes.Party}

-

Office Running For: {candidate.attributes.ElectionName}

-

Bio: {candidate.attributes.Bio}

- -
-

Questionnaire

- {Array.from({ length: 10 }).map((_, i) => { - const question = candidate.attributes[`Question${i + 1}` as keyof Candidate['attributes']]; - const answer = candidate.attributes[`Answer${i + 1}` as keyof Candidate['attributes']]; - return ( - question && answer && ( -
-

{question}

-

{answer}

-
- ) - ); - })} + {candidates.length > 0 ? ( + candidates.map(candidate => ( +
+

{candidate.attributes.Name}

+ {candidate.attributes.PhotoURL && ( + {candidate.attributes.Name} + )} +

District: {candidate.attributes.District}

+

Party: {candidate.attributes.Party}

+

Office Running For: {candidate.attributes.ElectionName}

+

Bio: {candidate.attributes.Bio}

+ +
+

Questionnaire

+ {Array.from({ length: 10 }).map((_, i) => { + const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; + const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; + const question = candidate.attributes[questionKey]; + const answer = candidate.attributes[answerKey]; + return ( + question && answer ? ( +
+

{question}

+

{answer}

+
+ ) : null + ); + })} +
- + )) ) : ( -

Candidate data not available.

+

No candidates available.

)}
); From 121486506e9fef4fd89cb54ed14740a62e680bec Mon Sep 17 00:00:00 2001 From: Tiffany Yu Date: Thu, 31 Oct 2024 14:57:42 -0400 Subject: [PATCH 03/17] added images onto page --- client/src/pages/candidateInfo/index.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index 6322baba..36d80a8a 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -8,7 +8,7 @@ interface Candidate { District: string; Party: string; ElectionName: string; - Bio: string; + Bio?: string; CampaignSiteLink?: string; LinkedInLink?: string; PhotoURL?: string; @@ -26,7 +26,7 @@ export default function CandidateInfo() { const fetchCandidateData = async () => { console.log("Fetching candidate data..."); try { - const response = await fetch(`https://pitne-voter-app-production.up.railway.app/api/candidates`); + const response = await fetch(`https://pitne-voter-app-production.up.railway.app/api/candidates?populate=Headshot`); console.log("API Response Status:", response.status); if (response.ok) { @@ -34,8 +34,20 @@ export default function CandidateInfo() { console.log("Fetched data:", data); if (data.data && data.data.length > 0) { - const fetchedCandidates: Candidate[] = data.data; - console.log("Fetched Candidates:", fetchedCandidates); + // Map through fetched candidates to add the Headshot URL + const fetchedCandidates: Candidate[] = data.data.map((candidate: any) => { + const headshotUrl = candidate.attributes.Headshot?.data?.attributes?.url + ? `https://pitne-voter-app-production.up.railway.app${candidate.attributes.Headshot.data.attributes.url}` + : undefined; + return { + ...candidate, + attributes: { + ...candidate.attributes, + PhotoURL: headshotUrl, // Add headshot URL to PhotoURL attribute + }, + }; + }); + console.log("Fetched Candidates with Headshot URL:", fetchedCandidates); setCandidates(fetchedCandidates); } else { console.warn("No candidate data available."); From b7bde964f2e35f3fa44a76fb3987d7dcb7395068 Mon Sep 17 00:00:00 2001 From: Grace Murphy Date: Thu, 31 Oct 2024 15:08:16 -0400 Subject: [PATCH 04/17] Update index.tsx --- client/src/pages/candidateInfo/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index 36d80a8a..226a1b3f 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -9,6 +9,7 @@ interface Candidate { Party: string; ElectionName: string; Bio?: string; + Bio: string; CampaignSiteLink?: string; LinkedInLink?: string; PhotoURL?: string; @@ -19,6 +20,7 @@ interface Candidate { // Component for Candidate Information Page export default function CandidateInfo() { const [candidates, setCandidates] = useState([]); + const [candidates, setCandidates] = useState([]); //holds array of candiate objects const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -27,6 +29,7 @@ export default function CandidateInfo() { console.log("Fetching candidate data..."); try { const response = await fetch(`https://pitne-voter-app-production.up.railway.app/api/candidates?populate=Headshot`); + const response = await fetch(`https://pitne-voter-app-production.up.railway.app/api/candidates`); console.log("API Response Status:", response.status); if (response.ok) { From 4b5ecd5f00bf913c257db6bd96cccf65f71b5f4a Mon Sep 17 00:00:00 2001 From: Grace Murphy Date: Thu, 31 Oct 2024 15:08:42 -0400 Subject: [PATCH 05/17] Update index.tsx --- client/src/pages/candidateInfo/index.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index 226a1b3f..0a166841 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -8,7 +8,6 @@ interface Candidate { District: string; Party: string; ElectionName: string; - Bio?: string; Bio: string; CampaignSiteLink?: string; LinkedInLink?: string; @@ -19,7 +18,6 @@ interface Candidate { // Component for Candidate Information Page export default function CandidateInfo() { - const [candidates, setCandidates] = useState([]); const [candidates, setCandidates] = useState([]); //holds array of candiate objects const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -28,7 +26,6 @@ export default function CandidateInfo() { const fetchCandidateData = async () => { console.log("Fetching candidate data..."); try { - const response = await fetch(`https://pitne-voter-app-production.up.railway.app/api/candidates?populate=Headshot`); const response = await fetch(`https://pitne-voter-app-production.up.railway.app/api/candidates`); console.log("API Response Status:", response.status); From 471572492245e53ed38db64ef5804184b8a3ad5b Mon Sep 17 00:00:00 2001 From: Tiffany Yu Date: Thu, 31 Oct 2024 15:17:35 -0400 Subject: [PATCH 06/17] basic formating --- client/src/pages/candidateInfo/index.tsx | 47 ++++++++++++++++-------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index 36d80a8a..0a2fc7d3 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -75,21 +75,38 @@ export default function CandidateInfo() { if (error) return

{error}

; return ( -
+
+
+ { /*
+ Pattern +
+ */} +
+
+

LEARN. PLAN.

+ +
+ +

Explore the elections, candidates, and crutial +
+ issues personalized to your community.

+
+
+ {candidates.length > 0 ? ( candidates.map(candidate => ( -
-

{candidate.attributes.Name}

+
+

{candidate.attributes.Name}

{candidate.attributes.PhotoURL && ( - {candidate.attributes.Name} + {candidate.attributes.Name} )} -

District: {candidate.attributes.District}

-

Party: {candidate.attributes.Party}

-

Office Running For: {candidate.attributes.ElectionName}

-

Bio: {candidate.attributes.Bio}

+

District: {candidate.attributes.District}

+

Party: {candidate.attributes.Party}

+

Office Running For: {candidate.attributes.ElectionName}

+

Bio: {candidate.attributes.Bio}

-
-

Questionnaire

+
+

Questionnaire

{Array.from({ length: 10 }).map((_, i) => { const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; @@ -97,9 +114,9 @@ export default function CandidateInfo() { const answer = candidate.attributes[answerKey]; return ( question && answer ? ( -
-

{question}

-

{answer}

+
+

{question}

+

{answer}

) : null ); @@ -108,8 +125,8 @@ export default function CandidateInfo() {
)) ) : ( -

No candidates available.

+

No candidates available.

)}
- ); + ); } From 473c9df3ef773272716812ac0b5597e1cc01be07 Mon Sep 17 00:00:00 2001 From: Grace Murphy Date: Thu, 31 Oct 2024 15:54:20 -0400 Subject: [PATCH 07/17] formatted page --- client/src/pages/candidateInfo/index.tsx | 82 +++++++++++++++--------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index 0a166841..51eefb3c 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -8,7 +8,7 @@ interface Candidate { District: string; Party: string; ElectionName: string; - Bio: string; + Bio?: string; CampaignSiteLink?: string; LinkedInLink?: string; PhotoURL?: string; @@ -18,15 +18,16 @@ interface Candidate { // Component for Candidate Information Page export default function CandidateInfo() { - const [candidates, setCandidates] = useState([]); //holds array of candiate objects + const [candidates, setCandidates] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); + const [expandedCandidateId, setExpandedCandidateId] = useState(null); // State for managing expanded candidate useEffect(() => { const fetchCandidateData = async () => { console.log("Fetching candidate data..."); try { - const response = await fetch(`https://pitne-voter-app-production.up.railway.app/api/candidates`); + const response = await fetch('https://pitne-voter-app-production.up.railway.app/api/candidates?populate=Headshot'); console.log("API Response Status:", response.status); if (response.ok) { @@ -71,6 +72,10 @@ export default function CandidateInfo() { console.log("Loading state:", isLoading); console.log("Candidates data:", candidates); + const toggleExpand = (candidateId: number) => { + setExpandedCandidateId(prevId => (prevId === candidateId ? null : candidateId)); // Toggle expand/collapse + }; + if (isLoading) return

Loading...

; if (error) return

{error}

; @@ -78,33 +83,52 @@ export default function CandidateInfo() {
{candidates.length > 0 ? ( candidates.map(candidate => ( -
-

{candidate.attributes.Name}

- {candidate.attributes.PhotoURL && ( - {candidate.attributes.Name} - )} -

District: {candidate.attributes.District}

-

Party: {candidate.attributes.Party}

-

Office Running For: {candidate.attributes.ElectionName}

-

Bio: {candidate.attributes.Bio}

- -
-

Questionnaire

- {Array.from({ length: 10 }).map((_, i) => { - const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; - const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; - const question = candidate.attributes[questionKey]; - const answer = candidate.attributes[answerKey]; - return ( - question && answer ? ( -
-

{question}

-

{answer}

-
- ) : null - ); - })} +
toggleExpand(candidate.id)} + > +
+
+

{candidate.attributes.Name}

+

Party: {candidate.attributes.Party}

+
+ {candidate.attributes.PhotoURL && ( + {candidate.attributes.Name} + )}
+ + {/* Expandable Details */} + {expandedCandidateId === candidate.id && ( +
+

District: {candidate.attributes.District}

+

Office Running For: {candidate.attributes.ElectionName}

+

Bio: {candidate.attributes.Bio}

+ +
+

Questionnaire

+ {Array.from({ length: 10 }).map((_, i) => { + const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; + const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; + const question = candidate.attributes[questionKey]; + const answer = candidate.attributes[answerKey]; + return ( + question && answer ? ( +
+

{question}

+

{answer}

+
+ ) : null + ); + })} +
+
+ )}
)) ) : ( From d2fe3b8dfa759119951200fbeb5794137822ed3a Mon Sep 17 00:00:00 2001 From: Grace Murphy Date: Thu, 31 Oct 2024 16:02:49 -0400 Subject: [PATCH 08/17] fixed padding at the top of page --- client/src/pages/candidateInfo/index.tsx | 102 ++++++++++++----------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index 51eefb3c..8e93fa2d 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -80,60 +80,62 @@ export default function CandidateInfo() { if (error) return

{error}

; return ( -
- {candidates.length > 0 ? ( - candidates.map(candidate => ( -
toggleExpand(candidate.id)} - > -
-
-

{candidate.attributes.Name}

-

Party: {candidate.attributes.Party}

+
{/* Added padding here */} +
+ {candidates.length > 0 ? ( + candidates.map(candidate => ( +
toggleExpand(candidate.id)} + > +
+
+

{candidate.attributes.Name}

+

Party: {candidate.attributes.Party}

+
+ {candidate.attributes.PhotoURL && ( + {candidate.attributes.Name} + )}
- {candidate.attributes.PhotoURL && ( - {candidate.attributes.Name} - )} -
- {/* Expandable Details */} - {expandedCandidateId === candidate.id && ( -
-

District: {candidate.attributes.District}

-

Office Running For: {candidate.attributes.ElectionName}

-

Bio: {candidate.attributes.Bio}

+ {/* Expandable Details */} + {expandedCandidateId === candidate.id && ( +
+

District: {candidate.attributes.District}

+

Office Running For: {candidate.attributes.ElectionName}

+

Bio: {candidate.attributes.Bio}

-
-

Questionnaire

- {Array.from({ length: 10 }).map((_, i) => { - const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; - const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; - const question = candidate.attributes[questionKey]; - const answer = candidate.attributes[answerKey]; - return ( - question && answer ? ( -
-

{question}

-

{answer}

-
- ) : null - ); - })} +
+

Questionnaire

+ {Array.from({ length: 10 }).map((_, i) => { + const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; + const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; + const question = candidate.attributes[questionKey]; + const answer = candidate.attributes[answerKey]; + return ( + question && answer ? ( +
+

{question}

+

{answer}

+
+ ) : null + ); + })} +
-
- )} -
- )) - ) : ( -

No candidates available.

- )} + )} +
+ )) + ) : ( +

No candidates available.

+ )} +
); } From 34b9322d2b6f74d86f979d9224c4582b3e4e7b45 Mon Sep 17 00:00:00 2001 From: Tiffany Yu Date: Fri, 1 Nov 2024 11:38:57 -0400 Subject: [PATCH 09/17] added header to candidate info page --- client/src/pages/candidateInfo/index.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index 8e93fa2d..bf63fb7e 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -81,6 +81,20 @@ export default function CandidateInfo() { return (
{/* Added padding here */} + {/* Header */} +
+
+
+

LEARN. PLAN.

+
+

+ Explore the election, candidates, and crutial +
+ issues personalized to your community! +

+
+
+
{candidates.length > 0 ? ( candidates.map(candidate => ( From a902d944450f6e5eebd174d47096ffd1ea539030 Mon Sep 17 00:00:00 2001 From: Tiffany Yu Date: Sat, 2 Nov 2024 18:07:15 -0400 Subject: [PATCH 10/17] fixed page formating --- client/src/pages/candidateInfo/index.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index bf63fb7e..6cb1022f 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -80,10 +80,10 @@ export default function CandidateInfo() { if (error) return

{error}

; return ( -
{/* Added padding here */} +
{/* Header */}
-
+

LEARN. PLAN.

@@ -95,13 +95,19 @@ export default function CandidateInfo() {
-
+
{candidates.length > 0 ? ( candidates.map(candidate => (
toggleExpand(candidate.id)} >
From 4dacb3f3656e093b18fe23a42840ad88495faf03 Mon Sep 17 00:00:00 2001 From: celine Date: Thu, 7 Nov 2024 10:39:49 -0500 Subject: [PATCH 11/17] removed ballotinfo page and places that link to it --- client/src/components/nav/NavBar.tsx | 3 +- client/src/pages/ballotInfo/[candidate].tsx | 251 ------------------ client/src/pages/ballotInfo/districtForm.tsx | 124 --------- .../electionCheckBox/electionCheckbox.tsx | 118 -------- .../electionCheckBox/electionCheckboxCard.tsx | 81 ------ client/src/pages/ballotInfo/index.tsx | 94 ------- client/src/pages/ballotInfo/popUpBox.tsx | 34 --- .../whatsOnTheBallot/ballotInitative.tsx | 165 ------------ .../whatsOnTheBallot/candidateData.tsx | 197 -------------- .../whatsOnTheBallot/peopleCard.tsx | 64 ----- client/src/pages/dropBoxLocations/index.tsx | 2 +- client/src/pages/voterInfo/index.tsx | 5 +- 12 files changed, 3 insertions(+), 1135 deletions(-) delete mode 100644 client/src/pages/ballotInfo/[candidate].tsx delete mode 100644 client/src/pages/ballotInfo/districtForm.tsx delete mode 100644 client/src/pages/ballotInfo/electionCheckBox/electionCheckbox.tsx delete mode 100644 client/src/pages/ballotInfo/electionCheckBox/electionCheckboxCard.tsx delete mode 100644 client/src/pages/ballotInfo/index.tsx delete mode 100644 client/src/pages/ballotInfo/popUpBox.tsx delete mode 100644 client/src/pages/ballotInfo/whatsOnTheBallot/ballotInitative.tsx delete mode 100644 client/src/pages/ballotInfo/whatsOnTheBallot/candidateData.tsx delete mode 100644 client/src/pages/ballotInfo/whatsOnTheBallot/peopleCard.tsx diff --git a/client/src/components/nav/NavBar.tsx b/client/src/components/nav/NavBar.tsx index 24c650a5..97c7bc37 100644 --- a/client/src/components/nav/NavBar.tsx +++ b/client/src/components/nav/NavBar.tsx @@ -22,13 +22,12 @@ const slideIn = keyframes` } `; -const pages = ['Upcoming Elections', 'Your Voter Info', 'Voting Options', 'Candidate Info', 'Ballot Info', 'Drop Box Locations']; +const pages = ['Upcoming Elections', 'Your Voter Info', 'Voting Options', 'Candidate Info', 'Drop Box Locations']; const links: Record = { 'Upcoming Elections': '/upcomingElections', 'Your Voter Info': '/voterInfo', 'Voting Options': '/votingOptions', 'Candidate Info': '/candidateInfo', - 'Ballot Info': '/ballotInfo', 'Drop Box Locations': '/dropBoxLocations' }; diff --git a/client/src/pages/ballotInfo/[candidate].tsx b/client/src/pages/ballotInfo/[candidate].tsx deleted file mode 100644 index cf266ff8..00000000 --- a/client/src/pages/ballotInfo/[candidate].tsx +++ /dev/null @@ -1,251 +0,0 @@ -/* Deeper candidate profiles that appear when their icon is clicked in the - * "What's on the Ballot" dropdown. Styles the entire deep profile page. Pulls - * data from strapi "Candidates" content. -*/ - -import React, { use, useEffect, useState } from 'react'; -import { useRouter } from 'next/router'; -import { localCandidateAPI, deployedCandidateAPI } 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(''); - const [allCandidateData, setAllCandidateData] = useState([]) - const [candidateData, setCandidateData] = useState(null); - const [questionsAndAnswers, setQuestionsAndAnswers] = useState({}); - - - // 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(deployedCandidateAPI + '?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); // Handle case where candidate is not found - } - } - }, [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((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 ( - <> -
- {/* Actual candidate data */} -
-
- {/* Go Back button */} - -
- {candidateData ? ( -
-
-
-
- -
-
- {/* Candidate image */} -
-
-
- {/* Name, role, party */} -
-

- {candidateData?.Name} -

-

- {candidateData?.Role} -

-

- {candidateData?.Party} -

- - - {/* Links */} -
- {candidateData.CampaignSiteLink && ( - - )} - {candidateData.LinkedinLink && ( - - )} -
- -
-
-
-
-
- - - {/* Questions and Answers if filled out */} - {Object.entries(questionsAndAnswers) && -
- - {Object.entries(questionsAndAnswers).map(([index, qa]) => ( - qa.question && qa.answer ? ( - - - {/* Question */} - } aria-controls={`panel${index}-content`} id={`panel${index}-header`}> - {qa.question} - - - {/* Answer */} - - {qa.answer} - - - - ) : null - ))} -

Questions curated by the founder, journalist Yawu Miller.

-
- - } -
- -
- ) : (null)} -
- - ); -} \ No newline at end of file diff --git a/client/src/pages/ballotInfo/districtForm.tsx b/client/src/pages/ballotInfo/districtForm.tsx deleted file mode 100644 index 3078290c..00000000 --- a/client/src/pages/ballotInfo/districtForm.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React, { useState } from 'react'; -import axios from 'axios'; -import { Button, Grid, TextField, Typography } from '@mui/material'; -import { localExpressURL, deployedExpressURL, setGlobalDistrictNum } from '@/common'; - -// Set base URL for Axios -const api = axios.create({ - baseURL: deployedExpressURL, -}); - -// On submit, set the district to be the result of api call -interface DistrictFormProps { - onFormSubmit: (district: string) => void; -} - -const DistrictForm: React.FC = ({ onFormSubmit }) => { - // 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(null); - const [error, setError] = useState(null); // Error state - - // Call API when address is submitted - const handleSubmit = async (event: React.FormEvent) => { - // Reset past data - event.preventDefault(); - setDistrictNum(null); - setError(null); // Reset error - - // 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) { - setDistrictNum(data); - setGlobalDistrictNum(data); - onFormSubmit(data); - } else { - setError("ERROR FETCHING DISTRICT - ensure address is within Boston bounds"); - } - } catch { - setError("ERROR FETCHING DISTRICT - ensure address is within Boston bounds"); - } - }; - - // Address form - return ( -
- {/* Address form */} -
- - - setStreet(e.target.value)} - required - sx={{ mb: 2 }} - /> - - - setCity("Boston")} - required - sx={{ mb: 2 }} - /> - - - setState("MA")} - required - sx={{ mb: 2 }} - /> - - - setZip(e.target.value)} - required - sx={{ mb: 2 }} - /> - - -
- -
-
- - {/* Error message */} - {error && ( - - {error} - - )} -
- ); -}; - -export default DistrictForm; diff --git a/client/src/pages/ballotInfo/electionCheckBox/electionCheckbox.tsx b/client/src/pages/ballotInfo/electionCheckBox/electionCheckbox.tsx deleted file mode 100644 index 8076fd8d..00000000 --- a/client/src/pages/ballotInfo/electionCheckBox/electionCheckbox.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* Upcoming election checkbox. Includes calls to strapi to fetch the election - * date and name. Styles the larger box holding the data. -*/ - -'use client'; -import react from 'react'; -import { useState, useEffect } from 'react'; -import ElectionCheckboxCard from './electionCheckboxCard'; -import { localBostonMunicipalAPI, deployedBostonMunicipalAPI, setGlobalCurrElection } from '@/common'; - - -interface ElectionDateObject { - attributes: { - ElectionDate: Date; - ElectionName: string; - } -} - - -// onCheck will be called when an election's box is checked -interface ElectionCheckboxProps { - onCheck: (electionName: string) => void; -} - - -const ElectionCheckbox: React.FC = ({ onCheck }) => { - const [electionDates, setElectionDates] = useState([]) - const [isLoading, setIsLoading] = useState(true); - const [sortedElectionDates, setSortedElectionDates] = useState([]) - const [selectedElection, setSelectedElection] = useState(null); - - - // Get the election dates from strapi - useEffect(() => { - const fetchElectionDates = async () => { - setIsLoading(true); - try { - const response = await fetch(deployedBostonMunicipalAPI, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (response.ok) { - const electionData = await response.json(); - setElectionDates(electionData.data) - setIsLoading(false); - - } else { - alert('Error fetching election dates') - setIsLoading(false) - } - - } catch (e) { - console.log(e); - setIsLoading(false) - } - } - fetchElectionDates(); - }, []) - - - // Sort the dates and calculate how far away they are - useEffect(() => { - if (electionDates.length > 0) { - const sortedDates = electionDates.sort((a: ElectionDateObject, b: ElectionDateObject) => { - return new Date(a.attributes.ElectionDate).getTime() - new Date(b.attributes.ElectionDate).getTime(); - }); - setSortedElectionDates(sortedDates); - } - }, [electionDates]); - - - // When box is checked, set the election as selected, set the global variable (in common/index.tsx), and call onCheck function - const handleCheckboxChange = (electionName: string) => { - setSelectedElection(electionName); - setGlobalCurrElection(electionName); - onCheck(electionName); - }; - - - return ( -
- {isLoading ? (null - ) : ( -
-
-
-
-
-
- {sortedElectionDates.length === 0 ? ( -

No upcoming elections

- ) : ( -
- {sortedElectionDates.map((election, index) => ( - - ))} -
- )} -
-
-
-
-
-
- )} -
- ) -}; - -export default ElectionCheckbox; \ No newline at end of file diff --git a/client/src/pages/ballotInfo/electionCheckBox/electionCheckboxCard.tsx b/client/src/pages/ballotInfo/electionCheckBox/electionCheckboxCard.tsx deleted file mode 100644 index a6253813..00000000 --- a/client/src/pages/ballotInfo/electionCheckBox/electionCheckboxCard.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* Upcoming election checkbox card. Given the election name and date, calculates - * the time remaining before the election and displays the contents of the card. - * Styles the card contents (name, date, checkbox, dividing lines) -*/ - -'use client'; -import react from 'react'; -import { useState, useEffect } from 'react'; -import Checkbox from '@mui/material/Checkbox'; - -type Props = { - electionName: string; - electionDate: Date; - onCheckboxChange: (electionName: string) => void; - isChecked: boolean; -}; - - -export default function ElectionCheckboxCard({ electionName, electionDate, onCheckboxChange, isChecked }: Props) { - const [displayElectionDate, setDisplayElectionDate] = useState('') - const [remainingDays, setRemainingDays] = useState(0); - const [electionChecked, setElectionChecked] = useState('') - - useEffect(() => { - // Create a new Date object and set the time to midnight local time - const changeDateDisplay = () => { - if (electionDate && electionName) { - - const electionDateObj = new Date(electionDate); - - electionDateObj.setHours(0, 0, 0, 0); - electionDateObj.setDate(electionDateObj.getDate() + 1) - - const formattedElectionDate = electionDateObj.toLocaleDateString('en-US', { - month: 'long', - day: 'numeric', - year: 'numeric' - }); - setDisplayElectionDate(formattedElectionDate); - } - } - - // Calculate time remaining from date till now - const getRemainingDate = () => { - const electionDateObj = new Date(electionDate); - const today = new Date(); - const diffTime = Math.abs(electionDateObj.getTime() - today.getTime()); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - setRemainingDays(diffDays) - } - - changeDateDisplay(); - getRemainingDate(); - }, [electionName, electionDate]); - - - // On change, set the election name - const handleChange = () => { - onCheckboxChange(electionName); - }; - - - return ( -
-
-

{electionName}

- -
-
{/* Line between rows */} -
-

{displayElectionDate}

-

{remainingDays} Days

-
-
- ) -} - diff --git a/client/src/pages/ballotInfo/index.tsx b/client/src/pages/ballotInfo/index.tsx deleted file mode 100644 index 92608290..00000000 --- a/client/src/pages/ballotInfo/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import ButtonFill from "@/components/button/ButtonFill" -import * as React from 'react'; -import ButtonFillEx from "@/components/button/ButtonFillEx"; -import DistrictForm from "./districtForm"; -import ElectionCheckbox from "./electionCheckBox/electionCheckbox"; -import CandidateData from "./whatsOnTheBallot/candidateData"; -import BallotInitative from "./whatsOnTheBallot/ballotInitative"; -import { globalDistrictNum } from "@/common"; -import { Button } from "@mui/material"; -import PopUpBox from "./popUpBox"; - - -export default function BallotInfo() { - const [isPopUpOpen, setIsPopUpOpen] = React.useState(false); - const handleOpen = () => setIsPopUpOpen(true); - const handleClose = () => setIsPopUpOpen(false); - - // Below are checks for form submission and election checkbox completion - const [isFormSubmitted, setIsFormSubmitted] = React.useState(null); - const [selectedElection, setSelectedElection] = React.useState(null); - - const handleFormSubmit = (district: string) => { - setIsFormSubmitted(district); - }; - - const handleCheck = (electionName: string) => { - setSelectedElection(electionName); - }; - - - return ( -
- {/* Header */} -
-
-

LEARN. PLAN.

- -
-

Explore the elections, candidates, and crucial issues personalized to your community.

-
- - - -

Enter your address to find out your district.

- {/* Address form */} -
- - - {isFormSubmitted && ( -

Your Council District: {globalDistrictNum}

- )} -
- - -
-

What's on the Ballot?

-

This sample ballot provides essential information on the races in upcoming elections. - Once you've confirmed your district, toggle between elections below to view the candidates and a detailed overview of their policies and goals. Our content is carefully curated and managed by our team, - headed by Journalist Yawu Miller. -

- {/* Election checkbox card */} - - - - {/* What's on the Ballot dropdown */} -
- {/* Don't make instance of candidate data till form and election are done */} - {(isFormSubmitted && selectedElection) ? ( - <> -

Candidates

- - -

Ballot Initiatives

- - - - - ) : ( -

Please fill out the address form above and select an election to see your ballot information.

- )} -
- -
- {/* Footer */} -
-

You may be wondering...

- - -
-
- ) -} \ No newline at end of file diff --git a/client/src/pages/ballotInfo/popUpBox.tsx b/client/src/pages/ballotInfo/popUpBox.tsx deleted file mode 100644 index 78e6a19e..00000000 --- a/client/src/pages/ballotInfo/popUpBox.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Button, Dialog, DialogActions, DialogContent } from '@mui/material'; -import React from 'react'; - -type Props = { - open: boolean; - onClose: () => void; -} - -const PopUpBox = ({ open, onClose }: Props) => { - return ( - - -

- Ballot initiatives allow Massachusetts residents to propose new laws and amend the state constitution. Petitioners need - signatures from 10 registered voters to submit a question to the attorney general in August of an odd-numbered year, - aiming for the ballot in the next even-numbered year. After approval, petitioners must collect 74,574 signatures by - December for the question to appear on the following year's ballot. -

-
- - - -
- ) -} - -export default PopUpBox; \ No newline at end of file diff --git a/client/src/pages/ballotInfo/whatsOnTheBallot/ballotInitative.tsx b/client/src/pages/ballotInfo/whatsOnTheBallot/ballotInitative.tsx deleted file mode 100644 index f5475525..00000000 --- a/client/src/pages/ballotInfo/whatsOnTheBallot/ballotInitative.tsx +++ /dev/null @@ -1,165 +0,0 @@ -/* Ballot Iniative data is fetched from strapi and displayed as separate - * dropdowns. Styles the dropdowns and their contents. -*/ - -'use client'; -import { useState, useEffect } from 'react'; -import { localBallotInitiativeAPI, deployedBallotInitiativeAPI } from '@/common'; -import * as React from 'react'; -import Accordion from '@mui/material/Accordion'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import Typography from '@mui/material/Typography'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import { Card, CardContent } from '@mui/material'; -import { globalDistrictNum, globalCurrElection } from '@/common'; - -interface Initiative { - id: number; - attributes: { - District: string; - InitiativeName: string; - ProponentEmail: string; - ElectionName: string; - ProponentName: string; - ProponentPhoneNumber: string; - WhatIsNo: string; - WhatIsYes: string; - }; -} - -interface Init { - District: string; - InitiativeName: string; - ProponentEmail: string; - ElectionName: string; - ProponentName: string; - ProponentPhoneNumber: string; - WhatIsNo: string; - WhatIsYes: string; -} - -export default function BallotInitiative() { - const [initiative, setInitiative] = useState([]); - const [districtNum, setDistrictNum] = useState(globalDistrictNum); - const [selectedElection, setSelectedElection] = useState(globalCurrElection); - const [filteredData, setFilteredData] = useState<{ [key: string]: Init[] }>({}); - - - // Get BI data from strapi - useEffect(() => { - const getData = async () => { - try { - const res = await fetch(deployedBallotInitiativeAPI, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (res.ok) { - const data = (await res.json()).data; - console.log(data) - setInitiative(data); - } else { - console.error('Failed to fetch data'); - } - } catch (e) { - console.error('Error fetching data:', e); - } - }; - getData(); - }, []); - - - // Filter the BI to the specific district and election - useEffect(() => { - setDistrictNum(globalDistrictNum); - setSelectedElection(globalCurrElection); - console.log(globalDistrictNum, globalCurrElection); - - if (initiative.length > 0 && globalDistrictNum && globalCurrElection) { - const curData: { [key: string]: Init[] } = {}; - - initiative.forEach((item) => { - const initDistrict = item.attributes.District; - const initElection = item.attributes.ElectionName; - if ((initDistrict.trim().toString() === globalDistrictNum || initDistrict.trim().toString() === 'All Districts') && initElection.trim().toString() === globalCurrElection?.trim()) { - if (curData[initDistrict]) { - curData[initDistrict].push(item.attributes); - } else { - curData[initDistrict] = [item.attributes]; - } - } - }); - setFilteredData(curData); - } - }, [initiative]); - - - return ( -
- {Object.keys(filteredData).length > 0 ? ( - Object.entries(filteredData).map(([key, items]) => ( - items.map((item, index) => ( - - - {/* Title */} - } - aria-controls={`panel${key}-${index}-content`} - id={`panel${key}-${index}-header`} - > - {item.InitiativeName} - - - {/* Content */} - - {/* Proponent Info */} -
- Proponent's Contact: - {item.ProponentName} -
- {item.ProponentEmail} -
- {item.ProponentPhoneNumber} -
-
- -
- {/* YES */} - - - What is a vote YES? -
    - {item.WhatIsYes.split('\n').map((line, index) => ( -
  • {line}
  • - ))} -
-
-
- - {/* NO */} - - - What is a vote NO? -
    - {item.WhatIsNo.split('\n').map((line, index) => ( -
  • {line}
  • - ))} -
-
-
-
-
-
- )) - - )) - ) : ( - // Case where no BI for the given election and district -
There is no data about the ballot for the district and election you have selected.
- )} -
- ); -} diff --git a/client/src/pages/ballotInfo/whatsOnTheBallot/candidateData.tsx b/client/src/pages/ballotInfo/whatsOnTheBallot/candidateData.tsx deleted file mode 100644 index dafac48c..00000000 --- a/client/src/pages/ballotInfo/whatsOnTheBallot/candidateData.tsx +++ /dev/null @@ -1,197 +0,0 @@ -/* Gets all candidates from strapi, queries them based on the voter's district - * and selected election, and displays the filtered results. Styles the outer - * dropdowns of candidates and the description of the role. -*/ - -'use client' -import { useEffect, useState } from 'react'; -import { localCandidateAPI, deployedCandidateAPI, localCandidateRoleAPI, deployedCandidateRoleAPI, globalDistrictNum, globalCurrElection } from '@/common'; -import Accordion from '@mui/material/Accordion'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import Typography from '@mui/material/Typography'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import PeopleCard from './peopleCard'; - - -interface CandidateAttributes { - CampaignSiteLink: string | null; - District: string; - ElectionName: string; - LinkedinLink: string | null; - Name: string; - Party: string; - Role: string; - createdAt: string; - publishedAt: string; - updatedAt: string; - Headshot: { - data: { - attributes: { - url: string - } - } - - } -} - -interface CandidateDataObject { - id: number; - attributes: CandidateAttributes; -} - -interface Candidate { - attributes: CandidateAttributes; -} - - -export default function CandidateData() { - const [allCandidateData, setAllCandidateData] = useState([]) - const [filteredCandidateData, setFilteredCandidateData] = useState<{ [key: string]: Candidate[] }>({}) - const [districtNum, setDistrictNum] = useState(globalDistrictNum); - const [selectedElection, setSelectedElection] = useState(globalCurrElection); - const [candidateRoleDate, setCandidateRoleData] = useState<{ [key: string]: string }>({}) - - - // Fetch candidate data from strapi - useEffect(() => { - const getData = async () => { - try { - const response = await fetch(deployedCandidateAPI + '?populate=*', { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (response.ok) { - const data = await response.json() - setAllCandidateData(data.data) - } - } - catch (e) { - console.log(e) - } - }; - - // Actually fetch data only if districtNum and election are set - if (districtNum && selectedElection) { - getData(); - } - }, [districtNum, selectedElection]); - - - // Set the district number to the global number which was set in DistrictForm - useEffect(() => { - setDistrictNum(globalDistrictNum); - setSelectedElection(globalCurrElection); - }, [globalDistrictNum, globalCurrElection]); - - - /* Query data, store data to new variable as nested hashtable based on the election date and district. - * Loop through the data, match the election data and district type, then check to see if their role - * is already in the hashtable. If yes, add another person to the value . If no, initialize the key - * with the person and value */ - useEffect(() => { - const sortedData: { [key: string]: Candidate[] } = {}; - const roleData: { [key: string]: string } = {}; - - // Get candidate role from strapi - const getData = async () => { - try { - const response = await fetch(deployedCandidateRoleAPI, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (response.ok) { - const data = (await response.json()).data; - data.forEach((role: any) => { - roleData[role.attributes.Role_Name] = role.attributes.Role_Description; - }) - - setCandidateRoleData(roleData); - } - } catch (e) { - console.log(e); - } - } - getData(); - - // Filter candidates and add to hash table - if (allCandidateData.length > 0 && districtNum) { - allCandidateData.forEach((candidateDataObject: CandidateDataObject) => { - const candidateDistrict = candidateDataObject.attributes.District.trim(); - const candidateElection = candidateDataObject.attributes.ElectionName.trim(); - if ((candidateDistrict === districtNum || candidateDistrict === 'All Districts') && candidateElection === selectedElection?.trim()) { - const candidate: Candidate = { - attributes: candidateDataObject.attributes - }; - - if (sortedData[candidate.attributes.Role]) { - sortedData[candidate.attributes.Role].push(candidate); - } else { - sortedData[candidate.attributes.Role] = [candidate]; - } - } - }); - setFilteredCandidateData(sortedData); - } - - //console.log(sortedData); - }, [allCandidateData, districtNum, selectedElection]) - - - useEffect(() => { - //console.log(filteredCandidateData); - - }, [filteredCandidateData]) - - - return ( -
- - {/* Map over the filtered candidates */} - {Object.keys(filteredCandidateData).length > 0 ? ( - <> - {Object.keys(filteredCandidateData).map((role, index) => ( - - } - aria-controls={`panel${index + 1}-content`} - id={`panel${index + 1}-header`} - > - {role} - - - - {/* Description of the role */} - - {candidateRoleDate[role] ? candidateRoleDate[role] : 'No description available for this role'} - - - {/* Map over the candidates for each role */} -
- {filteredCandidateData[role].map((candidate, idx) => ( -
- -
- ))} -
-
-
- ))} - - ) : ( -

There is no data about the ballot for the district and election you have selected.

- )} -
- ) -} \ No newline at end of file diff --git a/client/src/pages/ballotInfo/whatsOnTheBallot/peopleCard.tsx b/client/src/pages/ballotInfo/whatsOnTheBallot/peopleCard.tsx deleted file mode 100644 index bce5d327..00000000 --- a/client/src/pages/ballotInfo/whatsOnTheBallot/peopleCard.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* Card for each individual candidate. Includes name, party, picture as a short - * profile to be displayed in the dropdown. When clicked, it takes you to a - * deeper profile including the rest of the info from strapi. Styles the small - * profile card. -*/ - -'use client'; -import * as React from 'react'; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import CardMedia from '@mui/material/CardMedia'; -import Typography from '@mui/material/Typography'; -import { Avatar, CardActionArea, Stack } from '@mui/material'; -import { useRouter } from 'next/navigation'; -import { useEffect } from 'react'; - - -type Props = { - name: string; - affiliation: string; - picture: string; - link: string; -}; - - -const PeopleCard = ({ name, affiliation, picture, link }: Props) => { - const router = useRouter(); - const handleClick = (page: string) => { - const candidatePath = `/ballotInfo/${name.replace(/\s+/g, '')}`; - router.push(candidatePath); - } - - useEffect(() => { - //console.log(`http://localhost:1337${picture}`) - }, []) - - return ( - handleClick(link)} sx={{ - width: 300, height: 250, margin: 'auto', - backgroundColor: '#f4f4f4', // Blue background color - color: 'white', // Text color - transition: 'background-color 0.3s ease', // Smooth transition - '&:hover': { - backgroundColor: '#ffffff', // Blue color on hover - }, - }}> - - - - - - - {name} - - - {affiliation} - - - - - ); -} - -export default PeopleCard; \ No newline at end of file diff --git a/client/src/pages/dropBoxLocations/index.tsx b/client/src/pages/dropBoxLocations/index.tsx index 144ad9d1..6e89f6f2 100644 --- a/client/src/pages/dropBoxLocations/index.tsx +++ b/client/src/pages/dropBoxLocations/index.tsx @@ -33,7 +33,7 @@ export default function DropBoxLocations() { {/* Footer */}

You may be wondering...

- +
diff --git a/client/src/pages/voterInfo/index.tsx b/client/src/pages/voterInfo/index.tsx index 86c234c6..fd80e6a2 100644 --- a/client/src/pages/voterInfo/index.tsx +++ b/client/src/pages/voterInfo/index.tsx @@ -85,10 +85,7 @@ export default function VoterInfo() { {/* Footer */} -
-

Now that you know where you can vote, let's explore exactly who and what you are voting for.

- -
+
) From fa183b36fd60cb0e6341c2eb28dfec31dd66bbfb Mon Sep 17 00:00:00 2001 From: celine Date: Thu, 7 Nov 2024 10:42:53 -0500 Subject: [PATCH 12/17] fixed spot that was linking to BI page --- client/src/pages/upcomingElections/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/upcomingElections/index.tsx b/client/src/pages/upcomingElections/index.tsx index 05613bbb..7829a211 100644 --- a/client/src/pages/upcomingElections/index.tsx +++ b/client/src/pages/upcomingElections/index.tsx @@ -82,7 +82,7 @@ export default function UpcomingElections() { {/* Footer */}

You may be wondering...

- +
From f28ab409f639a4ec16d3d1bcfa4bf8396fa3e122 Mon Sep 17 00:00:00 2001 From: Tiffany Yu Date: Mon, 18 Nov 2024 15:51:21 -0500 Subject: [PATCH 13/17] changed layout of candidate info page --- client/src/pages/candidateInfo/index.tsx | 165 ++++++++++++++--------- 1 file changed, 102 insertions(+), 63 deletions(-) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index 6cb1022f..f7d1514f 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -80,82 +80,121 @@ export default function CandidateInfo() { if (error) return

{error}

; return ( -
- {/* Header */} -
-
-
-

LEARN. PLAN.

-
-

- Explore the election, candidates, and crutial -
- issues personalized to your community! -

+ +
+ {/* Header */} +
+
+
+

LEARN. PLAN.

+

+ Explore the election, candidates, and crucial +
+ issues personalized to your community! +

+
+ +
+ {candidates.length > 0 ? ( + candidates.map(candidate => ( +
toggleExpand(candidate.id)}> -
- {candidates.length > 0 ? ( - candidates.map(candidate => ( + {/* Candidate Image, Name, Party, and Arrow */}
toggleExpand(candidate.id)} - > -
-
-

{candidate.attributes.Name}

-

Party: {candidate.attributes.Party}

-
+ style={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: '15px' + }}> + {/* Candidate Image */} +
{candidate.attributes.PhotoURL && ( {candidate.attributes.Name} )} -
- {/* Expandable Details */} - {expandedCandidateId === candidate.id && ( -
-

District: {candidate.attributes.District}

-

Office Running For: {candidate.attributes.ElectionName}

-

Bio: {candidate.attributes.Bio}

- -
-

Questionnaire

- {Array.from({ length: 10 }).map((_, i) => { - const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; - const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; - const question = candidate.attributes[questionKey]; - const answer = candidate.attributes[answerKey]; - return ( - question && answer ? ( -
-

{question}

-

{answer}

-
- ) : null - ); - })} -
+
+

{candidate.attributes.Name}

+

{candidate.attributes.Party}

+

{candidate.attributes.ElectionName}

- )} +
+ + {/* Dropdown Arrow */} +
+ {expandedCandidateId === candidate.id ? '▲' : '▼'} +
- )) - ) : ( -

No candidates available.

- )} -
+ + {/* Expandable Details */} + {expandedCandidateId === candidate.id && ( +
+

District: {candidate.attributes.District}

+

Bio: {candidate.attributes.Bio}

+ +
+

Questionnaire

+ {Array.from({ length: 10 }).map((_, i) => { + const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; + const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; + const question = candidate.attributes[questionKey]; + const answer = candidate.attributes[answerKey]; + return ( + question && answer ? ( +
+

{question}

+

{answer}

+
+ ) : null + ); + })} +
+
+ )} +
+ )) + ) : ( +

No candidates available.

+ )}
+
+ ); } From 6d54a07539a1fef48e0f2222e1625929da919314 Mon Sep 17 00:00:00 2001 From: Grace Murphy Date: Mon, 18 Nov 2024 16:10:57 -0500 Subject: [PATCH 14/17] adding initial filter --- client/README.md | 2 +- client/src/pages/candidateInfo/index.tsx | 228 ++++++++++++----------- 2 files changed, 120 insertions(+), 110 deletions(-) diff --git a/client/README.md b/client/README.md index c4033664..12489ce9 100644 --- a/client/README.md +++ b/client/README.md @@ -1,4 +1,4 @@ -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). +npm installThis 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). ## Getting Started diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index f7d1514f..55672cf7 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -16,12 +16,22 @@ interface Candidate { }; } +const parties = ['Democrat', 'Republican', 'Independent', 'Non Partisan', 'Other']; +const electionTypes = ['Local', 'State', 'National']; +const districts = ['District 1', 'District 2', 'District 3', 'District 4']; // Example districts, replace with actual + // Component for Candidate Information Page export default function CandidateInfo() { const [candidates, setCandidates] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - const [expandedCandidateId, setExpandedCandidateId] = useState(null); // State for managing expanded candidate + const [expandedCandidateId, setExpandedCandidateId] = useState(null); + const [filteredCandidates, setFilteredCandidates] = useState([]); + const [filters, setFilters] = useState({ + party: '', + electionType: '', + district: '', + }); useEffect(() => { const fetchCandidateData = async () => { @@ -35,7 +45,6 @@ export default function CandidateInfo() { console.log("Fetched data:", data); if (data.data && data.data.length > 0) { - // Map through fetched candidates to add the Headshot URL const fetchedCandidates: Candidate[] = data.data.map((candidate: any) => { const headshotUrl = candidate.attributes.Headshot?.data?.attributes?.url ? `https://pitne-voter-app-production.up.railway.app${candidate.attributes.Headshot.data.attributes.url}` @@ -44,12 +53,13 @@ export default function CandidateInfo() { ...candidate, attributes: { ...candidate.attributes, - PhotoURL: headshotUrl, // Add headshot URL to PhotoURL attribute + PhotoURL: headshotUrl, }, }; }); console.log("Fetched Candidates with Headshot URL:", fetchedCandidates); setCandidates(fetchedCandidates); + setFilteredCandidates(fetchedCandidates); // Set initial filtered candidates } else { console.warn("No candidate data available."); setError("No candidate data available."); @@ -69,132 +79,132 @@ export default function CandidateInfo() { fetchCandidateData(); }, []); - console.log("Loading state:", isLoading); - console.log("Candidates data:", candidates); - const toggleExpand = (candidateId: number) => { - setExpandedCandidateId(prevId => (prevId === candidateId ? null : candidateId)); // Toggle expand/collapse + setExpandedCandidateId(prevId => (prevId === candidateId ? null : candidateId)); + }; + + const handleFilterChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFilters(prevFilters => ({ + ...prevFilters, + [name]: value, + })); }; + // Filter candidates based on selected filters + useEffect(() => { + const filtered = candidates.filter(candidate => { + const matchesParty = filters.party ? candidate.attributes.Party === filters.party : true; + const matchesElection = filters.electionType ? candidate.attributes.ElectionName === filters.electionType : true; + const matchesDistrict = filters.district ? candidate.attributes.District === filters.district : true; + return matchesParty && matchesElection && matchesDistrict; + }); + setFilteredCandidates(filtered); + }, [filters, candidates]); + if (isLoading) return

Loading...

; if (error) return

{error}

; return ( - -
- {/* Header */} -
-
-
-

LEARN. PLAN.

+
+ {/* Sidebar for Filters */} +
+

Filter Candidates

+ +
+ + +
+ +
+ + +
+ +
+ +
-

- Explore the election, candidates, and crucial -
- issues personalized to your community! -

-
-
- {candidates.length > 0 ? ( - candidates.map(candidate => ( -
toggleExpand(candidate.id)}> - - {/* Candidate Image, Name, Party, and Arrow */} + {/* Main Content */} +
+ {filteredCandidates.length > 0 ? ( + filteredCandidates.map(candidate => (
- {/* Candidate Image */} -
+ key={candidate.id} + className="candidate-card" + style={{ backgroundColor: 'White', + boxShadow: '0px 4px 5px rgba(0, 0, 0, 0.5)', + border: '2px solid #ccc', + padding: '10px', + margin: '10px', + cursor: 'pointer', + borderRadius: '5px' }} + onClick={() => toggleExpand(candidate.id)} + > +
+
+

{candidate.attributes.Name}

+

Party: {candidate.attributes.Party}

+
{candidate.attributes.PhotoURL && ( {candidate.attributes.Name} )} - -
-

{candidate.attributes.Name}

-

{candidate.attributes.Party}

-

{candidate.attributes.ElectionName}

-
- {/* Dropdown Arrow */} -
- {expandedCandidateId === candidate.id ? '▲' : '▼'} -
-
- - {/* Expandable Details */} - {expandedCandidateId === candidate.id && ( -
-

District: {candidate.attributes.District}

-

Bio: {candidate.attributes.Bio}

- -
-

Questionnaire

- {Array.from({ length: 10 }).map((_, i) => { - const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; - const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; - const question = candidate.attributes[questionKey]; - const answer = candidate.attributes[answerKey]; - return ( - question && answer ? ( -
-

{question}

-

{answer}

-
- ) : null - ); - })} + {/* Expandable Details */} + {expandedCandidateId === candidate.id && ( +
+

District: {candidate.attributes.District}

+

Office Running For: {candidate.attributes.ElectionName}

+

Bio: {candidate.attributes.Bio}

+ +
+

Questionnaire

+ {Array.from({ length: 10 }).map((_, i) => { + const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; + const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; + const question = candidate.attributes[questionKey]; + const answer = candidate.attributes[answerKey]; + return ( + question && answer ? ( +
+

{question}

+

{answer}

+
+ ) : null + ); + })} +
-
- )} -
- )) - ) : ( -

No candidates available.

- )} + )} +
+ )) + ) : ( +

No candidates available.

+ )} +
-
- ); } + From 95234e1cfeb3dced69be61a5f6176c1b0da2a7a1 Mon Sep 17 00:00:00 2001 From: Grace Murphy Date: Mon, 18 Nov 2024 16:36:08 -0500 Subject: [PATCH 15/17] fixing formatting --- client/src/pages/candidateInfo/index.tsx | 50 ++++++++++++++---------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index 55672cf7..afb9c10f 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -17,7 +17,7 @@ interface Candidate { } const parties = ['Democrat', 'Republican', 'Independent', 'Non Partisan', 'Other']; -const electionTypes = ['Local', 'State', 'National']; +const electionTypes = ['Federal Election', 'State Election', 'Municipal Election', 'Special Election', 'Primary Election', 'Ballot Questions/Referendum']; const districts = ['District 1', 'District 2', 'District 3', 'District 4']; // Example districts, replace with actual // Component for Candidate Information Page @@ -149,34 +149,43 @@ export default function CandidateInfo() {
toggleExpand(candidate.id)} > + {/* Candidate Card Layout */}
-
-

{candidate.attributes.Name}

-

Party: {candidate.attributes.Party}

+
+ {candidate.attributes.PhotoURL && ( + {candidate.attributes.Name} + )} +
+

{candidate.attributes.Name}

+

Party: {candidate.attributes.Party}

+

Election: {candidate.attributes.ElectionName}

+
+
+ {/* Arrow Icon */} +
+ ▼
- {candidate.attributes.PhotoURL && ( - {candidate.attributes.Name} - )}
{/* Expandable Details */} {expandedCandidateId === candidate.id && ( -
+

District: {candidate.attributes.District}

-

Office Running For: {candidate.attributes.ElectionName}

Bio: {candidate.attributes.Bio}

@@ -207,4 +216,3 @@ export default function CandidateInfo() {
); } - From 454f48dd89c4f1aa931e8d0e2a21c611d2fd385c Mon Sep 17 00:00:00 2001 From: celine Date: Mon, 18 Nov 2024 16:51:34 -0500 Subject: [PATCH 16/17] new logo --- client/public/bva_logo.png | Bin 0 -> 27104 bytes client/src/components/nav/NavBar.tsx | 103 +++++++-------------------- 2 files changed, 26 insertions(+), 77 deletions(-) create mode 100644 client/public/bva_logo.png diff --git a/client/public/bva_logo.png b/client/public/bva_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..72c1dbd296dac6684e69e44304da71a22ba5eea2 GIT binary patch literal 27104 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 VES7xtIT#u6fT@+q3uEW-{{TMMn9u+K literal 0 HcmV?d00001 diff --git a/client/src/components/nav/NavBar.tsx b/client/src/components/nav/NavBar.tsx index 97c7bc37..e9d6c4f0 100644 --- a/client/src/components/nav/NavBar.tsx +++ b/client/src/components/nav/NavBar.tsx @@ -6,7 +6,6 @@ import * as React from 'react'; import { AppBar, Box, Button, MenuItem, Toolbar, IconButton, Typography, Menu, Container } from '@mui/material'; import MenuIcon from '@mui/icons-material/Menu'; -import StarIcon from '@mui/icons-material/Star'; import { useRouter } from 'next/navigation'; import { keyframes } from '@mui/system'; import { usePathname } from 'next/navigation'; @@ -31,11 +30,10 @@ const links: Record = { 'Drop Box Locations': '/dropBoxLocations' }; - function NavBar() { const [anchorElNav, setAnchorElNav] = React.useState(null); - const router = useRouter() + const router = useRouter(); const handleOpenNavMenu = (event: React.MouseEvent) => { setAnchorElNav(event.currentTarget); @@ -48,42 +46,25 @@ function NavBar() { const handleClick = (page: string) => { handleCloseNavMenu(); router.push(links[page]); - } - - - // Below is testing for active page link - const currentPath = usePathname(); - const isActive = (path: string | null) => { - return currentPath === path; - } + }; + const currentPath = usePathname(); + const isActive = (path: string | null) => { + return currentPath === path; + }; return ( - {/* BELOW IS FOR STANDARD NAVBAR */} - {/* REPLACE WITH STAR LOGO */} - { - handleClick('Upcoming Elections'); - }} - - > - Boston Voter - + + Boston Voter Logo handleClick('Upcoming Elections')} + /> + {/* Page links below */} @@ -123,55 +104,23 @@ function NavBar() { - - {/* BELOW IS FOR RESPONSIVE NAVBAR (CONDENSED DROP DOWN) */} - - {/* REPLACE WITH STAR LOGO */} - { - handleClick('Upcoming Elections'); - }} > - Boston Voter - - - + {/* Desktop navigation */} {pages.map((page) => ( @@ -182,5 +131,5 @@ function NavBar() { ); } -export default NavBar; +export default NavBar; From 6088a2009ff3697a8eef1642bcc7c8da483889c1 Mon Sep 17 00:00:00 2001 From: Grace Murphy Date: Mon, 18 Nov 2024 17:07:21 -0500 Subject: [PATCH 17/17] formatting fix --- client/src/pages/candidateInfo/index.tsx | 87 +++++++++++++++--------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/client/src/pages/candidateInfo/index.tsx b/client/src/pages/candidateInfo/index.tsx index afb9c10f..be731185 100644 --- a/client/src/pages/candidateInfo/index.tsx +++ b/client/src/pages/candidateInfo/index.tsx @@ -106,14 +106,27 @@ export default function CandidateInfo() { if (error) return

{error}

; return ( -
+
{/* Sidebar for Filters */} -
-

Filter Candidates

+
+

Filter Candidates

-
- - {parties.map(party => ( @@ -121,9 +134,15 @@ export default function CandidateInfo() {
-
- - {electionTypes.map(type => ( @@ -131,9 +150,15 @@ export default function CandidateInfo() {
-
- - {districts.map(district => ( @@ -153,10 +178,10 @@ export default function CandidateInfo() { backgroundColor: 'White', boxShadow: '0px 4px 5px rgba(0, 0, 0, 0.5)', border: '2px solid #ccc', - padding: '10px', - margin: '10px', + padding: '15px', + marginBottom: '20px', cursor: 'pointer', - borderRadius: '5px', + borderRadius: '10px', }} onClick={() => toggleExpand(candidate.id)} > @@ -167,7 +192,13 @@ export default function CandidateInfo() { {candidate.attributes.Name} )}
@@ -184,33 +215,25 @@ export default function CandidateInfo() { {/* Expandable Details */} {expandedCandidateId === candidate.id && ( -
+

District: {candidate.attributes.District}

Bio: {candidate.attributes.Bio}

Questionnaire

- {Array.from({ length: 10 }).map((_, i) => { - const questionKey = `Question${i + 1}` as keyof Candidate['attributes']; - const answerKey = `Answer${i + 1}` as keyof Candidate['attributes']; - const question = candidate.attributes[questionKey]; - const answer = candidate.attributes[answerKey]; - return ( - question && answer ? ( -
-

{question}

-

{answer}

-
- ) : null - ); - })} + {Array.from({ length: 5 }, (_, index) => ( +
+ Question {index + 1}: +

{candidate.attributes[`Q${index + 1}`]}

+
+ ))}
)}
)) ) : ( -

No candidates available.

+

No candidates found matching the selected filters.

)}