Skip to content

Commit

Permalink
Merge pull request #3 from Ropold/frontend1
Browse files Browse the repository at this point in the history
Frontend1
  • Loading branch information
Ropold authored Feb 18, 2025
2 parents a3be941 + f2bed66 commit b9acbbd
Show file tree
Hide file tree
Showing 14 changed files with 404 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
public record MemoryModel(
String id,
String name,
String matchId,
int matchId,
Category category,
String description,
boolean isActive,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public record MemoryModelDto(
String name,
String matchId,
int matchId,
Category category,
String description,
boolean isActive,
Expand Down
46 changes: 45 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@ import Home from "./components/Home.tsx";
import NotFound from "./components/NotFound.tsx";
import {MemoryModel} from "./components/model/MemoryModel.ts";
import Play from "./components/Play.tsx";
import Profile from "./components/Profile.tsx";
import ProtectedRoute from "./components/ProtectedRoute.tsx";
import AddMemoryCard from "./components/AddMemoryCard.tsx";
import MyMemories from "./components/MyMemories.tsx";
import {UserDetails} from "./components/model/UserDetailsModel.ts";
import Details from "./components/Details.tsx";

export default function App() {

const [user, setUser] = useState<string>("anonymousUser");
const [userDetails, setUserDetails] = useState<UserDetails | null>(null);
const [activeMemories, setActiveMemories] = useState<MemoryModel[]>([]);
const [allMemories, setAllMemories] = useState<MemoryModel[]>([]);


function getUser() {
Expand All @@ -26,6 +34,17 @@ export default function App() {
});
}

function getUserDetails() {
axios.get("/api/users/me/details")
.then((response) => {
setUserDetails(response.data as UserDetails);
})
.catch((error) => {
console.error(error);
setUserDetails(null);
});
}

const getActiveMemories = () => {
axios
.get("/api/memory-hub/active")
Expand All @@ -37,11 +56,29 @@ export default function App() {
});
}

const getAllMemories = () => {
axios
.get("/api/memory-hub")
.then((response) => {
setAllMemories(response.data);
})
.catch((error) => {
console.error(error);
});
}

useEffect(() => {
getUser();
getActiveMemories();
}, []);

useEffect(() => {
if (user !== "anonymousUser") {
getUserDetails();
getAllMemories();
}
}, [user]);

return (
<>
<Navbar
Expand All @@ -50,8 +87,15 @@ export default function App() {
/>
<Routes>
<Route path="*" element={<NotFound />} />
<Route path="/" element={<Home />} />
<Route path="/" element={<Home activeMemories={activeMemories} />} />
<Route path="/play" element={<Play activeMemories={activeMemories} />} />
<Route path="/memory/:id" element={<Details allMemories={allMemories} />} />

<Route element={<ProtectedRoute user={user} />}>
<Route path="/my-memories" element={<MyMemories />} />
<Route path="/add" element={<AddMemoryCard userDetails={userDetails}/>} />
<Route path="/profile" element={<Profile userDetails={userDetails} />} />
</Route>
</Routes>
<Footer />
</>
Expand Down
190 changes: 187 additions & 3 deletions frontend/src/components/AddMemoryCard.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,194 @@
import { UserDetails } from "./model/UserDetailsModel.ts";
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";

export default function AddMemoryCard() {
type MemoryCardProps = {
userDetails: UserDetails | null;
}

export default function AddMemoryCard(props: Readonly<MemoryCardProps>) {

const [name, setName] = useState<string>("");
const [category, setCategory] = useState<string>("CLOUDINARY_IMAGE");
const [description, setDescription] = useState<string>("");
const [image, setImage] = useState<File | null>(null);
const [imageUrl, setImageUrl] = useState<string>(""); // Setzt den Image-URL State
const [matchId, setMatchId] = useState<number>(1); // Defaultwert als Zahl (1)
const [errorMessages, setErrorMessages] = useState<string[]>([]);
const [showPopup, setShowPopup] = useState(false);

const navigate = useNavigate();

// useEffect, um imageUrl zu setzen, wenn die Kategorie sich ändert und GitHub Avatar ausgewählt wird
useEffect(() => {
if (category === "GITHUB_AVATAR" && props.userDetails?.html_url) {
setImageUrl(props.userDetails.html_url); // Setze imageUrl auf die GitHub URL
} else {
setImageUrl(""); // Leere das imageUrl bei anderen Kategorien
}
}, [category, props.userDetails?.html_url]); // Abhängig von der Kategorie und der GitHub-URL des Benutzers

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

const data = new FormData();

if (image) {
data.append("image", image);
}

const memoryData = {
name,
matchId: matchId, // matchId als number
category,
description,
isActive: true,
appUserGithubId: props.userDetails?.id,
appUserUsername: props.userDetails?.login,
appUserAvatarUrl: props.userDetails?.avatar_url,
appUserGithubUrl: props.userDetails?.html_url,
imageUrl: imageUrl,
};

data.append("memoryModelDto", new Blob([JSON.stringify(memoryData)], {type: "application/json"}));

console.log("memoryData:", memoryData);

axios
.post("/api/memory-hub", data, {
headers: {
"Content-Type": "multipart/form-data",
}
})
.then((response) => {
console.log("Antwort vom Server:", response.data);
navigate(`/memory/${response.data.id}`);
})
.catch((error) => {
if (error.response && error.response.status === 400 && error.response.data) {
const errorMessages = error.response.data;
const errors: string[] = [];
Object.keys(errorMessages).forEach((field) => {
errors.push(`${field}: ${errorMessages[field]}`);
});

setErrorMessages(errors);
setShowPopup(true);
} else {
alert("An unexpected error occurred. Please try again.");
}
});
}

const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
setImage(e.target.files[0]);
}
}

const handleClosePopup = () => {
setShowPopup(false);
setErrorMessages([]);
};

// onChange für den matchId-Input
const onMatchIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(e.target.value); // Umwandlung von String in Zahl
if (!isNaN(value)) {
setMatchId(Math.min(20, Math.max(1, value))); // Stellen sicher, dass der Wert im Bereich 1 bis 20 liegt
}
};

return (
<div>
<div className="edit-form">
<h2>Add Memory Card</h2>
<form onSubmit={handleSubmit}>
<label>
Name:
<input
className="input-small"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>

<label>
Category:
<select
className="input-small"
value={category}
onChange={(e) => setCategory(e.target.value)}
required
>
<option value="CLOUDINARY_IMAGE">Cloudinary Image</option>
<option value="GITHUB_AVATAR">GitHub Avatar</option>
</select>
</label>

<label>
Description:
<textarea
className="textarea-large"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</label>

<label>
Match ID (default 1, max 20):
<input
className="input-small"
type="number"
value={matchId}
onChange={onMatchIdChange} // Verwendung der angepassten Methode
min="1"
max="20"
/>
</label>

<label>
Image:
<input
type="file"
onChange={onFileChange}
disabled={category === "GITHUB_AVATAR"} // Deaktiviert das Dateiauswahlfeld, wenn GitHub Avatar ausgewählt ist
/>
</label>

{category === "GITHUB_AVATAR" && props.userDetails?.avatar_url && (
<img
src={props.userDetails.avatar_url}
className="memory-card-image"
alt="GitHub Avatar"
/>
)}

{image && category === "CLOUDINARY_IMAGE" && (
<img src={URL.createObjectURL(image)} className="memory-card-image" alt="Preview" />
)}

<div className="button-group">
<button type="submit">Add Memory Card</button>
</div>
</form>

{showPopup && (
<div className="popup-overlay">
<div className="popup-content">
<h3>Validation Errors</h3>
<ul>
{errorMessages.map((msg, index) => (
<li key={index}>{msg}</li>
))}
</ul>
<div className="popup-actions">
<button className="popup-cancel" onClick={handleClosePopup}>Close</button>
</div>
</div>
</div>
)}
</div>
);

}
38 changes: 38 additions & 0 deletions frontend/src/components/Details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { MemoryModel } from "./model/MemoryModel.ts";
import { useParams } from "react-router-dom";
import {DefaultMemory} from "./model/DefaultMemory.ts";

type DetailsProps = {
allMemories: MemoryModel[];
};


export default function Details(props: Readonly<DetailsProps>) {
const { id } = useParams<{ id: string }>();

// Suche das Memory-Objekt mit der passenden ID
const memory = props.allMemories.find(mem => mem.id === id) || DefaultMemory;

return (
<div>
<h2>Memory Details</h2>
{memory ? (
<div>
<h3>{memory.name}</h3>
<p><strong>Category:</strong> {memory.category}</p>
<p><strong>Description:</strong> {memory.description}</p>
<p><strong>Active:</strong> {memory.isActive ? "Yes" : "No"}</p>
<p>
<strong>Created by:</strong>
<a href={memory.appUserGithubUrl} target="_blank" rel="noopener noreferrer">
{memory.appUserUsername}
</a>
</p>
<img src={memory.imageUrl} alt={memory.name} width={300} />
</div>
) : (
<p>Memory not found.</p>
)}
</div>
);
}
19 changes: 15 additions & 4 deletions frontend/src/components/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import {MemoryModel} from "./model/MemoryModel.ts";
import MemoryCard from "./MemoryCard";

export default function Home(){
return(

type HomeProps = {
activeMemories: MemoryModel[];
}

export default function Home(props: Readonly<HomeProps>) {
return (
<div>
<h2>MemoryHub - Home</h2>
<div>
{props.activeMemories.map(memory => (
<MemoryCard key={memory.id} memory={memory} />
))}
</div>
</div>
)
);
}
22 changes: 22 additions & 0 deletions frontend/src/components/MemoryCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {useNavigate} from "react-router-dom";
import {MemoryModel} from "./model/MemoryModel.ts";

type MemoryCardProps = {
memory: MemoryModel
}

export default function MemoryCard(props: Readonly<MemoryCardProps>) {

const navigate = useNavigate();

const handleCardClick = () => {
navigate(`/memory/${props.memory.id}`);
}

return (
<div className="memory-card" onClick={handleCardClick}>
<h3>{props.memory.name}</h3>
<img src={props.memory.imageUrl} alt={props.memory.name} className="memory-card-image" />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

export default function MyMemoriesCards(){
export default function MyMemories(){
return (
<div>
<h2>My Memories Cards</h2>
Expand Down
Loading

0 comments on commit b9acbbd

Please sign in to comment.