Skip to content

Commit

Permalink
Replace Firebase with Supabase (#567)
Browse files Browse the repository at this point in the history
* Use new Supabase DB over Firebase

* Remove debug code

* Allow members to be part of multiple circles
  • Loading branch information
vontell authored Feb 12, 2025
1 parent f5b7abf commit a102cc1
Show file tree
Hide file tree
Showing 20 changed files with 261 additions and 865 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/betasite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- develop
env:
AWS_DEFAULT_REGION: us-east-2
VITE_DB_URL: https://phlask-beta.firebaseio.com
VITE_DB_URL: https://wantycfbnzzocsbthqzs.supabase.co
permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout
Expand Down
36 changes: 18 additions & 18 deletions .github/workflows/userTestsite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,27 @@ on:
- main
env:
AWS_DEFAULT_REGION: us-east-2
REACT_APP_DB_URL: https://phlask-usertest.firebaseio.com
REACT_APP_DB_URL: 'https://wantycfbnzzocsbthqzs.supabase.co'
permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1-node16
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/beta-access
role-session-name: github-${{ github.sha }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: Build and Deploy the Map
run: |
echo Build started on `date`
echo Building the webapp...
docker compose run prod_build
echo Build completed on `date`
echo Pushing the webapp to S3...
aws s3 sync docker/build s3://usertest.phlask.me --delete
- uses: actions/checkout@v1
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1-node16
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/beta-access
role-session-name: github-${{ github.sha }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: Build and Deploy the Map
run: |
echo Build started on `date`
echo Building the webapp...
docker compose run prod_build
echo Build completed on `date`
echo Pushing the webapp to S3...
aws s3 sync docker/build s3://usertest.phlask.me --delete
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ build/
# Local target for files built on docker
docker/

# Firebase
# /src/firebase

# dev files
resources/
.env
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ Code behind the PHLASK Web Map
│ ├── App.js
│ ├── actions <-- Source for all Redux actions
│ ├── components <-- Source for all React components
│ ├── firebase <-- Source for configurations used to connect to Firebase database
│ ├── helpers <-- Helper functions shared across components/pages
│ ├── hooks <-- Custom hooks
│ ├── reducers <-- Redux reducers
Expand Down Expand Up @@ -194,5 +193,5 @@ The site runs on:
- AWS CloudFront (https://aws.amazon.com/cloudfront/)
- Serves as a global Content Delivery Network (CDN) for the content hosted in S3
- Enables us to have a custom domain with SSL in order to ensure your traffic to the page is encrypted via HTTPS (https://en.wikipedia.org/wiki/HTTPS)
- Google Firebase Realtime Database (https://firebase.google.com/docs/database)
- Stores the tap data used to generate the information on our site
- Supabase (https://supabase.com/docs)
- Stores the resources and contributors data used to generate the information on our site
2 changes: 0 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@
></script>
</head>
<body>
<script src="https://www.gstatic.com/firebasejs/7.6.0/firebase-app.js"></script>

<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
"@mui/material": "^5.6.2",
"@mui/styles": "^5.14.1",
"@reduxjs/toolkit": "^1.8.2",
"@supabase/supabase-js": "^2.48.0",
"@vis.gl/react-google-maps": "^1.4.0",
"bootstrap": "^5.1.3",
"cypress": "^13.6.6",
"firebase": "^9.6.11",
"google-maps-react": "^2.0.6",
"popper.js": "^1.16.1",
"react": "^18.3.1",
Expand All @@ -30,9 +30,9 @@
"react-images-upload": "^1.2.8",
"react-places-autocomplete": "^7.3.0",
"react-redux": "^9.1.2",
"react-touch-events": "^3.0.0",
"reselect": "^5.1.1",
"sass": "^1.50.1",
"react-touch-events": "^3.0.0"
"sass": "^1.50.1"
},
"pre-commit": [
"lint"
Expand Down
37 changes: 25 additions & 12 deletions redux_guide.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,55 @@
# Redux Guide

## Purpose

The purpose of this guide is to instruct developers of the PHLASK Map project on how to interact with the Redux state used throughout the site. A deep understanding of how Redux generally works is out of scope for this document, as that has been covered by the wider dev community.

## Why Redux?
The main is Redux was chosen very early on (2019) into our transition to React was to simplify passing state properties when they need to be modified by child/sibling components. The team's understanding of React was immature at the time, but we were struggling with the idea of passing properties. We had a lot of unrelated components that interact with all of the tap data, and we want the tap data to be stored locally in order to minimize the amount of pulls from Firebase. Storing the tap data in Redux state allowed us to pass references to the taps via their IDs and then pull any additional data by interacting with the central Redux state.

The main is Redux was chosen very early on (2019) into our transition to React was to simplify passing state properties when they need to be modified by child/sibling components. The team's understanding of React was immature at the time, but we were struggling with the idea of passing properties. We had a lot of unrelated components that interact with all of the tap data, and we want the tap data to be stored locally in order to minimize the amount of pulls from Supabase. Storing the tap data in Redux state allowed us to pass references to the taps via their IDs and then pull any additional data by interacting with the central Redux state.

## How is the Redux state used?

### Initialization

In order to start up the state, we call the `store` function in `index.js` as a property of the `Provider` component. This exposes the state to everything contained under that Provider component. Since it is the top-level component for the site, it is available to all components.

### Initial State

The initial state is defined in `store.js`. This file defines:

- Use of Redux Thunk to enable async interactions with Redux state: https://github.com/reduxjs/redux-thunk
- Use of `reducers/filterMarkers.js` as the definition of the state structure.
- `initialState` - Defines the properties for the Redux state
- `case actions.ACTION_NAME` - Defines the different actions available to interact with the state and how they behave when invoked.
- For more information about reducers, read: https://redux.js.org/tutorials/essentials/part-1-overview-concepts#reducers
- `initialState` - Defines the properties for the Redux state
- `case actions.ACTION_NAME` - Defines the different actions available to interact with the state and how they behave when invoked.
- For more information about reducers, read: https://redux.js.org/tutorials/essentials/part-1-overview-concepts#reducers

### Manipulating the state

The `actions/actions.js` file defines the actions that can be performed by components to manipulate the state. For more information about actions, read: https://redux.js.org/tutorials/essentials/part-1-overview-concepts#actions

Actions are referenced in components via two different approaches, depending on how the component is built:

#### Functional Components

For an example of how class based components interact with Redux state, check `components/Head/Head.js`.

- `const dispatch = useDispatch();` is added to the function in order to enable the use of Redux dispatches. These are the functions from `actions/actions.js` which can be triggered to manipulate state.
- For information about Redux dispatches, see: https://redux.js.org/tutorials/essentials/part-1-overview-concepts#dispatch
- This is used within the functional component by calling the `dispatch` function with the following properties
- `type` - The type value defined in `actions/actions.js`
- `property_name` - Additional properties defined for the action with the `type` property with value of `type`.
- For information about Redux dispatches, see: https://redux.js.org/tutorials/essentials/part-1-overview-concepts#dispatch
- This is used within the functional component by calling the `dispatch` function with the following properties
- `type` - The type value defined in `actions/actions.js`
- `property_name` - Additional properties defined for the action with the `type` property with value of `type`.
- `const property_name = useSelector(state => state.redux_state_property)` is used to reference Redux state.

#### Class Based Components

**NOTE:** We are gradually deprecating this approach in favor of functional components. This subsection is included while the deprecation is underway.
For an example of how class based components interact with Redux state, check `components/SearchBar/SearchBar.js`.

- A top-level `const mapStateToProps` is defined to map Redux state properties to properties that should be set in the component.
- To interact with these proprties within the component class, use `this.props.property_name`
- To interact with these proprties within the component class, use `this.props.property_name`
- A top-level `const mapDispatchToProps` is defined to map Redux dispatches. These are the functions from `actions/actions.js` which can be triggered to manipulate state.
- For information about Redux dispatches, see: https://redux.js.org/tutorials/essentials/part-1-overview-concepts#dispatch
- These functions should be imported using standard javascript imports.
- To interact with dispatches within the component class, use `this.props.function_name`
- For information about Redux dispatches, see: https://redux.js.org/tutorials/essentials/part-1-overview-concepts#dispatch
- These functions should be imported using standard javascript imports.
- To interact with dispatches within the component class, use `this.props.function_name`
- A top-level export of the component is arranged such that it uses the `connect()` redux function to connect the component class to the Redux state store.
17 changes: 4 additions & 13 deletions src/actions/actions.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { createAction, createAsyncThunk } from '@reduxjs/toolkit';
import { initializeApp } from 'firebase/app';
import { getDatabase, get, ref } from 'firebase/database';
import { resourcesConfig } from '../firebase/firebaseConfig';
import testData from '../firebase/functionalTest';
import testData from '../testData/functionalTest';
import { getResources as getResourcesFromDB } from '../db';

export const SET_TOGGLE_STATE = 'SET_TOGGLE_STATE';
export const setToggleState = (toggle, toggleState) => ({
Expand Down Expand Up @@ -33,16 +31,9 @@ export const resetFilterFunction = createAction('RESET_FILTER_FUNCTION');
export const getResources = createAsyncThunk(
'fetch-resources',
async (_, { dispatch }) => {
const app = initializeApp(resourcesConfig);
const database = getDatabase(app);

if (import.meta.env.VITE_CYPRESS_TEST) return testData;
const snapshot = await get(ref(database, '/'));
const results = snapshot.val();
return Object.entries(results).map(([id, resource]) => ({
...resource,
id
}));

return getResourcesFromDB();
}
);

Expand Down
12 changes: 2 additions & 10 deletions src/components/AddResourceModal/AddResourceModalV2.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { initializeApp } from 'firebase/app';
import { getDatabase, ref, push } from 'firebase/database';
import { geocodeByAddress, getLatLng } from 'react-places-autocomplete';
import noop from 'utils/noop';
import { TOOLBAR_MODAL_NONE, pushNewResource } from 'actions/actions';
import { resourcesConfig } from 'firebase/firebaseConfig';

import debounce from 'utils/debounce';

Expand All @@ -15,6 +12,7 @@ import {
FORAGE_RESOURCE_TYPE,
BATHROOM_RESOURCE_TYPE
} from 'types/ResourceEntry';
import { addResource } from '../../db';

import ShareSocials from './ShareSocials';
import ChooseResource from './ChooseResource';
Expand Down Expand Up @@ -339,13 +337,7 @@ const AddResourceModalV2 = () => {
};
}

// TODO(vontell): We probably should not init this here ever time, although it is likely fine.
const app = initializeApp(resourcesConfig);
const database = getDatabase(app);
push(ref(database, '/'), newResource).then(result => {
const { _path: path } = result;
const id = path.pieces[0];
newResource.id = id;
addResource(newResource).then(result => {
dispatch(pushNewResource(newResource));
});
});
Expand Down
44 changes: 25 additions & 19 deletions src/components/ContributorsList/ContributorsListItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,32 @@ import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';

const contributorToCircleName = contributor => {
const circles = [];
if (contributor.civic_member) circles.push('Civic');
if (contributor.data_member) circles.push('Data');
if (contributor.design_member) circles.push('Design');
if (contributor.development_member) circles.push('Development');
if (contributor.project_management_member) circles.push('Project Management');
return circles.join(', ');
};

const ContributorsListItem = ({ contributor }) => (
<ListItem sx={{ paddingInline: 0 }}>
<Stack>
<Box>
<Typography fontWeight={700}>
{contributor.First} {contributor.Last}
</Typography>
</Box>
<Stack direction="row">
<Typography component="span" variant="subtitle2">
{contributor.circle}
</Typography>
{contributor.isConvener && (
<Typography component="span" variant="subtitle2">
, Convener
</Typography>
)}
</Stack>
<ListItem sx={{ paddingInline: 0 }}>
<Stack>
<Box>
<Typography fontWeight={700}>
{contributor.first_name} {contributor.last_name}
</Typography>
</Box>
<Stack direction="row">
<Typography component="span" variant="subtitle2">
{contributorToCircleName(contributor)}
{contributor.convener ? ', Convener' : ''}
</Typography>
</Stack>
</ListItem>
);
</Stack>
</ListItem>
);

export default ContributorsListItem;
11 changes: 4 additions & 7 deletions src/components/SelectedTapDetails/SelectedTapDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,21 +145,18 @@ const SelectedTapDetails = ({
let resourceSubtitleTwo;

if (
resource.name !== undefined &&
resource.name &&
resource.name.trim().length > 0 &&
resource.address !== undefined &&
resource.address &&
resource.address.trim().length > 0
) {
resourceTitle = resource.name;
resourceSubtitleOne = resource.address;
resourceSubtitleTwo = latLongFormatted;
} else if (resource.name !== undefined && resource.name.trim().length > 0) {
} else if (resource.name && resource.name.trim().length > 0) {
resourceTitle = resource.name;
resourceSubtitleOne = latLongFormatted;
} else if (
resource.address !== undefined &&
resource.address.trim().length > 0
) {
} else if (resource.address && resource.address.trim().length > 0) {
resourceTitle = resource.address;
resourceSubtitleOne = latLongFormatted;
} else if (
Expand Down
12 changes: 8 additions & 4 deletions src/components/SelectedTapHours/SelectedTapHours.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ const SelectedTapHours = ({ selectedPlace }) => {
selectedPlace.hours.forEach((orgHours, index) => ({
day: hours.getDays(index),
open:
orgHours.open !== undefined && orgHours.open !== ''
orgHours.open !== undefined &&
orgHours.open !== '' &&
orgHours.open !== null
? hours.getSimpleHours(orgHours.open)
: null,
close:
orgHours.close !== undefined && orgHours.close !== ''
orgHours.close !== undefined &&
orgHours.close !== '' &&
orgHours.close !== null
? hours.getSimpleHours(orgHours.close)
: null
}));
Expand Down Expand Up @@ -72,10 +76,10 @@ const SelectedTapHours = ({ selectedPlace }) => {
let placeOpeningInfo;
if (isOpen) {
placeOpeningInfo = { color: 'green', label: 'Open' };
} else if (typeof isOpen === 'undefined' || isOpen === null) {
placeOpeningInfo = { color: 'orange', label: 'Open times unavailable' };
} else if (!isOpen) {
placeOpeningInfo = { color: 'red', label: 'Closed' };
} else if (typeof isOpen === 'undefined') {
placeOpeningInfo = { color: 'orange', label: 'Open times unavailable' };
}

return (
Expand Down
Loading

0 comments on commit a102cc1

Please sign in to comment.