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,38 @@
.DS_STORE
node_modules
scripts/flow/*/.flowconfig
.flowconfig
*~
*.pyc
.grunt
_SpecRunner.html
__benchmarks__
build/
remote-repo/
coverage/
.module-cache
fixtures/dom/public/react-dom.js
fixtures/dom/public/react.js
test/the-files-to-test.generated.js
*.log*
chrome-user-data
*.sublime-project
*.sublime-workspace
.idea
*.iml
.vscode
*.swp
*.swo
packages/react-devtools-core/dist
packages/react-devtools-extensions/chrome/build
packages/react-devtools-extensions/chrome/*.crx
packages/react-devtools-extensions/chrome/*.pem
packages/react-devtools-extensions/firefox/build
packages/react-devtools-extensions/firefox/*.xpi
packages/react-devtools-extensions/firefox/*.pem
packages/react-devtools-extensions/shared/build
packages/react-devtools-extensions/.tempUserDataDir
packages/react-devtools-inline/dist
packages/react-devtools-shell/dist
packages/react-devtools-timeline/dist

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1.1",
"@testing-library/user-event": "^13.5.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
<title>Photos</title>
</head>
<body class="container">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

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: () => {}
});