Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
Expand Down
1,842 changes: 1,834 additions & 8 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"bootstrap": "^5.3.2",
"firebase": "^10.7.1",
"react": "^18.1.0",
"react-bootstrap": "^2.9.1",
"react-dom": "^18.1.0",
"react-router-dom": "^6.21.0",
"react-scripts": "5.0.1"
},
"scripts": {
Expand Down
20 changes: 0 additions & 20 deletions src/App.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/App.css → src/components/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
}

.App-logo {
height: 40vmin;
height: 5vmin;
pointer-events: none;
}

Expand Down
66 changes: 66 additions & 0 deletions src/components/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { onAuthStateChanged } from "firebase/auth";
import React from "react";
import "./App.css";
import AuthForm from "./AuthForm";
import Composer from "./Composer";
import NewsFeed from "./NewsFeed";
import { auth } from "../firebase";
import logo from "../logo.png";

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedInUser: null,
shouldRenderAuthForm: false,
};
}

componentDidMount() {
onAuthStateChanged(auth, (user) => {
// If user is logged in, save logged-in user to state
if (user) {
this.setState({ loggedInUser: user });
return;
}
// Else set logged-in user in state to null
this.setState({ loggedInUser: null });
});
}

toggleAuthForm = () => {
this.setState((state) => ({
shouldRenderAuthForm: !state.shouldRenderAuthForm,
}));
};

render() {
const authForm = <AuthForm toggleAuthForm={this.toggleAuthForm} />;
const composer = <Composer loggedInUser={this.state.loggedInUser} />;
const createAccountOrSignInButton = (
<div>
<button onClick={this.toggleAuthForm}>Create Account Or Sign In</button>
<br />
</div>
);
const composerAndNewsFeed = (
<div>
{/* Render composer if user logged in, else render auth button */}
{this.state.loggedInUser ? composer : createAccountOrSignInButton}
<br />
<NewsFeed />
</div>
);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<br />
{this.state.shouldRenderAuthForm ? authForm : composerAndNewsFeed}
</header>
</div>
);
}
}

export default App;
126 changes: 126 additions & 0 deletions src/components/AuthForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React from "react";
import Button from "react-bootstrap/Button";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
} from "firebase/auth";
import { auth } from "../firebase";

class AuthForm extends React.Component {
constructor(props) {
super(props);
this.state = {
emailInputValue: "",
passwordInputValue: "",
isNewUser: true,
errorCode: "",
errorMessage: "",
};
}

// Use a single method to control email and password form inputs
handleInputChange = (event) => {
this.setState({ [event.target.name]: event.target.value });
};

handleSubmit = (event) => {
event.preventDefault();

const closeAuthForm = () => {
// Reset auth form state
this.setState({
emailInputValue: "",
passwordInputValue: "",
isNewUser: true,
errorCode: "",
errorMessage: "",
});
// Toggle auth form off after authentication
this.props.toggleAuthForm();
};

const setErrorState = (error) => {
this.setState({
errorCode: error.code,
errorMessage: error.message,
});
};

// Authenticate user on submit
if (this.state.isNewUser) {
createUserWithEmailAndPassword(
auth,
this.state.emailInputValue,
this.state.passwordInputValue
)
.then(closeAuthForm)
.catch(setErrorState);
} else {
signInWithEmailAndPassword(
auth,
this.state.emailInputValue,
this.state.passwordInputValue
)
.then(closeAuthForm)
.catch(setErrorState);
}
};

toggleNewOrReturningAuth = () => {
this.setState((state) => ({ isNewUser: !state.isNewUser }));
};

render() {
return (
<div>
<p>
{this.state.errorCode ? `Error code: ${this.state.errorCode}` : null}
</p>
<p>
{this.state.errorMessage
? `Error message: ${this.state.errorMessage}`
: null}
</p>
<p>Sign in with this form to post.</p>
<form onSubmit={this.handleSubmit}>
<label>
<span>Email: </span>
<input
type="email"
name="emailInputValue"
value={this.state.emailInputValue}
onChange={this.handleInputChange}
/>
</label>
<br />
<label>
<span>Password: </span>
<input
type="password"
name="passwordInputValue"
value={this.state.passwordInputValue}
onChange={this.handleInputChange}
/>
</label>
<br />
<input
type="submit"
value={this.state.isNewUser ? "Create Account" : "Sign In"}
// Disable form submission if email or password are empty
disabled={
!this.state.emailInputValue || !this.state.passwordInputValue
}
/>
<br />
<Button variant="link" onClick={this.toggleNewOrReturningAuth}>
{this.state.isNewUser
? "If you have an account, click here to login"
: "If you are a new user, click here to create account"}
</Button>
</form>
</div>
);
}
}

export default AuthForm;
92 changes: 92 additions & 0 deletions src/components/Composer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { push, ref as databaseRef, set } from "firebase/database";
import {
getDownloadURL,
ref as storageRef,
uploadBytes,
} from "firebase/storage";
import React from "react";
import { database, storage } from "../firebase";

// Save Firebase folder names as constants to avoid bugs due to misspelling
const IMAGES_FOLDER_NAME = "images";
const POSTS_FOLDER_NAME = "posts";

class Composer extends React.Component {
constructor(props) {
super(props);
this.state = {
fileInputFile: null,
fileInputValue: "",
textInputValue: "",
};
}

handleFileInputChange = (event) => {
this.setState({
fileInputFile: event.target.files[0],
fileInputValue: event.target.value,
});
};

handleTextInputChange = (event) => {
this.setState({ textInputValue: event.target.value });
};

// Note use of array fields syntax to avoid having to manually bind this method to the class
handleSubmit = (event) => {
// Prevent default form submit behaviour that will reload the page
event.preventDefault();

// Store images in an images folder in Firebase Storage
const fileRef = storageRef(
storage,
`${IMAGES_FOLDER_NAME}/${this.state.fileInputFile.name}`
);

// Upload file, save file download URL in database with post text
uploadBytes(fileRef, this.state.fileInputFile).then(() => {
getDownloadURL(fileRef).then((downloadUrl) => {
const postListRef = databaseRef(database, POSTS_FOLDER_NAME);
const newPostRef = push(postListRef);
set(newPostRef, {
imageLink: downloadUrl,
text: this.state.textInputValue,
authorEmail: this.props.loggedInUser.email,
});
// Reset input field after submit
this.setState({
fileInputFile: null,
fileInputValue: "",
textInputValue: "",
});
});
});
};

render() {
return (
<form onSubmit={this.handleSubmit}>
<p>{this.props.loggedInUser ? this.props.loggedInUser.email : null}</p>
<input
type="file"
value={this.state.fileInputValue}
onChange={this.handleFileInputChange}
/>
<br />
<input
type="text"
value={this.state.textInputValue}
onChange={this.handleTextInputChange}
/>
<input
type="submit"
value="Post"
// Disable Send button when text input is empty
disabled={!this.state.textInputValue}
/>
</form>
);
}
}

export default Composer;
51 changes: 51 additions & 0 deletions src/components/FilterInputModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useState } from "react";
import Modal from "react-bootstrap/Modal";
import Button from "react-bootstrap/Button";

const FilterInputModal = (props) => {
const typeDisplay = {
stockCode: "stock code",
stockName: "stock name",
platform: "platform",
};
const [filterField, setFilterField] = useState("");
const handleChange = (e) => {
setFilterField(e.target.value);
};

const closeHandler = () => {
setFilterField("");
props.close();
};

const filterSetHandler = () => {
props.setFilter(filterField);
setFilterField("");
props.close();
return true;
};
return (
<Modal show={props.show} onHide={props.close}>
<Modal.Header closeButton>
<Modal.Title>Filter by: {typeDisplay[props.filterCat]}</Modal.Title>
</Modal.Header>
<Modal.Body>
<input
type="text"
value={filterField}
onChange={(e) => handleChange(e)}
></input>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={closeHandler}>
Close
</Button>
<Button variant="primary" onClick={filterSetHandler}>
Filter
</Button>
</Modal.Footer>
</Modal>
);
};

export default FilterInputModal;
Loading