Skip to content

Commit

Permalink
Merge branch 'main' into taha/piecomponent
Browse files Browse the repository at this point in the history
  • Loading branch information
santi-g-s committed Apr 19, 2023
2 parents 70f7958 + 40659a6 commit 9e32328
Show file tree
Hide file tree
Showing 14 changed files with 456 additions and 90 deletions.
23 changes: 8 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
# Boilerplate
# Beat The Streets - Dashboard

This is a simple boilerplate designed to serve as robust template for quickly starting development on a [Typescript](https://www.typescriptlang.org) based [MERN](https://www.mongodb.com/mern-stack) web application.
<img src="https://user-images.githubusercontent.com/28734546/232344772-312c55da-6a2a-4a36-a828-3203b54503c9.png" width=50% height=50% >

## Features
This is a project for [Beat the Streets](https://beatthestreets.org/) that aggregates and displays statistics and data points related to both current locations and target expansion locations. The goal is to provide an all-in-one platform where administrators can view the performance of individual locations as well as nation-wide impact. Furthermore, the platform will also allow them to view the most pertinent data on potential future locations, allowed them to make more informed decisions as the organisation grows.

- Session based authentication with [Passport](https://www.passportjs.org)
- Emailing for account verification and resetting password with [SendGrid](https://sendgrid.com)
- Admin functionality for viewing/deleting/promoting other users
- Clean authentication pages built with [Material UI](https://mui.com)
- In memory database testing with [Jest](https://jestjs.io) and [Supertest](https://www.npmjs.com/package/supertest)
- [AirBnb Typescript styling](https://github.com/airbnb/javascript) with [Prettier](https://prettier.io) and [ESLint](https://eslint.org)
- [Husky](https://typicode.github.io/husky/#/) and [lint-staged](https://github.com/okonet/lint-staged) for checking linting on commits
- [GitHub Actions](https://docs.github.com/en/actions) for ensuring linting + tests pass on pushes
This project was built using the MERN stack. Setup is outlined below.

## Required tools
## Setup

### Required tools

These are necessary to build and run the project at full functionality

- Install [Yarn Package Manager](https://classic.yarnpkg.com/en/docs/install/#mac-stable)
- Install [NodeJS](https://nodejs.org/en/download/)

## Recommended tools
### Recommended tools

To take full advantage of the linting/formatting, recommend adding the [Prettier](https://prettier.io) and [ESLint](https://eslint.org) VSCode extensions and configuring them as shown [here](https://levelup.gitconnected.com/setting-up-eslint-with-prettier-typescript-and-visual-studio-code-d113bbec9857#:~:text=Install%20the%20following%20Visual%20Studio%20Code%20extensions) for code highlighting and formatting on save. Skip to the section labeled "Add the following to your VS Code settings.json". To access your settings.json, follow what is linked [here](https://stackoverflow.com/questions/65908987/how-can-i-open-visual-studio-codes-settings-json-file). See [here](https://blog.logrocket.com/using-prettier-eslint-automate-formatting-fixing-javascript/#differences-between-eslint-prettier) for the differences between the two tools and how they work together.

Finally, we also recommend downloading the [Live Share](https://visualstudio.microsoft.com/services/live-share/) extension by Microsoft for improved Collaboration. This allows for easy peer programming on one shared repository instance.

## Setup

### MongoDB

The boilerplate uses [MongoDB](https://www.mongodb.com) as the database to store information for authentication. To have this available for use, do the following
Expand Down
62 changes: 46 additions & 16 deletions client/src/AdminDashboard/AdminDashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,61 @@
import React, { useLayoutEffect } from 'react';
import { Typography, Grid } from '@mui/material';
import {
Typography,
Grid,
ToggleButtonGroup,
ToggleButton,
Toolbar,
Box,
} from '@mui/material';
import ScreenGrid from '../components/ScreenGrid';
import UserTable from './UserTable';
import InviteUserButton from '../components/buttons/InviteUserButton';
import CityDashboardPage from './CityDashboardPage';
import UserDashboardPage from './UserDashboardPage';

/**
* A page only accessible to admins that displays all users in a table and allows
* Admin to delete users from admin and promote users to admin.
* This is the wrapper page that let's the admin swap between managing users and ciites
*/
function AdminDashboardPage() {
const [manageState, setManageState] = React.useState('city');

const handleChange = (
event: React.MouseEvent<HTMLElement>,
newValue: string,
) => {
setManageState(newValue);
};

useLayoutEffect(() => {
document.body.style.backgroundColor = 'white';
});
return (
<ScreenGrid>
<Grid item>
<Typography variant="h2">Welcome to the Admin Dashboard</Typography>
</Grid>
<Grid item container width="60vw" justifyContent="flex-end">
<InviteUserButton />
</Grid>
<Grid item>
<div style={{ height: '60vh', width: '60vw' }}>
<UserTable />
</div>
</Grid>
</ScreenGrid>
<Grid
container
xs={12}
flexDirection="column"
padding={2}
justifyContent="center"
alignItems="center"
>
<Toolbar />
<Box padding={2}>
<ToggleButtonGroup
color="primary"
value={manageState}
exclusive
onChange={handleChange}
aria-label="Platform"
>
<ToggleButton value="city">Manage Cities</ToggleButton>
<ToggleButton value="user">Manage Users</ToggleButton>
</ToggleButtonGroup>
</Box>

{manageState === 'city' && <CityDashboardPage />}

{manageState === 'user' && <UserDashboardPage />}
</Grid>
);
}

Expand Down
92 changes: 92 additions & 0 deletions client/src/AdminDashboard/CityDashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React, { useEffect, useState, useLayoutEffect } from 'react';
import { Typography, Grid } from '@mui/material';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import { useData } from '../util/api';
import ICity from '../util/types/city';
import Edit from '../images/editDashboard.png';

/**
* M
*/

function createData(name: string) {
return { name };
}

function CityDashboardPage() {
const [cityList, setCityList] = useState<ICity[]>([]);
const [rows, setRows] = useState<string[]>([]);

useLayoutEffect(() => {
document.body.style.backgroundColor = 'white';
});
const cityData = useData(`cities/all`);

useEffect(() => {
setCityList(cityData?.data);
}, [cityData]);

// eslint-disable-next-line no-plusplus
for (let i = 0; i < cityList?.length; i++) {
if (rows.length <= cityList?.length) {
if (cityList[i].isAccredited === true) {
rows.push(`${cityList[i].cityName} (Accredited)`);
} else {
rows.push(cityList[i].cityName);
}
}
}

return (
<Grid
container
xs={12}
justifyContent="center"
alignItems="center"
flexDirection="column"
>
<Grid item>
<div style={{ height: '60vh', width: '60vw' }}>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>City Name</TableCell>
<TableCell align="right">Edit</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow
key={row}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{row}
</TableCell>
<TableCell align="right">
<img
src={Edit}
alt="edit button"
width="25"
height="25"
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
</Grid>
</Grid>
);
}

export default CityDashboardPage;
36 changes: 36 additions & 0 deletions client/src/AdminDashboard/UserDashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { useLayoutEffect } from 'react';
import { Typography, Grid } from '@mui/material';
import ScreenGrid from '../components/ScreenGrid';
import UserTable from './UserTable';
import InviteUserButton from '../components/buttons/InviteUserButton';

/**
* A page only accessible to admins that displays all users in a table and allows
* Admin to delete users from admin and promote users to admin.
*/
function UserDashboardPage() {
useLayoutEffect(() => {
document.body.style.backgroundColor = 'white';
});

return (
<Grid
container
xs={12}
justifyContent="center"
alignItems="center"
flexDirection="column"
>
<Grid item container width="60vw" justifyContent="flex-end">
<InviteUserButton />
</Grid>
<Grid item>
<div style={{ height: '60vh', width: '60vw' }}>
<UserTable />
</div>
</Grid>
</Grid>
);
}

export default UserDashboardPage;
7 changes: 5 additions & 2 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import theme from './assets/theme';
import { store, persistor } from './util/redux/store';
import NotFoundPage from './NotFound/NotFoundPage';
import HomePage from './Home/HomePage';
import AdminDashboardPage from './AdminDashboard/AdminDashboardPage';
import {
UnauthenticatedRoutesWrapper,
ProtectedRoutesWrapper,
Expand All @@ -28,6 +27,7 @@ import Header from './components/Header';
import PieComponent from './components/PieComponent';
import LineComponent from './components/LineComponent';
import NumberTile from './components/indicatorComponents/Under18';
import AdminDashboardPage from './AdminDashboard/AdminDashboardPage';

function App() {
return (
Expand Down Expand Up @@ -68,7 +68,10 @@ function App() {
<Route path="/home" element={<HomePage />} />
</Route>
<Route element={<AdminRoutesWrapper />}>
<Route path="/users" element={<AdminDashboardPage />} />
<Route
path="/admin-dashboard"
element={<AdminDashboardPage />}
/>
</Route>

{/* Route which redirects to a different page depending on if the user is an authenticated or not by utilizing the DynamicRedirect component */}
Expand Down
21 changes: 21 additions & 0 deletions client/src/CityDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Paper from '@mui/material/Paper';
import { Typography, Grid, Toolbar } from '@mui/material';
import Header from './components/Header';
import PieComponent from './components/PieComponent';
import { Typography, Grid, Toolbar, Button, Icon } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import Under18 from './components/indicatorComponents/Under18';
import Poverty from './components/indicatorComponents/Poverty';
import CityNameWidget from './components/widgets/CityNameWidget';
Expand All @@ -14,6 +16,8 @@ import RevenueWidget from './components/widgets/RevenueWidget';
import CoachesWidget from './components/widgets/CoachesWidget';
import LineComponent from './components/LineComponent';
import Bachelor from './components/indicatorComponents/Bachelor';
import TotalChapters from './components/indicatorComponents/TotalChapters';
import HighSchoolGradsPercent from './components/indicatorComponents/HighSchoolGradsPercent';

const heights = [150, 80, 90, 70, 110, 150, 130, 200, 60, 90, 150, 80, 90, 200];

Expand All @@ -26,6 +30,13 @@ function CityDashboard() {
document.body.style.backgroundColor = 'lightgray';
});

// TODO: this should navigate to the dashboard for all cities,
// not the home page (once the all cities page exists)
const navigator = useNavigate();
const onNavigateMainDashboard = () => {
navigator('/home');
};

return (
<Box
component="main"
Expand All @@ -35,6 +46,14 @@ function CityDashboard() {
<Grid container spacing={4}>
<Grid item xs={4}>
<CityNameWidget city="Philadelphia city, Pennsylvania" />
<Button
sx={{ mt: 2 }}
variant="text"
color="primary"
onClick={onNavigateMainDashboard}
>
Back to all cities
</Button>
</Grid>
<Grid item xs={8}>
<Masonry columns={3} spacing={4}>
Expand All @@ -58,6 +77,8 @@ function CityDashboard() {
<LineComponent variant="revenue" />
<LineComponent variant="expenses" />
<PieComponent cityProp="Philadelphia city, Pennsylvania" />
<TotalChapters />
<HighSchoolGradsPercent city="Philadelphia city, Pennsylvania" />
{heights.map((height, ind) => (
<Paper elevation={0} key={ind} sx={{ height, p: 3 }}>
<Typography>add graph here: #{ind + 1}</Typography>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useEffect, useState } from 'react';
import { Toolbar } from '@mui/material';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import { useData } from '../../util/api';
import ICity from '../../util/types/city';
import COLORS from '../../assets/colors';

type RevenueWidgetProps = {
city: string;
};

function HighSchoolGradsPercent({ city }: RevenueWidgetProps) {
const cityData = useData(`cities/${city}`);

if (cityData) {
const hsGraduateList: { [key: number]: number } =
cityData?.data.indicators.high_school_graduates;

const HSGraduateValue =
Object.values(hsGraduateList)[Object.values(hsGraduateList).length - 1];

return (
<Paper elevation={0} key={-1} sx={{ overflow: 'hidden' }}>
<Box sx={{ p: 3 }}>
<Typography variant="h6" sx={{ fontWeight: 700 }}>
High School Graduates
</Typography>
<Typography variant="subtitle2" sx={{ color: COLORS.gray, mb: 1 }}>
High school graduate or higher, percent of persons age 25+
</Typography>
<Typography
variant="h4"
sx={{ color: COLORS.primaryBlue }}
align="center"
>
{Intl.NumberFormat('en-US', {
notation: 'compact',
maximumFractionDigits: 1,
}).format(HSGraduateValue)}
</Typography>
</Box>
</Paper>
);
}

return <Paper elevation={0} key={-1} sx={{ overflow: 'hidden' }} />;
}

export default HighSchoolGradsPercent;
Loading

0 comments on commit 9e32328

Please sign in to comment.