-
Notifications
You must be signed in to change notification settings - Fork 14
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
Jasper #2
base: main
Are you sure you want to change the base?
Jasper #2
Changes from all commits
e6fe784
32c7425
06feb68
ffdd2b9
e0eb44f
3e0c123
a26fe48
a24de48
c58fb16
52ae5f2
1427c80
89ff66e
1d4ff8b
1e45c44
5a476ab
ead4dbf
2844b21
2bb009c
03a7e45
7201c8e
6efae82
833af93
1198896
12d2d68
1a2cac5
cbb10b6
142c886
7c32ef3
0e6584a
ceeac0a
bd20721
49a7d60
72c9cca
b1e2b5a
637d79d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
index.html,1704176534558,78dbd630820dbab73673c587c12b69f1fcab3a54b55b856404286d483dd14b19 | ||
assets/index-QMTPcTcZ.css,1704176534558,ae20ab9e32f733d718e58156fdfe7e626402487f2b517c774b69b968a80c5fe7 | ||
logo.png,1704176534434,a4f3d73a96357dc00ef4132fd3c12aafb62a1493729a9d6f97252a8d8bcd532d | ||
JetBrainsMono-Light.woff2,1704176534432,5117442cda8e5a3cafaafbbdf6758ed37f7ce347798b4e537a2168ad0e3bd88f | ||
SourceSans3VF-Upright.ttf.woff2,1704176534434,a5e8c4da5456f6b9675784affac017e8d160cf3983598e323352cfae09332c5e | ||
assets/index-AxPC6F22.js,1704176534558,4579863da50676eca6a81f5634280deabf43506db41948b0ed6acd8863255747 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"projects": { | ||
"default": "rocketgram-b65c3" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"hosting": { | ||
"public": "dist", | ||
"ignore": [], | ||
"rewrites": [ | ||
{ | ||
"source": "**", | ||
"destination": "/index.html" | ||
} | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
<!doctype html> | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite + React</title> | ||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
<title>Rocketgram</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="/src/main.jsx"></script> | ||
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,71 @@ | ||
import { useState, useEffect } from "react"; | ||
import { createHashRouter, RouterProvider } from "react-router-dom"; | ||
import Composer from "./Composer"; | ||
import NewsFeed from "./NewsFeed"; | ||
import AuthForm from "./AuthForm"; | ||
import { onAuthStateChanged } from "firebase/auth"; | ||
import { auth } from "./firebase"; | ||
import logo from "/logo.png"; | ||
import "./App.css"; | ||
import { onChildAdded, push, ref, set } from "firebase/database"; | ||
import { database } from "./firebase"; | ||
import { useState, useEffect } from "react"; | ||
|
||
// Save the Firebase message folder name as a constant to avoid bugs due to misspelling | ||
const DB_MESSAGES_KEY = "messages"; | ||
import NavBar from "./NavBar"; | ||
|
||
function App() { | ||
const [messages, setMessages] = useState([]); | ||
export default function App() { | ||
//check login status | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice work lifting up the required states for the application to run smoothly |
||
const [isLoggedIn, setIsLoggedIn] = useState(false); | ||
//user info | ||
const [email, setEmail] = useState(""); | ||
const [uid, setUid] = useState(""); | ||
|
||
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 | ||
setMessages((prevState) => | ||
// Store message key so we can use it as a key in our list items when rendering messages | ||
[...prevState, { key: data.key, val: data.val() }] | ||
); | ||
onAuthStateChanged(auth, (user) => { | ||
if (user) { | ||
setIsLoggedIn(true); | ||
setEmail(auth.currentUser.email); | ||
setUid(auth.currentUser.uid); | ||
} else setIsLoggedIn(false); | ||
}); | ||
}, []); | ||
|
||
const writeData = () => { | ||
const messageListRef = ref(database, DB_MESSAGES_KEY); | ||
const newMessageRef = push(messageListRef); | ||
set(newMessageRef, "abc"); | ||
}; | ||
|
||
// Convert messages in state to message JSX elements to render | ||
let messageListItems = messages.map((message) => ( | ||
<li key={message.key}>{message.val}</li> | ||
)); | ||
const router = createHashRouter([ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could use nested Routing along with an outlet to reduce the number of times you load and reload the navbar |
||
{ | ||
path: "/", | ||
element: ( | ||
<> | ||
<NavBar /> | ||
<AuthForm | ||
isLoggedIn={isLoggedIn} | ||
email={email} | ||
setEmail={setEmail} | ||
setUid={setUid} | ||
/> | ||
</> | ||
), | ||
}, | ||
{ | ||
path: "/NewsFeed", | ||
element: ( | ||
<> | ||
<NavBar /> | ||
{isLoggedIn && <Composer uid={uid} email={email} />} | ||
<div className="card"> | ||
<ul | ||
className="message-box" | ||
style={{ borderBottom: "1px dotted white" }}> | ||
<NewsFeed isLoggedIn={isLoggedIn} uid={uid} /> | ||
</ul> | ||
</div> | ||
</> | ||
), | ||
}, | ||
]); | ||
|
||
return ( | ||
<> | ||
<div> | ||
<img src={logo} className="logo" alt="Rocket logo" /> | ||
</div> | ||
<h1>Instagram Bootcamp</h1> | ||
<div className="card"> | ||
{/* TODO: Add input field and add text input as messages in Firebase */} | ||
<button onClick={writeData}>Send</button> | ||
<ol>{messageListItems}</ol> | ||
</div> | ||
<h1>Rocketgram</h1> | ||
<RouterProvider router={router} /> | ||
</> | ||
); | ||
} | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { useState } from "react"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice work keeping all auth logic in one component and either showing the form to login or the button ton sign out, really smart UI and UX thought! |
||
import { | ||
createUserWithEmailAndPassword, | ||
signInWithEmailAndPassword, | ||
signOut, | ||
} from "firebase/auth"; | ||
import { auth } from "./firebase"; | ||
|
||
export default function AuthForm({ isLoggedIn, email, setEmail, setUid }) { | ||
//Login | ||
const [emailValue, setEmailValue] = useState(""); | ||
const [passwordValue, setPasswordValue] = useState(""); | ||
//error | ||
const [errorMsg, setErrorMsg] = useState(""); | ||
|
||
const signUp = async () => { | ||
try { | ||
const userCredential = await createUserWithEmailAndPassword( | ||
auth, | ||
emailValue, | ||
passwordValue | ||
); | ||
console.log(userCredential); | ||
setErrorMsg(""); | ||
} catch (error) { | ||
setErrorMsg(error.message); | ||
} | ||
}; | ||
|
||
const logIn = async () => { | ||
try { | ||
const userCredential = await signInWithEmailAndPassword( | ||
auth, | ||
emailValue, | ||
passwordValue | ||
); | ||
console.log(userCredential); | ||
setErrorMsg(""); | ||
} catch (error) { | ||
setErrorMsg(error.message); | ||
} | ||
}; | ||
|
||
const logOut = async () => { | ||
try { | ||
await signOut(auth); | ||
setErrorMsg(""); | ||
setUid(""); | ||
setEmail(""); | ||
} catch (error) { | ||
setErrorMsg(error.message); | ||
} | ||
}; | ||
return ( | ||
<> | ||
{!isLoggedIn ? ( | ||
<> | ||
<p> | ||
<label htmlFor="email">E-mail: </label> | ||
<input | ||
id="email" | ||
type="email" | ||
autoComplete="on" | ||
pattern=".+@example\.com" | ||
value={emailValue} | ||
onChange={(e) => setEmailValue(e.target.value)} | ||
/> | ||
</p> | ||
<p> | ||
<label htmlFor="password">Password: </label> | ||
<input | ||
id="password" | ||
type="password" | ||
autoComplete="on" | ||
value={passwordValue} | ||
onChange={(e) => setPasswordValue(e.target.value)} | ||
/> | ||
</p> | ||
<p>{errorMsg}</p> | ||
<button onClick={signUp}>Sign Up</button> | ||
<button onClick={logIn}>Login</button> | ||
</> | ||
) : ( | ||
<> | ||
<p>{errorMsg}</p> | ||
<p>Welcome: {email}</p> | ||
<button onClick={logOut}>Logout</button> | ||
</> | ||
)} | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { useState } from "react"; | ||
import { push, set, ref as databaseRef } from "firebase/database"; | ||
import { | ||
getDownloadURL, | ||
uploadBytes, | ||
ref as storageRef, | ||
} from "firebase/storage"; | ||
import { database, storage } from "./firebase"; | ||
|
||
const DB_MESSAGES_KEY = "messages"; | ||
const DB_IMAGES_KEY = "images"; | ||
|
||
export default function Composer({ uid, email }) { | ||
const [inputValue, setInputValue] = useState(""); | ||
const [file, setFile] = useState(null); | ||
|
||
const messagesRef = databaseRef(database, DB_MESSAGES_KEY); | ||
|
||
const writeData = async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice implementation of async and await code! |
||
let name = ""; | ||
let url = ""; | ||
if (file) { | ||
const newStorageRef = storageRef( | ||
storage, | ||
DB_IMAGES_KEY + "/" + file.name | ||
); | ||
await uploadBytes(newStorageRef, file); | ||
url = await getDownloadURL(newStorageRef); | ||
name = file.name; | ||
} | ||
set(push(messagesRef), { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great implementation of likes to ensure that users can only like once, I also like that you have a like Count such that you dont need to process firebase data every load. |
||
timestamp: `${new Date()}`, | ||
edited: "", | ||
message: inputValue, | ||
fileName: name, | ||
fileUrl: url, | ||
likeCount: 0, | ||
poster: uid, | ||
posterEmail: email, | ||
like: { [uid]: false }, | ||
}); | ||
setInputValue(""); | ||
setFile(null); | ||
}; | ||
|
||
return ( | ||
<form onSubmit={(e) => (e.preventDefault(), e.target.reset())}> | ||
<input | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use some stylesheets to reduce repeated code |
||
name="message-input" | ||
style={{ width: "14.6em", marginRight: "1.4em" }} | ||
type="text" | ||
value={inputValue} | ||
onChange={(e) => setInputValue(e.target.value)} | ||
/> | ||
<button | ||
style={{ margin: "0.5em" }} | ||
disabled={!inputValue} | ||
onClick={writeData}> | ||
Send | ||
</button> | ||
<br /> | ||
<input | ||
style={{ margin: "0.5em" }} | ||
type="file" | ||
onChange={(e) => setFile(e.target.files[0])} | ||
/> | ||
</form> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer browserRouter implementations over hash routers, we discussed how you can fix this in the morning standup sessions