Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instagram-Bootcamp #50

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
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 @@ -12,6 +12,7 @@
/build

# misc
.env
.DS_Store
.env.local
.env.development.local
Expand Down
1,670 changes: 867 additions & 803 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"firebase": "^9.8.0",
"bootstrap": "^5.3.2",
"firebase": "^9.23.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-router-dom": "^6.16.0",
"react-scripts": "5.0.1"
},
"scripts": {
Expand Down
9 changes: 2 additions & 7 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
/* App.css */
.App {
text-align: center;
}

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

.App-header {
background-color: #282c34;
background-color: skyblue;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
280 changes: 235 additions & 45 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,250 @@
import React from "react";
import { onChildAdded, push, ref, set } from "firebase/database";
import { database } from "./firebase";
import logo from "./logo.png";
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { push, ref, remove, set } from "firebase/database";
import { database, auth } from "./firebase";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
onAuthStateChanged,
signOut,
sendPasswordResetEmail as sendPasswordResetEmailFirebase,
} from "firebase/auth";
import Message from "./Component/Message";
import ChatContainer from "./Component/ChatContainer";
import ChatInput from "./Component/ChatInput";
import {
onChildAdded,
onChildChanged,
onChildRemoved,
} from "firebase/database";
import "./App.css";
import "./ChatContainer.css";
import "./ChatInput.css";
import "./Message.css";

// Save the Firebase message folder name as a constant to avoid bugs due to misspelling
const DB_MESSAGES_KEY = "messages";

class App extends React.Component {
constructor(props) {
super(props);
// Initialise empty messages array in state to keep local state in sync with Firebase
// When Firebase changes, update local state, which will update local UI
this.state = {
messages: [],
};
}
const App = () => {
const [messages, setMessages] = useState([]);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [newMessage, setNewMessage] = useState("");
const [isInputEnabled, setIsInputEnabled] = useState(false);
const [user, setUser] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState(false);

componentDidMount() {
useEffect(() => {
const messagesRef = ref(database, DB_MESSAGES_KEY);
// onChildAdded will return data for every child at the reference and every subsequent new child
onChildAdded(messagesRef, (data) => {
// Add the subsequent child to local component state, initialising a new array to trigger re-render
this.setState((state) => ({
// Store message key so we can use it as a key in our list items when rendering messages
messages: [...state.messages, { key: data.key, val: data.val() }],
}));

const messagesAddedListener = onChildAdded(messagesRef, (data) => {
setMessages((prevMessages) => [
...prevMessages,
{
key: data.key,
message: data.val().message,
datetime: data.val().datetime,
user: data.val().user,
},
]);
});
}

// Note use of array fields syntax to avoid having to manually bind this method to the class
writeData = () => {
const messagesChangedListener = onChildChanged(messagesRef, (data) => {
setMessages((prevMessages) =>
prevMessages.map((message) =>
message.key === data.key
? {
key: data.key,
message: data.val().message,
datetime: data.val().datetime,
user: data.val().user,
}
: message
)
);
});

const messagesRemovedListener = onChildRemoved(messagesRef, (data) => {
setMessages((prevMessages) =>
prevMessages.filter((message) => message.key !== data.key)
);
});

const authStateChangedListener = onAuthStateChanged(auth, async (user) => {
if (user) {
setUser(user);
setIsLoggedIn(true);
setIsInputEnabled(true);
} else {
setUser(null);
setIsLoggedIn(false);
setIsInputEnabled(false);
}
});

return () => {
messagesAddedListener();
messagesChangedListener();
messagesRemovedListener();
authStateChangedListener();
};
}, []);

const handleSignup = async () => {
try {
await createUserWithEmailAndPassword(auth, email, password);
alert("User signed up successfully!");
} catch (error) {
alert("Error signing up:", error.message);
}
};

const handleLogin = async () => {
try {
if (!email || !password) {
throw new Error("Error: Please type in your username and password.");
}

await signInWithEmailAndPassword(auth, email, password);
alert("You have signed in.");
} catch (error) {
alert("Error: Wrong Username or Password was typed in.");
}
};

const handleSubmit = (event) => {
event.preventDefault();
if (newMessage.trim() !== "") {
writeData();
}
};

const writeData = () => {
if (newMessage.trim() !== "") {
const messageListRef = ref(database, DB_MESSAGES_KEY);
const newMessageRef = push(messageListRef);
const datetime = new Date().toLocaleString();

try {
set(newMessageRef, {
message: newMessage,
datetime,
user: user.uid,
});
setNewMessage("");
} catch (error) {
console.error("Error writing message:", error);
}
}
};

const handleInputChange = (event) => {
setNewMessage(event.target.value);
};

const handleEmailChange = (event) => {
setEmail(event.target.value);
};

const handlePasswordChange = (event) => {
setPassword(event.target.value);
};

const handleDeleteMessage = (messageKey) => {
const messageRef = ref(database, `${DB_MESSAGES_KEY}/${messageKey}`);
remove(messageRef);
};

const handleClearLog = () => {
setMessages([]);
const messageListRef = ref(database, DB_MESSAGES_KEY);
const newMessageRef = push(messageListRef);
set(newMessageRef, "abc");
remove(messageListRef);
};

const handleLogout = async () => {
try {
await signOut(auth);
alert("User signed out");
} catch (error) {
alert("Error signing out: " + error.message);
}
};

const handleResetPassword = async () => {
try {
await sendPasswordResetEmailFirebase(auth, email);
alert("Password reset email sent to: " + email);
} catch (error) {
alert("Error sending password reset email:", error.message);
}
};

render() {
// Convert messages in state to message JSX elements to render
let messageListItems = this.state.messages.map((message) => (
<li key={message.key}>{message.val}</li>
));
return (
<div className="App">
const handleEditMessage = (messageKey, editedMessage) => {
const messageRef = ref(
database,
`${DB_MESSAGES_KEY}/${messageKey}/message`
);
set(messageRef, editedMessage);
};

return (
<div className="App">
<Router>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
{/* TODO: Add input field and add text input as messages in Firebase */}
<button onClick={this.writeData}>Send</button>
<ol>{messageListItems}</ol>
<Routes>
<Route
path="/"
element={
isLoggedIn ? (
<>
<button onClick={handleLogout}>Logout</button>
<ChatContainer
messages={messages}
user={user}
onDeleteMessage={handleDeleteMessage}
onEditMessage={handleEditMessage}
MessageComponent={Message}
/>
<ChatInput
newMessage={newMessage}
onInputChange={handleInputChange}
onSubmit={handleSubmit}
isInputEnabled={isInputEnabled}
/>
<br></br>
<button onClick={handleClearLog}>Clear Log</button>
</>
) : (
<>
<label htmlFor="email">Email : </label>
<input
type="text"
value={email}
onChange={handleEmailChange}
placeholder="Enter Email"
/>
<br />
<label htmlFor="password">Password : </label>
<input
type="password"
value={password}
onChange={handlePasswordChange}
placeholder="Enter Password"
/>
<br />
<button onClick={handleLogin}>Login</button>
<button onClick={handleSignup}>Sign Up</button>
<button onClick={handleResetPassword} disabled={!email}>
Forget Password
</button>
</>
)
}
/>
</Routes>
</header>
</div>
);
}
}
</Router>
</div>
);
};

export default App;
10 changes: 10 additions & 0 deletions src/ChatContainer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* ChatContainer.css */
.chat-container {
max-width: 600px;
margin: 20px auto;
}

.chat-container {
display: flex;
flex-direction: column;
}
28 changes: 28 additions & 0 deletions src/ChatInput.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* ChatInput.css */
form {
display: flex;
margin-top: 20px;
}

form input[type="text"] {
flex: 1;
padding: 10px;
border-radius: 25px;
border: 1px solid white;
font-size: 14px;
}

form button {
background-color: #0084ff;
border: none;
color: white;
padding: 10px 20px;
border-radius: 25px;
margin-left: 10px;
font-size: 14px;
cursor: pointer;
}

form button:disabled {
background-color: white;
}
Loading