Skip to content

Commit

Permalink
Merge pull request #92 from shuyi320/main
Browse files Browse the repository at this point in the history
added favorite
  • Loading branch information
birongliu authored Dec 9, 2024
2 parents c7c4ab3 + ac0b111 commit 012a807
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 14 deletions.
30 changes: 21 additions & 9 deletions backend/routes/api.users.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
addPetToFavorites,
removePetFromFavorites,
create,
getUserById,
} from "../database/models/userInfo.js";
import { get } from "../database/models/petModel.js";
import { createClerkClient } from "@clerk/backend";
const router = Router();

Expand Down Expand Up @@ -115,18 +117,28 @@ router.post("/removePetFromFavorites", async (req, res) => {
});

router.get("/getFavorites/:userId", async (req, res) => {
try {
const { userId } = req.params;
const user = await getUserById(userId);
try {
const { userId } = req.params;
const user = await getUserById(userId);

if (!user) {
return res.status(404).json({ error: "User not found" });
}
if (!user) {
return res.status(404).json({ error: "User not found" });
}

res.status(200).json({ favorites: user.favorites });
} catch (error) {
res.status(400).json({ error: error.message });
const favorites = user.favorites;//array of pet ids
const pets = [];

for (let i = 0; i < favorites.length; i++) {
const pet = await get(favorites[i]);
if (pet) {
pets.push(pet);
}
}

res.status(200).json({ pets });
} catch (error) {
res.status(400).json({ error: error.message });
}
});


Expand Down
20 changes: 20 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
"@tanstack/react-query": "^5.59.20",
"@vercel/analytics": "^1.3.1",
"clsx": "^2.1.1",
"lucide-react": "^0.468.0",
"next": "^14.2.8",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.4.0",
"socket.io-client": "^4.8.1",
"tailwind-merge": "^2.5.2",
"uuid": "^11.0.3"
Expand Down
100 changes: 100 additions & 0 deletions frontend/src/app/(users)/dashboard/favorites/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'use client'
import { useState, useEffect } from "react";
import { Heart, ChevronRight } from "lucide-react";
import Image from "next/image";
import { useUser } from "@clerk/nextjs";

interface Pet {
animalId: string;
name: string;
breed: string;
description: string;
pictureThumbnailUrl: string;
isAdoptionPending: boolean;
size: string;
health: {
isCurrentVaccinations: boolean;
};
isFavorited: boolean;
}

export default function FavoritesPage() {
const [pets, setPets] = useState<Pet[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const { user } = useUser();

useEffect(() => {
const fetchFavorites = async () => {
try {
if (!user) return;
setLoading(true);
const response = await fetch(`http://localhost:3001/api/users/getFavorites/${user.id}`);
if (!response.ok) {
throw new Error("Failed to fetch favorite pets");
}

const data = await response.json();
setPets(
data.pets.map((pet: any) => ({
id: pet.id,
name: pet.name,
breed: pet.breed,
pictureThumbnailUrl: pet.pictureThumbnailUrl,
isFavorited: true,
}))
);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};

fetchFavorites();
}, [user]);

if (loading) return <div className="text-center py-20">Loading favorite pets...</div>;
if (error) return <div className="text-center text-red-500 py-20">{error}</div>;

return (
<div className="min-h-screen ml-64 bg-gray-100 p-8">
<div className="max-w-6xl mx-auto">
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-3xl font-bold text-gray-900">Favorites</h1>
<p className="text-gray-500 mt-2">
Your perfect companions, unforgettable faces, ready to fill your heart and home with love.
</p>
</div>
</div>

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 relative">
{pets.map((pet) => (
<div key={pet.animalId} className="bg-white rounded-lg shadow-md overflow-hidden">
<div className="relative h-[300px]">
<Image
src={`${pet.pictureThumbnailUrl}`}
alt={`Photo of ${pet.name}`}
width={800}
height={400}
className="w-full h-[400px] object-cover"
/>
<button className="absolute top-2 right-2 p-2 rounded-full bg-white/20 hover:bg-white/40 transition-colors">
<Heart className={`h-5 w-5 ${pet.isFavorited ? "fill-red-500 text-red-500" : "text-gray-500"}`} />
</button>
</div>
<div className="p-4">
<h3 className="text-lg font-semibold">{pet.name}</h3>
<p className="text-gray-500">{pet.breed}</p>
</div>
</div>
))}
<button className="absolute -right-12 top-1/2 transform -translate-y-1/2 p-2 rounded-full bg-white shadow-md hover:bg-gray-100 transition-colors">
<ChevronRight className="h-8 w-8" />
</button>
</div>
</div>
</div>
);
}
Binary file not shown.
24 changes: 20 additions & 4 deletions frontend/src/app/pets/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Image from "next/image";
import Link from "next/link";
import { useParams, useRouter } from "next/navigation";
import React, { useEffect, useState } from "react";
import { FaHeart } from "react-icons/fa";
import { useUser } from "@clerk/nextjs";

const pet = {
id: 1,
Expand Down Expand Up @@ -47,6 +49,22 @@ export default function Page() {
};
resolve();
}, [id]);
const { user } = useUser();

const handleFavorite = async () => {
setIsFavorite(!isFavorite);
if (!user) return;
const response = await fetch('http://localhost:3001/api/users/addFavorite', {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ userId: user.id, petId: id }),
})
const data = await response.json();
console.log(data)
}


return (
<div className="container lg:mx-10 px-4 py-8">
Expand All @@ -69,11 +87,9 @@ export default function Page() {
/>
<button
className="absolute top-4 right-4 p-2 bg-white/80 backdrop-blur-sm hover:bg-white/90 rounded-full"
onClick={() => setIsFavorite(!isFavorite)}
onClick={handleFavorite}
>
{/* <Heart
className={`h-5 w-5 ${isFavorite ? "fill-current text-red-500" : "text-gray-600"}`}
/> */}
<FaHeart className={`h-5 w-5 ${isFavorite ? "fill-current text-red-500" : "text-gray-600"}`}/>
<span className="sr-only">Add to favorites</span>
</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

import { io } from "socket.io-client";

export const socket = io(`ws://${process.env.NEXT_PUBLIC_API_URL}`, { withCredentials: true });;
export const socket = io(`ws://${process.env.NEXT_PUBLIC_WS_URL}`, { withCredentials: true });;

0 comments on commit 012a807

Please sign in to comment.