consolidate all repos to one for archive

This commit is contained in:
2025-01-28 13:46:42 +01:00
commit a6610fbc7a
5350 changed files with 2705721 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
import { useState, useEffect } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { UserContext } from "./userContext";
import Header from "./components/Header";
import Photos from "./components/Photos";
import Login from "./components/Login";
import Register from "./components/Register";
import Profile from "./components/Profile";
import Logout from "./components/Logout";
import AddPhoto from "./components/AddPhoto";
import Detail from './components/Detail';
function App() {
/**
* Podatek o tem, ali je uporabnik prijavljen ali ne, bomo potrebovali v vseh komponentah.
* State je dosegljiv samo znotraj trenutne komponente. Če želimo deliti spremenljivke z
* ostalimi komponentami, moramo uporabiti Context.
* Vsebino Contexta smo definirali v datoteki userContext.js. Poleg objekta 'user', potrebujemo
* še funkcijo, ki bo omogočala posodabljanje te vrednosti. To funkcijo definiramo v komponenti App
* (updateUserData). V render metodi pripravimo UserContext.Provider, naš Context je potem dosegljiv
* v vseh komponentah, ki se nahajajo znotraj tega providerja.
* V komponenti Login ob uspešni prijavi nastavimo userContext na objekt s trenutno prijavljenim uporabnikom.
* Ostale komponente (npr. Header) lahko uporabijo UserContext.Consumer, da dostopajo do prijavljenega
* uporabnika.
* Context se osveži, vsakič ko osvežimo aplikacijo v brskalniku. Da preprečimo neželeno odjavo uporabnika,
* lahko context trajno hranimo v localStorage v brskalniku.
*/
const [user, setUser] = useState(localStorage.user ? JSON.parse(localStorage.user) : null);
const updateUserData = (userInfo) => {
localStorage.setItem("user", JSON.stringify(userInfo));
setUser(userInfo);
}
/**
* Na vrhu vključimo komponento Header, z naslovom in menijem.
* Nato vključimo Router, ki prikaže ustrezno komponento v odvisnosti od URL naslova.
* Pomembno je, da za navigacijo in preusmeritve uporabljamo komponenti Link in Navigate, ki sta
* definirani v react-router-dom modulu. Na ta način izvedemo navigacijo brez osveževanja
* strani. Klasične metode (<a href=""> in window.location) bi pomenile osvežitev aplikacije
* in s tem dodatno obremenitev (ponovni izris komponente Header, ponastavitev Contextov,...)
*/
return (
<BrowserRouter>
<UserContext.Provider value={{
user: user,
setUserContext: updateUserData
}}>
<div className="App">
<Header title="Instagram"></Header>
<br />
<Routes>
<Route path="/" exact element={<Photos />}></Route>
<Route path="/login" exact element={<Login />}></Route>
<Route path="/register" element={<Register />}></Route>
<Route path="/publish" element={<AddPhoto />}></Route>
<Route path="/profile" element={<Profile />}></Route>
<Route path="/logout" element={<Logout />}></Route>
<Route path="/Detail/:id" element={<Detail />}></Route>
</Routes>
</div>
</UserContext.Provider>
</BrowserRouter>
);
}
export default App;

View File

@@ -0,0 +1,53 @@
import { useContext, useState } from 'react'
import { Navigate } from 'react-router';
import { UserContext } from '../userContext';
function AddPhoto(props) {
const userContext = useContext(UserContext);
const[name, setName] = useState('');
const[file, setFile] = useState('');
const[uploaded, setUploaded] = useState(false);
async function onSubmit(e){
e.preventDefault();
if(!name){
alert("Vnesite ime!");
return;
}
const formData = new FormData();
formData.append('name', name);
formData.append('image', file);
const res = await fetch('http://localhost:3001/photos', {
method: 'POST',
credentials: 'include',
body: formData
});
const data = await res.json();
setUploaded(true);
}
return (
<form className="form-group mx-auto w-50" onSubmit={onSubmit} >
{!userContext.user ? <Navigate replace to="/login" /> : ""}
{uploaded ? <Navigate replace to="/" /> : ""}
<div className="mb-3">
<label>Ime slike</label>
<input type="text" className="form-control" name="ime" placeholder="Ime slike"
value={name} onChange={(e)=>{setName(e.target.value)}} required/>
</div>
<div className="mb-3">
<label>Izberi sliko: </label>
<input style={{marginLeft: 5 + 'px'}} type="file" id="file"
onChange={(e)=>{setFile(e.target.files[0])}} required/>
</div>
<input className="btn btn-primary" type="submit" name="submit" value="Naloži" />
</form>
)
}
export default AddPhoto;

View File

@@ -0,0 +1,12 @@
function Comments(props){
return(
<div className="card my-3">
<div className="card-body">
<p className="card-text">{props.comment}</p>
</div>
</div>
)
}
export default Comments;

View File

@@ -0,0 +1,111 @@
import { useState, useEffect, useCallback } from 'react';
import { useParams } from 'react-router-dom';
import { UserContext } from '../userContext';
import Comments from './Comments';
import IncrementButton from './IncrementButton';
function Detail() {
const [photo, setPhoto] = useState(null);
const [likes, setLikes] = useState(0);
const [commentInput, setCommentInput] = useState('');
const [comments, setComments] = useState([]);
const { id } = useParams();
useEffect(() => {
const getPhoto = async () => {
const res = await fetch(`http://localhost:3001/photos/${id}`);
const data = await res.json();
setPhoto(data);
setLikes(data.likes);
setComments(data.comments);
};
getPhoto();
}, [id]);
const addToLike = useCallback(() => {
setLikes(likes + 1);
}, [likes]);
if (!photo) {
return <div>Loading...</div>;
}
const handleCommentChange = (event) => {
setCommentInput(event.target.value);
};
const handleCommentSubmit = (event) => {
setComments([...comments, commentInput]);
setCommentInput('');
};
const addComment = async () => {
// Make a request to the backend API to add the comment
fetch(`http://localhost:3001/photos/comment/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ comment: commentInput }),
});
// Update the comments state variable
handleCommentSubmit();
};
return (
<div>
<div className="card">
<img className="card-img" src={"http://localhost:3001/" + photo.path} alt={photo.name} />
<ul className="list-group list-group-flush">
<li className="list-group-item">Naslov: {photo.name}</li>
<li className="list-group-item">Avtor: {photo.postedBy.username}</li>
<li className="list-group-item">Objavljen: {photo.createdAt}</li>
<li className="list-group-item">Vsecki: {likes}</li>
</ul>
<UserContext.Consumer>
{context => (
context.user ?
<div className="card-body">
<IncrementButton text="Like" type="like" className="btn btn-primary" setLikes={addToLike} amount={likes} source={id}></IncrementButton>
<IncrementButton text="Report" type="report" className="btn btn-danger" amount={photo.reports} source={id}></IncrementButton>
</div>
:
<></>
)}
</UserContext.Consumer>
</div>
<br />
<h3>Komentar:</h3>
<div style={{marginBottom: 10 + 'px'}}>
{comments.map((comment, index) => <Comments key={comments.indexOf(comment)} comment={comment}></Comments>)}
</div>
<UserContext.Consumer>
{context => (
context.user ?
<div>
<textarea className="form-control" placeholder="Add a comment" rows="3"
value={commentInput} onChange={handleCommentChange}>
</textarea>
<br/>
<button className='btn btn-primary' onClick={addComment}>Dodaj komentar</button>
</div>
:
<></>
)}
</UserContext.Consumer>
</div>
);
}
export default Detail;

View File

@@ -0,0 +1,35 @@
import { useContext } from "react";
import { UserContext } from "../userContext";
import { Link } from "react-router-dom";
function Header(props) {
return (
<header>
<nav className="navbar bg-light-subtle">
<div className="container-fluid navbar-expand-lg">
<div className="navbar-nav">
<Link className="nav-link" to='/'>Domov</Link>
<UserContext.Consumer>
{context => (
context.user ?
<>
<Link className="nav-link" to='/publish'>Objavi sliko</Link>
<Link className="nav-link" to='/profile'>Profil</Link>
<Link className="nav-link" to='/logout'>Odjava</Link>
</>
:
<>
<Link className="nav-link" to='/login'>Prijava</Link>
<Link className="nav-link" to='/register'>Registracija</Link>
</>
)}
</UserContext.Consumer>
</div>
</div>
</nav>
</header>
);
}
export default Header;

View File

@@ -0,0 +1,47 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
function IncrementButton(props) {
const [reports, setReports] = useState(0);
const navigate = useNavigate();
const incrementLikes = async () => {
props.setLikes((prevLikes) => prevLikes + 1);
await fetch(`http://localhost:3001/photos/increase/${props.type}/${props.source}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ likes: props.likes + 1 }),
});
};
const incrementReports = async () => {
setReports((prevReports) => {
const updatedReports = prevReports + 1;
if (updatedReports === 5) {
navigate('/'); // Redirect to the home page
}
return updatedReports;
});
await fetch(`http://localhost:3001/photos/increase/${props.type}/${props.source}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ reports: reports + 1 }),
});
};
return (
<span style={{marginRight: 10 + 'px'}} className={props.class} onClick={(props.text === 'Report') ? incrementReports : incrementLikes}>
{props.text}
</span>
);
}
export default IncrementButton;

View File

@@ -0,0 +1,60 @@
import { useContext, useState } from 'react';
import { UserContext } from '../userContext';
import { Navigate } from 'react-router-dom';
function Login(){
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const userContext = useContext(UserContext);
async function Login(e){
e.preventDefault();
const res = await fetch("http://localhost:3001/users/login", {
method: "POST",
credentials: "include",
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify({
username: username,
password: password
})
});
const data = await res.json();
if(data._id !== undefined){
userContext.setUserContext(data);
} else {
setUsername("");
setPassword("");
setError("Invalid username or password");
}
}
return (
<form onSubmit={Login} className="mx-auto w-50">
{userContext.user ? <Navigate replace to="/" /> : ""}
<div className="mb-3">
<label className="form-label" >Uporabniško ime</label>
<input className="form-control" type="text" name="username" placeholder="Uporabniško ime"
value={username} onChange={(e)=>(setUsername(e.target.value))}/>
</div>
<div className="mb-3">
<label className="form-label" >Geslo</label>
<input className="form-control" type="password" name="password" placeholder="Geslo"
value={password} onChange={(e)=>(setPassword(e.target.value))}/>
</div>
<br/>
<div className="text-center">
<input className="btn btn-primary" type="submit" name="submit" value="Log in"/>
</div>
<label>{error}</label>
</form>
);
}
export default Login;

View File

@@ -0,0 +1,20 @@
import { useEffect, useContext } from 'react';
import { UserContext } from '../userContext';
import { Navigate } from 'react-router-dom';
function Logout(){
const userContext = useContext(UserContext);
useEffect(function(){
const logout = async function(){
userContext.setUserContext(null);
const res = await fetch("http://localhost:3001/users/logout");
}
logout();
}, []);
return (
<Navigate replace to="/" />
);
}
export default Logout;

View File

@@ -0,0 +1,23 @@
import { Link } from 'react-router-dom';
function Photo(props) {
return (
<div>
<div className="card w-50">
<img className="card-img" src={"http://localhost:3001/" + props.photo.path} alt={props.photo.name} />
<ul className="list-group list-group-flush">
<li className="list-group-item">Naslov: {props.photo.name}</li>
<li className="list-group-item">Avtor: {props.photo.postedBy.username}</li>
<li className="list-group-item">Objavljen: {props.photo.createdAt}</li>
<li className="list-group-item">Vsecki: {props.photo.likes}</li>
</ul>
<div className="card-body">
<Link className="btn btn-primary" to={`/Detail/${props.photo._id}`}>Podrobnosti</Link>
</div>
</div>
<br />
</div>
);
}
export default Photo;

View File

@@ -0,0 +1,23 @@
import { useState, useEffect } from 'react';
import Photo from './Photo';
function Photos(){
const [photos, setPhotos] = useState([]);
useEffect(function(){
const getPhotos = async function(){
const res = await fetch("http://localhost:3001/photos");
const data = await res.json();
setPhotos(data);
}
getPhotos();
}, []);
return(
<div>
<h3>Slike:</h3>
{photos.map(photo=>(<Photo photo={photo} key={photo._id}></Photo>))}
</div>
);
}
export default Photos;

View File

@@ -0,0 +1,42 @@
import { useContext, useEffect, useState } from 'react';
import { UserContext } from '../userContext';
import { Navigate } from 'react-router-dom';
function Profile(){
const userContext = useContext(UserContext);
const [profile, setProfile] = useState({});
const [totalLikes, setTotalLikes] = useState(0);
const [posts, setPosts] = useState(0);
useEffect(function(){
const getProfile = async function(){
const res = await fetch("http://localhost:3001/users/profile", {credentials: "include"});
const data = await res.json();
setProfile(data);
setTotalLikes(data.totalLikes);
setPosts(data.posts);
}
getProfile();
}, []);
useEffect(() => {
// This useEffect will be triggered whenever totalLikes or posts change,
// and you can use it to update the state variables with the latest values.
setTotalLikes(profile.totalLikes);
setPosts(profile.posts);
}, [profile.totalLikes, profile.posts]);
return (
<>
{!userContext.user ? <Navigate replace to="/login" /> : ""}
<h1>Profil</h1>
<h5>Uporabnisko ime: {profile.username}</h5>
<h5>Email: {profile.email}</h5>
<h6>Stevilo objav: {posts}</h6>
<h6>Stevilo likov: {totalLikes} </h6>
</>
);
}
export default Profile;

View File

@@ -0,0 +1,63 @@
import { useState } from 'react';
function Register() {
const [username, setUsername] = useState([]);
const [password, setPassword] = useState([]);
const [email, setEmail] = useState([]);
const [error, setError] = useState([]);
async function Register(e){
e.preventDefault();
const res = await fetch("http://localhost:3001/users", {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: email,
username: username,
password: password
})
});
const data = await res.json();
if(data._id !== undefined){
window.location.href="/";
}
else{
setUsername("");
setPassword("");
setEmail("");
setError("Registration failed");
}
}
return(
<form className="mx-auto w-50" onSubmit={Register}>
<div className="mb-3">
<label className="form-label">Uporabniško ime</label>
<input className="form-control" type="text" name="username" placeholder="Uporabniško ime" required
value={username} onChange={(e)=>(setUsername(e.target.value))}/>
</div>
<div className="mb-3">
<label className="form-label">Geslo</label>
<input className="form-control" type="password" name="password" placeholder="Geslo" required
value={password} onChange={(e)=>(setPassword(e.target.value))}/>
</div>
<div className="mb-3">
<label className="form-label">Email</label>
<input className="form-control" type="text" name="email" placeholder="E-mail" required
value={email} onChange={(e)=>(setEmail(e.target.value))}/>
</div>
<div className="text-center">
<input className="btn btn-primary" type="submit" name="submit" value="Registracija" />
</div>
<label>{error}</label>
</form>
);
}
export default Register;

View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@@ -0,0 +1,6 @@
import { createContext } from 'react';
export const UserContext = createContext({
user: null,
setUserContext: () => {}
});