Skip to content

Commit

Permalink
Merge pull request #3 from thejoltjoker/client/favorites
Browse files Browse the repository at this point in the history
Client/favorites
  • Loading branch information
thejoltjoker authored Mar 3, 2024
2 parents 5c06878 + 72204f4 commit dd1c8f7
Show file tree
Hide file tree
Showing 26 changed files with 599 additions and 192 deletions.
3 changes: 2 additions & 1 deletion client/src/components/ImageGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ const ImageGrid = ({ images: results }: Props) => {
<>
<div className="relative w-full columns-1 gap-4 px-4 py-8 sm:columns-2 md:gap-8 md:px-8 lg:columns-3 2xl:columns-4">
{results.map((item) => (
<ImageGridItem key={item.link} data={item} />
<ImageGridItem key={item.link} image={item} />
))}
</div>
<div className="pb-8 text-center">
{/* TODO Load more images */}
<Button>Load more</Button>
</div>
</>
Expand Down
32 changes: 11 additions & 21 deletions client/src/components/ImageGridItem.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
import { ImageItem } from "../models/ImageItem";
import SaveImage from "./SaveImage";
// TODO Show placeholder on broken img
interface ImageGridItemProps {
data: ImageItem;
image: ImageItem;
}

const ImageGridItem = ({ data }: ImageGridItemProps) => {
const ImageGridItem = ({ image }: ImageGridItemProps) => {
return (
<div
className="group relative mt-4 rounded-2xl bg-slate-200 transition duration-300 first:mt-0 hover:scale-[103%] md:mt-8"
style={{
aspectRatio: `${data.thumbnailWidth} / ${data.thumbnailHeight}`,
aspectRatio: `${image.thumbnailWidth} / ${image.thumbnailHeight}`,
}}
>
<div className="image-title absolute bottom-3 left-3 z-20 w-full overflow-clip whitespace-nowrap text-white opacity-0 transition duration-200 group-hover:opacity-100">
{data.title}
{image.title}
</div>
<div className="absolute left-3 top-3 z-20 rounded-full p-1 text-white transition group-hover:bg-blue-500">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="stroke h-6 w-6 translate-y-[1px] fill-none stroke-white stroke-2 group-hover:fill-white"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"
/>
</svg>
<div className="absolute left-3 top-3 z-20">
<SaveImage image={image} />
</div>
<div className="absolute bottom-3 right-3 z-20 text-white">
<svg
Expand All @@ -46,13 +36,13 @@ const ImageGridItem = ({ data }: ImageGridItemProps) => {
</svg>
</div>
<img
src={data.thumbnailLink}
alt={data.title}
src={image.thumbnailLink}
alt={image.title}
className="z-10 w-full overflow-hidden rounded-2xl transition duration-300 group-hover:shadow-xl"
style={{
aspectRatio: `${data.thumbnailWidth} / ${data.thumbnailHeight}`,
aspectRatio: `${image.thumbnailWidth} / ${image.thumbnailHeight}`,
}}
onMouseOver={(e) => (e.currentTarget.src = data.link)}
onMouseOver={(e) => (e.currentTarget.src = image.link)}
/>
</div>
);
Expand Down
50 changes: 50 additions & 0 deletions client/src/components/SaveImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useFavoritesContext } from "../contexts/FavoritesContext";
import { ImageItem } from "../models/ImageItem";
import { FavoritesActionType } from "../reducers/FavoritesReducer";
import { createFavorite } from "../services/user.service";

type Props = { image: ImageItem };

const SaveImage = ({ image }: Props) => {
const { favorites, dispatch } = useFavoritesContext();

// TODO Dynamically get userId
const userId = "1";
const handleSave = async () => {
try {
await createFavorite(userId, image);
dispatch({
type: FavoritesActionType.Add,
payload: [image],
});
} catch (error) {
console.error("Error when saving image to favorites");
}
};

return (
<button
className={
favorites.find((img) => img.imageId === image.imageId)
? "rounded-full bg-blue-500 p-1 text-white transition"
: "rounded-full p-1 text-white transition group-hover:bg-blue-500"
}
onClick={handleSave}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className={`stroke h-6 w-6 translate-y-[1px] fill-none stroke-white stroke-2 group-hover:fill-white ${favorites.find((img) => img.imageId === image.imageId) && "fill-white"}`}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"
/>
</svg>
</button>
);
};

export default SaveImage;
15 changes: 15 additions & 0 deletions client/src/contexts/FavoritesContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Dispatch, createContext, useContext } from "react";
import { ImageItem } from "../models/ImageItem";
import { FavoritesAction } from "../reducers/FavoritesReducer";

interface FavoritesContextModel {
favorites: ImageItem[];
dispatch: Dispatch<FavoritesAction>;
}

export const FavoritesContext = createContext<FavoritesContextModel>({
favorites: [],
dispatch: () => {},
});

export const useFavoritesContext = () => useContext(FavoritesContext);
15 changes: 0 additions & 15 deletions client/src/contexts/UserContext.ts

This file was deleted.

69 changes: 41 additions & 28 deletions client/src/layouts/MainLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,53 @@
import { Outlet } from "react-router-dom";
import Navbar from "../components/Navbar";
import { UserContext } from "../contexts/UserContext";
import { FavoritesContext } from "../contexts/FavoritesContext";

import { Auth0Provider } from "@auth0/auth0-react";
import axios from "axios";
import { useEffect, useReducer } from "react";
import { User } from "../models/User";
import { UserActionType, UserReducer } from "../reducers/UserReducer";
import {
FavoritesActionType,
FavoritesReducer,
} from "../reducers/FavoritesReducer";
import { getFavorites } from "../services/user.service";

const MainLayout = () => {
const [user, userDispatch] = useReducer(UserReducer, {
userId: "0",
email: "",
favorites: [],
});
const initialState = [
{
imageId: "initial",
title: "",
snippet: "",
contextLink: "",
link: "",
thumbnailLink: "",
thumbnailWidth: 0,
thumbnailHeight: 0,
},
];
const [favorites, dispatch] = useReducer(FavoritesReducer, initialState);

useEffect(() => {
if (user.userId != "0") return;
let ignore = false;
const fetchUser = async () => {
try {
const userId = "1";
const url = `http://localhost:3000/api/user/${userId}`;
const response = await axios.get<User>(url);
if (response && !ignore) {
console.log(response.data);
userDispatch({
type: UserActionType.SET,
payload: { ...response.data },
});
if (favorites.length == 1 && favorites[0].imageId === "initial") {
const fetchUser = async () => {
try {
const userId = "1";

const response = await getFavorites(userId);
if (response && !ignore) {
console.log(response);
dispatch({
type: FavoritesActionType.Set,
payload: response,
});
}
} catch (error) {
console.error("Error when getting user.");
}
} catch (error) {
console.error("Error when getting user.");
}
};
fetchUser();
};
fetchUser();
} else {
return;
}
return () => {
ignore = true;
};
Expand All @@ -48,10 +61,10 @@ const MainLayout = () => {
redirect_uri: window.location.origin,
}}
>
<UserContext.Provider value={{ user, dispatch: userDispatch }}>
<FavoritesContext.Provider value={{ favorites, dispatch }}>
<Navbar />
<Outlet />
</UserContext.Provider>
</FavoritesContext.Provider>
</Auth0Provider>
</div>
);
Expand Down
5 changes: 5 additions & 0 deletions client/src/models/GetFavoritesResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ImageItem } from "./ImageItem";

export interface GetFavoritesResponse {
favorites: ImageItem[];
}
1 change: 1 addition & 0 deletions client/src/models/ImageItem.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface ImageItem {
imageId: string;
title: string;
snippet: string;
contextLink: string;
Expand Down
4 changes: 2 additions & 2 deletions client/src/models/User.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UserFavorite } from "./UserFavorite";
import { ImageItem } from "./ImageItem";

export interface User {
userId?: string;
email: string;
favorites: UserFavorite[];
favorites: ImageItem[];
}
5 changes: 0 additions & 5 deletions client/src/models/UserFavorite.ts

This file was deleted.

18 changes: 6 additions & 12 deletions client/src/pages/SavedPage.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import axios from "axios";
import { useEffect, useState } from "react";
import { useState } from "react";
import ImageGrid from "../components/ImageGrid";
import { useFavoritesContext } from "../contexts/FavoritesContext";
import { ImageItem } from "../models/ImageItem";
import { User } from "../models/User";
import { useUserContext } from "../contexts/UserContext";

const SavedPage = () => {
const [images, setImages] = useState<ImageItem[]>();
const { user, dispatch } = useUserContext();


const { favorites, dispatch } = useFavoritesContext();

return (
<div className="flex">
<div className="max-w-1/4 h-full border border-green-400">
<p className="text-2xl">{user && user.email}</p>
</div>
<div className="max-w-1/4 h-full border border-green-400"></div>
<div className="grow">
{images ? (
<ImageGrid images={images} />
{favorites ? (
<ImageGrid images={favorites} />
) : (
<p className="text-center text-slate-500">No images saved.</p>
)}
Expand Down
26 changes: 26 additions & 0 deletions client/src/reducers/FavoritesReducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ImageItem } from "../models/ImageItem";

export enum FavoritesActionType {
Set,
Add,
}

export interface FavoritesAction {
type: FavoritesActionType;
payload: ImageItem[];
}

export const FavoritesReducer = (
favorites: ImageItem[],
action: FavoritesAction,
) => {
switch (action.type) {
case FavoritesActionType.Set: {
return [...action.payload];
}
case FavoritesActionType.Add: {
return [...favorites, ...action.payload];
}
}
throw new Error("Unknown action: " + action.type);
};
28 changes: 0 additions & 28 deletions client/src/reducers/UserReducer.ts

This file was deleted.

Loading

0 comments on commit dd1c8f7

Please sign in to comment.