diff --git a/backend/src/main/java/ropold/backend/controller/MemoryController.java b/backend/src/main/java/ropold/backend/controller/MemoryController.java index 1b2edb7..8c19786 100644 --- a/backend/src/main/java/ropold/backend/controller/MemoryController.java +++ b/backend/src/main/java/ropold/backend/controller/MemoryController.java @@ -106,6 +106,44 @@ public MemoryModel addMemory( )); } + @ResponseStatus(HttpStatus.CREATED) + @PostMapping("/avatar") + public MemoryModel addMemoryAvatar (@RequestBody MemoryModelDto memoryModelDto) { + return memoryService.addMemoryAvatar( + new MemoryModel( + null, + memoryModelDto.name(), + memoryModelDto.matchId(), + memoryModelDto.category(), + memoryModelDto.description(), + memoryModelDto.isActive(), + memoryModelDto.appUserGithubId(), + memoryModelDto.appUserUsername(), + memoryModelDto.appUserAvatarUrl(), + memoryModelDto.appUserGithubUrl(), + memoryModelDto.imageUrl() + )); + } + + @PutMapping("/avatar/{id}") + public MemoryModel updateMemoryAvatar(@PathVariable String id, @RequestBody MemoryModelDto memoryModelDto) { + return memoryService.updateMemoryAvatar( + id, + new MemoryModel( + id, + memoryModelDto.name(), + memoryModelDto.matchId(), + memoryModelDto.category(), + memoryModelDto.description(), + memoryModelDto.isActive(), + memoryModelDto.appUserGithubId(), + memoryModelDto.appUserUsername(), + memoryModelDto.appUserAvatarUrl(), + memoryModelDto.appUserGithubUrl(), + memoryModelDto.imageUrl() + )); + } + @PutMapping("/{id}") public MemoryModel updateMemory( @PathVariable String id, diff --git a/backend/src/main/java/ropold/backend/service/MemoryService.java b/backend/src/main/java/ropold/backend/service/MemoryService.java index 7430f0e..0ed339d 100644 --- a/backend/src/main/java/ropold/backend/service/MemoryService.java +++ b/backend/src/main/java/ropold/backend/service/MemoryService.java @@ -104,6 +104,45 @@ public MemoryModel toggleMemoryActive(String id) { return memoryRepository.save(updatedMemoryModel); } + public MemoryModel addMemoryAvatar(MemoryModel memoryModel) { + memoryModel = new MemoryModel( + idService.generateRandomId(), + memoryModel.name(), + memoryModel.matchId(), + memoryModel.category(), + memoryModel.description(), + memoryModel.isActive(), + memoryModel.appUserGithubId(), + memoryModel.appUserUsername(), + memoryModel.appUserAvatarUrl(), + memoryModel.appUserGithubUrl(), + memoryModel.imageUrl() + ); + return memoryRepository.save(memoryModel); + } + + public MemoryModel updateMemoryAvatar(String id, MemoryModel memoryModel) { + if (!memoryRepository.existsById(id)) { + throw new MemoryNotFoundException("No Memory found with ID: " + id); + } + + MemoryModel updatedMemoryModel = new MemoryModel( + id, + memoryModel.name(), + memoryModel.matchId(), + memoryModel.category(), + memoryModel.description(), + memoryModel.isActive(), + memoryModel.appUserGithubId(), + memoryModel.appUserUsername(), + memoryModel.appUserAvatarUrl(), + memoryModel.appUserGithubUrl(), + memoryModel.imageUrl() + ); + return memoryRepository.save(updatedMemoryModel); + } + + //Not Used public List getMemoriesByMatchId(int matchId) { return memoryRepository.findAll().stream() diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3f1af71..0510d0e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -139,7 +139,7 @@ export default function App() { } /> } /> } /> - } /> + } /> }> } /> diff --git a/frontend/src/components/AddMemoryCard.tsx b/frontend/src/components/AddMemoryCard.tsx index 92afc6e..8b18cb6 100644 --- a/frontend/src/components/AddMemoryCard.tsx +++ b/frontend/src/components/AddMemoryCard.tsx @@ -27,25 +27,19 @@ export default function AddMemoryCard(props: Readonly) { // 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 + if (category === "GITHUB_AVATAR" && props.userDetails?.avatar_url) { + setImageUrl(props.userDetails.avatar_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 + }, [category, props.userDetails?.avatar_url]); // Abhängig von der Kategorie und der GitHub-URL des Benutzers const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - const data = new FormData(); - - if (image) { - data.append("image", image); - } - const memoryData = { name, - matchId: matchId, // matchId als number + matchId, category, description, isActive: true, @@ -56,35 +50,69 @@ export default function AddMemoryCard(props: Readonly) { 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."); - } - }); - } + if (category === "GITHUB_AVATAR") { + // JSON-POST an den Avatar-Endpunkt + axios + .post("/api/memory-hub/avatar", memoryData, { + headers: { + "Content-Type": "application/json", + } + }) + .then((response) => { + console.log("Avatar Memory gespeichert:", 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."); + } + }); + } else { + // Multipart-POST an den Standard-Endpunkt + const data = new FormData(); + + if (image) { + data.append("image", image); + } + + data.append("memoryModelDto", new Blob([JSON.stringify(memoryData)], { type: "application/json" })); + + axios + .post("/api/memory-hub", data, { + headers: { + "Content-Type": "multipart/form-data", + } + }) + .then((response) => { + console.log("Memory gespeichert:", 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) => { if (e.target.files) { diff --git a/frontend/src/components/Details.tsx b/frontend/src/components/Details.tsx index 9db3a0e..b5f6a80 100644 --- a/frontend/src/components/Details.tsx +++ b/frontend/src/components/Details.tsx @@ -1,37 +1,76 @@ import { MemoryModel } from "./model/MemoryModel.ts"; import { useParams } from "react-router-dom"; import {DefaultMemory} from "./model/DefaultMemory.ts"; +import "./styles/Details.css"; +import "./styles/Profile.css"; +import {useEffect, useState} from "react"; +import axios from "axios"; type DetailsProps = { allMemories: MemoryModel[]; + favorites: string[]; + user: string; + toggleFavorite: (memoryId: string) => void; }; - export default function Details(props: Readonly) { + const [memory, setMemory] = useState(DefaultMemory); const { id } = useParams<{ id: string }>(); - // Suche das Memory-Objekt mit der passenden ID - const memory = props.allMemories.find(mem => mem.id === id) || DefaultMemory; + const fetchMemoryDetails = () => { + if (!id) return; + axios + .get(`/api/memory-hub/${id}`) + .then((response) => setMemory(response.data)) + .catch((error) => console.error("Error fetching memory details", error)); + }; + + useEffect(() => { + fetchMemoryDetails(); + }, [id]); + + const isFavorite = props.favorites.includes(memory.id); return ( -
- {memory ? ( +
+

{memory.name}

+

Category: {memory.category}

+

MatchId: {memory.matchId}

+

Description: {memory.description || "No description available"}

+ + + {memory.imageUrl && ( + {memory.name} + )} + + {props.user !== "anonymousUser" && ( +
+ +
+ )} + +
+

Memory added by GitHub User

-

{memory.name}

-

Category: {memory.category}

-

Description: {memory.description}

-

Active: {memory.isActive ? "Yes" : "No"}

+

Username: {memory.appUserUsername}

- Created by: + GitHub Profile: - {memory.appUserUsername} + Visit Profile

- {memory.name} + {`${memory.appUserUsername}'s
- ) : ( -

Memory not found.

- )} +
); } diff --git a/frontend/src/components/Home.tsx b/frontend/src/components/Home.tsx index 493e634..e9a66da 100644 --- a/frontend/src/components/Home.tsx +++ b/frontend/src/components/Home.tsx @@ -1,6 +1,7 @@ -import {MemoryModel} from "./model/MemoryModel.ts"; +import { MemoryModel } from "./model/MemoryModel.ts"; +import { useEffect, useState } from "react"; import MemoryCard from "./MemoryCard"; - +import SearchBar from "./SearchBar.tsx"; type HomeProps = { user: string; @@ -10,16 +11,91 @@ type HomeProps = { showSearch: boolean; currentPage: number; paginate: (pageNumber: number) => void; -} +}; export default function Home(props: Readonly) { + const [searchQuery, setSearchQuery] = useState(""); + const [filteredMemories, setFilteredMemories] = useState([]); + const [filterType, setFilterType] = useState<"name" | "category" | "matchId" | "all">("name"); + const [selectedCategory, setSelectedCategory] = useState(""); + const memoriesPerPage = 9; + + useEffect(() => { + if (!props.showSearch) { + setSearchQuery(""); + } + }, [props.showSearch]); + + useEffect(() => { + const filtered = filterMemories(props.activeMemories, searchQuery, filterType, selectedCategory); + setFilteredMemories(filtered); + }, [props.activeMemories, searchQuery, filterType, selectedCategory]); + + const filterMemories = (memories: MemoryModel[], query: string, filterType: string, category: string | "") => { + const lowerQuery = query.toLowerCase(); + + return memories.filter((memory) => { + const matchesCategory = category ? memory.category === category : true; + const matchesName = filterType === "name" && memory.name.toLowerCase().includes(lowerQuery); + const matchesMatchId = filterType === "matchId" && memory.matchId.toString().includes(lowerQuery); + const matchesAll = + filterType === "all" && + (memory.name.toLowerCase().includes(lowerQuery) || + memory.category.toLowerCase().includes(lowerQuery) || + memory.matchId.toString().includes(lowerQuery)); + + return matchesCategory && (matchesName || matchesMatchId || matchesAll); + }); + }; + + const getPaginationData = (memories: MemoryModel[]) => { + const indexOfLastMemory = props.currentPage * memoriesPerPage; + const indexOfFirstMemory = indexOfLastMemory - memoriesPerPage; + const currentMemories = memories.slice(indexOfFirstMemory, indexOfLastMemory); + const totalPages = Math.ceil(memories.length / memoriesPerPage); + return { currentMemories, totalPages }; + }; + + const { currentMemories, totalPages } = getPaginationData(filteredMemories); + return ( -
-
- {props.activeMemories.map(memory => ( - + <> + {props.showSearch && ( + + )} + +
+ {currentMemories.map((memory) => ( + ))}
-
+ +
+ {Array.from({ length: totalPages }, (_, index) => ( + + ))} +
+ ); -} \ No newline at end of file +} diff --git a/frontend/src/components/MemoryCard.tsx b/frontend/src/components/MemoryCard.tsx index a63d5f3..5d8290e 100644 --- a/frontend/src/components/MemoryCard.tsx +++ b/frontend/src/components/MemoryCard.tsx @@ -1,6 +1,7 @@ import {useNavigate} from "react-router-dom"; import {MemoryModel} from "./model/MemoryModel.ts"; import "./styles/MemoryCard.css"; +import "./styles/Buttons.css"; type MemoryCardProps = { memory: MemoryModel @@ -17,10 +18,25 @@ export default function MemoryCard(props: Readonly) { navigate(`/memory/${props.memory.id}`); } + const isFavorite = props.favorites.includes(props.memory.id); + return (

{props.memory.name}

{props.memory.name} + + {props.user !== "anonymousUser" && ( + + )}
); } \ No newline at end of file diff --git a/frontend/src/components/MyMemories.tsx b/frontend/src/components/MyMemories.tsx index abe9d0f..efdf6e9 100644 --- a/frontend/src/components/MyMemories.tsx +++ b/frontend/src/components/MyMemories.tsx @@ -24,6 +24,7 @@ export default function MyMemories(props: Readonly) { const [editedMemory, setEditedMemory] = useState(null); const [image, setImage] = useState(null); const [category, setCategory] = useState("CLOUDINARY_IMAGE"); + const [imageUrl, setImageUrl] = useState(null); const [showPopup, setShowPopup] = useState(false); const [memoryToDelete, setMemoryToDelete] = useState(null); @@ -31,6 +32,14 @@ export default function MyMemories(props: Readonly) { setUserMemories(props.allMemories.filter(memory => memory.appUserGithubId === props.user)); }, [props.allMemories, props.user]); + useEffect(() => { + if (category === "GITHUB_AVATAR" && props.userDetails?.avatar_url) { + setImageUrl(props.userDetails.avatar_url); + } else { + setImage(null); + } + }, [category, props.userDetails?.avatar_url]); + const handleCategoryChange = (e: React.ChangeEvent) => { setCategory(e.target.value as Category); }; @@ -40,46 +49,80 @@ export default function MyMemories(props: Readonly) { if (memoryToEdit) { setEditedMemory(memoryToEdit); setIsEditing(true); - if (memoryToEdit.imageUrl) { + + if (memoryToEdit.imageUrl === memoryToEdit.appUserAvatarUrl) { + setCategory("GITHUB_AVATAR"); + } else { + setCategory(memoryToEdit.category); + } + + if (memoryToEdit.category === "GITHUB_AVATAR" && props.userDetails?.avatar_url) { + setImageUrl(props.userDetails.avatar_url); + } else { + setImageUrl(""); + } + + if (memoryToEdit.category === "CLOUDINARY_IMAGE" && memoryToEdit.imageUrl) { fetch(memoryToEdit.imageUrl) .then(response => response.blob()) .then(blob => { const file = new File([blob], "current-image.jpg", { type: blob.type }); - setImage(file); + setImage(file); // Setze das Bild als File }) .catch(error => console.error("Error loading current image:", error)); } else { - setImage(null); + setImage(null); // Falls kein Bild vorhanden, setze das Bild auf null } } }; + const handleSaveEdit = async (e: React.FormEvent) => { e.preventDefault(); if (!editedMemory) return; - const data = new FormData(); + if (category === "GITHUB_AVATAR") { + // JSON-Request für GitHub Avatar + const updatedMemoryData = { ...editedMemory, imageUrl: imageUrl ?? "" }; // Nutze das imageUrl direkt - if (image) { - data.append("image", image); - setEditedMemory(prev => prev ? { ...prev, imageUrl: "temp-image" } : null); - } + axios.put(`/api/memory-hub/avatar/${editedMemory.id}`, updatedMemoryData, { + headers: { "Content-Type": "application/json" } + }) + .then(response => { + props.setAllMemories(prevMemories => + prevMemories.map(memory => memory.id === editedMemory.id ? { ...memory, ...response.data } : memory) + ); + setIsEditing(false); + }) + .catch(error => { + console.error("Error saving changes:", error); + alert("An unexpected error occurred. Please try again later."); + }); - const updatedMemoryData = { ...editedMemory }; + } else { + // Multipart-Request für andere Kategorien + const data = new FormData(); - data.append("memoryModelDto", new Blob([JSON.stringify(updatedMemoryData)], { type: "application/json" })); + if (image) { + data.append("image", image); + setEditedMemory(prev => prev ? { ...prev, imageUrl: "temp-image" } : null); + } - axios.put(`/api/memory-hub/${editedMemory.id}`, data, { headers: { "Content-Type": "multipart/form-data" } }) - .then(response => { - props.setAllMemories(prevMemories => - prevMemories.map(memory => memory.id === editedMemory.id ? { ...memory, ...response.data } : memory) - ); - setIsEditing(false); - }) - .catch(error => { - console.error("Error saving changes:", error); - alert("An unexpected error occurred. Please try again later."); - }); + const updatedMemoryData = { ...editedMemory }; + data.append("memoryModelDto", new Blob([JSON.stringify(updatedMemoryData)], { type: "application/json" })); + + axios.put(`/api/memory-hub/${editedMemory.id}`, data, { headers: { "Content-Type": "multipart/form-data" } }) + .then(response => { + props.setAllMemories(prevMemories => + prevMemories.map(memory => memory.id === editedMemory.id ? { ...memory, ...response.data } : memory) + ); + setIsEditing(false); + }) + .catch(error => { + console.error("Error saving changes:", error); + alert("An unexpected error occurred. Please try again later."); + }); + } }; const onFileChange = (e: React.ChangeEvent) => { @@ -113,6 +156,31 @@ export default function MyMemories(props: Readonly) { setMemoryToDelete(null); }; + const handleToggleActiveStatus = (memoryId: string) => { + axios + .put(`/api/memory-hub/${memoryId}/toggle-active`) + .then(() => { + props.setAllMemories((prevMemories) => + prevMemories.map((memory) => + memory.id === memoryId ? { ...memory, isActive: !memory.isActive } : memory + ) + ); + }) + .catch((error) => { + console.error("Error during Toggle Offline/Active", error); + alert("An Error while changing the status of Active/Offline."); + }); + }; + + const handleMatchIdChange = (e: React.ChangeEvent) => { + setEditedMemory(prev => { + if (prev) { + return { ...prev, matchId: parseInt(e.target.value) }; // Setze matchId im editedMemory + } + return prev; + }); + }; + return (
{isEditing ? ( @@ -124,7 +192,7 @@ export default function MyMemories(props: Readonly) { setEditedMemory({ ...editedMemory!, name: e.target.value }) } @@ -143,11 +211,39 @@ export default function MyMemories(props: Readonly) { + + +