diff --git a/.firebase/hosting.ZGlzdA.cache b/.firebase/hosting.ZGlzdA.cache
index 7386bad..2a5282b 100644
--- a/.firebase/hosting.ZGlzdA.cache
+++ b/.firebase/hosting.ZGlzdA.cache
@@ -1,4 +1,5 @@
-index.html,1687738745527,016072d1a763c361890ca06a62a7509cec0382e92d1e8e12b7e0e5a53048f1ac
-assets/index-8891e0f8.css,1687738745527,1a075d488a7b08e62a37a19d72261dc174ddbfe1f61c1342a2920099475124e3
-vite.svg,1687738745365,d3bbbc44b3ea71906a72bf2ec1a4716903e2e3d9f85a5007205a65d1f12e2923
-assets/index-12ee572c.js,1687738745527,d261caadfead5a996917ab86646080185c8b095760e73d8b2aa8c676d5173f50
+favicon.png,1688159708032,f3c2237334cbc2f8708df1eceaaeac907fac4a7bf117fa24820c3f6f2f1527a8
+vite.svg,1688159708032,d3bbbc44b3ea71906a72bf2ec1a4716903e2e3d9f85a5007205a65d1f12e2923
+index.html,1688159708161,57754b7fb7c46b0206381ec771f8869b706661e6564aa2f21d651551cee35957
+assets/index-8891e0f8.css,1688159708161,1a075d488a7b08e62a37a19d72261dc174ddbfe1f61c1342a2920099475124e3
+assets/index-2acb8918.js,1688159708161,d383cc21fd468f50796218728cb2f99f00b299840e31bc209bbc7150448b9284
diff --git a/index.html b/index.html
index d3308e1..871c2e3 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Vite + React + TS
+ Civics Central
diff --git a/package-lock.json b/package-lock.json
index 000c014..80d28a9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -44,6 +44,15 @@
"vitest": "^0.32.2"
}
},
+ "node_modules/@aashutoshrathi/word-wrap": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/@adobe/css-tools": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz",
@@ -5163,17 +5172,17 @@
}
},
"node_modules/optionator": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
- "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+ "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
"dev": true,
"dependencies": {
+ "@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.3"
+ "type-check": "^0.4.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -6662,15 +6671,6 @@
"node": ">=8"
}
},
- "node_modules/word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/src/App.tsx b/src/App.tsx
index 2ea4885..3eaa124 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -6,16 +6,17 @@ import { WhyVote } from "./Pages/WhyVote/WhyVote.tsx";
import { Example } from "./Pages/Example/Example.tsx";
import { ContactUs } from "./Pages/ContactUs/ContactUs.tsx";
import { Box } from "@mui/material";
-import { GlobalContext } from "./context/GlobalContext.tsx";
+import { GlobalContextWrapper } from "./context/GlobalContextWrapper.tsx";
import { Footer } from "./components/footer/Footer.tsx";
+import { CivicInfo } from "./Pages/CivicInfo/CivicInfo.tsx";
function App() {
return (
-
+
@@ -29,11 +30,13 @@ function App() {
} />
} />
} />
+ } />
+ } />
-
+
);
}
diff --git a/src/Interfaces.ts b/src/Interfaces.ts
index 80fca70..ab8c52f 100644
--- a/src/Interfaces.ts
+++ b/src/Interfaces.ts
@@ -19,6 +19,8 @@ export interface UserData {
};
}
+// ================== Representatives Data =====================
+
export interface RepresentativeDataResponse {
normalizedInput: NormalizedAddressInput;
kind: string;
@@ -55,3 +57,19 @@ export interface Channel {
type: string;
id: string;
}
+
+// ================== Representatives Data =====================
+
+// ================== Available Elections Data =================
+export interface AvailableElectionsDataResponse {
+ kind: string;
+ elections: Election[];
+}
+
+export interface Election {
+ id: string;
+ name: string;
+ electionDay: string;
+ ocdDivisionId: string;
+}
+// ================== Available Elections Data =================
diff --git a/src/Pages/CivicInfo/CivicInfo.tsx b/src/Pages/CivicInfo/CivicInfo.tsx
new file mode 100644
index 0000000..db7be75
--- /dev/null
+++ b/src/Pages/CivicInfo/CivicInfo.tsx
@@ -0,0 +1,40 @@
+import { Box, Grid, Typography } from "@mui/material";
+import {
+ useAvailableElectionsContext,
+ useRepresentativeDataContext,
+} from "../../context/customHooks.ts";
+import { useEffect } from "react";
+import { AddressSearchBar } from "../../components/addressSearchBar/AddressSearchBar.tsx";
+
+export const CivicInfo = () => {
+ const representativeData = useRepresentativeDataContext();
+ const availableElections = useAvailableElectionsContext();
+ useEffect(() => {
+ console.log({ representativeData });
+ console.log({ availableElections });
+ }, [representativeData, availableElections]);
+ return (
+
+
+
+
+
+ Representative Data
+
+
+ Upcoming Elections
+
+
+ Election Info
+
+
+
+ );
+};
diff --git a/src/Pages/Example/Example.tsx b/src/Pages/Example/Example.tsx
index 15cbcfb..eccb149 100644
--- a/src/Pages/Example/Example.tsx
+++ b/src/Pages/Example/Example.tsx
@@ -12,7 +12,7 @@ import { UserData } from "../../Interfaces.ts";
import axios from "axios";
import { ExampleList } from "./exampleList/ExampleList.tsx";
import { ExampleSearchBar } from "./exampleSearchBar/ExampleSearchBar.tsx";
-import { useRepresentativeDataResponse } from "../../context/customHooks.ts";
+import { useRepresentativeDataContext } from "../../context/customHooks.ts";
export function Example() {
const [randomUsers, setRandomUsers] = useState([]);
@@ -24,7 +24,7 @@ export function Example() {
const theme = useTheme();
// Pulling the representative data from react context
- const representativeData = useRepresentativeDataResponse();
+ const representativeData = useRepresentativeDataContext();
// Check the console to view the theme object
useEffect(() => {
diff --git a/src/Pages/Home/Home.test.tsx b/src/Pages/Home/Home.test.tsx
index 3638ed4..7df51f1 100644
--- a/src/Pages/Home/Home.test.tsx
+++ b/src/Pages/Home/Home.test.tsx
@@ -1,9 +1,10 @@
import { render, screen } from "@testing-library/react";
import { Home } from "./Home.tsx";
+import { BrowserRouter } from "react-router-dom";
describe("App", () => {
it("renders headline", () => {
- render();
+ render(, { wrapper: BrowserRouter });
screen.debug();
diff --git a/src/Pages/Home/Home.tsx b/src/Pages/Home/Home.tsx
index e653a3f..7dea3f8 100644
--- a/src/Pages/Home/Home.tsx
+++ b/src/Pages/Home/Home.tsx
@@ -1,5 +1,5 @@
import { Box, Typography } from "@mui/material";
-import { HomePageSearchBar } from "../../components/homepageSearchBar/HomepageSearchBar.tsx";
+import { AddressSearchBar } from "../../components/addressSearchBar/AddressSearchBar.tsx";
export function Home() {
return (
@@ -17,7 +17,7 @@ export function Home() {
Enter your residential address and we'll take it from here!
-
+
{
+interface AddressSearchBarProps {
+ isHomePage: boolean;
+}
+
+export const AddressSearchBar = ({ isHomePage }: AddressSearchBarProps) => {
// =================== React States ===================
const [searchBarValue, setSearchBarValue] = useState("");
const [placeholder, setPlaceholder] = useState(
@@ -39,15 +44,23 @@ export const HomePageSearchBar: React.FC = () => {
const [googleApiErrorMessage, setGoogleApiErrorMessage] =
useState("");
+ const [representativeCallSuccessful, setRepresentativeCallSuccessful] =
+ useState(false);
+ const [electionCallSuccessful, setElectionCallSuccessful] =
+ useState(false);
+
// =================== React States ===================
// =================== React Hooks ===================
- const representativeDataResponse = useRepresentativeDataResponse();
+ // Allows us to navigate to a new url
+ const navigate = useNavigate();
- // TODO - figure out what type this should be
- const setRepresentativeDataResponse: any = useSetRepresentativeDataResponse();
+ // Getting access to setters from context
+ const setRepresentativeDataResponse: any = useSetRepresentativeDataContext();
+ const setAvailableElections: any = useSetAvailableElectionsContext();
+ // For changing address
useEffect(() => {
let placeholderIndex = 0;
const interval = setInterval(() => {
@@ -58,29 +71,33 @@ export const HomePageSearchBar: React.FC = () => {
return () => clearInterval(interval);
}, []);
+ // Only navigate if both our calls were successful and we are still on the home page
useEffect(() => {
- console.log(representativeDataResponse);
- }, [representativeDataResponse]);
+ if (representativeCallSuccessful && electionCallSuccessful && isHomePage) {
+ navigate("/civicInformation");
+ }
+ });
// =================== React Hooks ===================
// =================== Helper Functions ==============
+ // Updates the state of the search bar value when typing
const handleChange = (event: React.ChangeEvent) => {
setSearchBarValue(event.target.value);
};
const handleClick = () => {
const address = encodeAddress(searchBarValue);
- quereyRepresentativeAPI(address);
+ queryRepresentativeAPI(address);
};
+ // Have to change spaces in the entered address to %20 to send over and http call
const encodeAddress = (value: string) => {
- const encodedAddress = value.trim().replaceAll(" ", "%20");
- return encodedAddress;
+ return value.trim().replaceAll(" ", "%20");
};
- const quereyRepresentativeAPI = (address: string) => {
+ const queryRepresentativeAPI = (address: string) => {
setButtonIsDisabled(true);
axios
.get(
@@ -91,12 +108,39 @@ export const HomePageSearchBar: React.FC = () => {
.then((res) => {
console.log(res);
setRepresentativeDataResponse(res.data);
+ setRepresentativeCallSuccessful(true);
})
.catch((err) => {
+ // Catch any errors in the api chain
console.log(err);
setGoogleApiErrorMessage(err.response.data.error.message);
setSnackBarIsOpen(true);
})
+ .finally(() => {
+ // Call the query for elections API after the representative call is successful
+ queryAvailableElectionsAPI();
+ });
+ };
+
+ const queryAvailableElectionsAPI = () => {
+ axios
+ .get(
+ `https://www.googleapis.com/civicinfo/v2/elections?key=${
+ import.meta.env.VITE_CIVICS_API_KEY
+ }`
+ )
+ .then((res) => {
+ // Setting the state on successful response
+ setAvailableElections(res.data);
+ setElectionCallSuccessful(true);
+ })
+ .catch((err) => {
+ console.log(err);
+ if (!snackBarIsOpen) {
+ setGoogleApiErrorMessage(err.response.data.error.message);
+ setSnackBarIsOpen(true);
+ }
+ })
.finally(() => {
setButtonIsDisabled(false);
});
@@ -107,8 +151,28 @@ export const HomePageSearchBar: React.FC = () => {
};
// =================== Helper Functions ==============
+
+ // =================== Custom Styles ==============
+ const homePageStyling = {
+ width: "80%",
+ marginTop: "50px",
+ display: "block",
+ flexDirection: "column",
+ textAlign: "right",
+ margin: "auto",
+ };
+
+ const informationPageStyling = {
+ width: "80%",
+ marginTop: "50px",
+ display: "flex",
+ flexDirection: { xs: "column", sm: "row" },
+ textAlign: "center",
+ margin: "50px auto",
+ };
+
return (
-
+
{
),
}}
/>
- }
+ onClick={handleClick}
+ disabled={buttonIsDisabled || searchBarValue.trim().length === 0}
>
- }
- onClick={handleClick}
- disabled={buttonIsDisabled || searchBarValue.length === 0}
- >
- Find my elections info!
-
-
+ Find my elections info!
+
+
-
+
@@ -36,9 +40,6 @@ export function Navbar() {
Civics Central
-