Skip to content

Commit

Permalink
Merge branch 'main' into realtime-chat-backend
Browse files Browse the repository at this point in the history
  • Loading branch information
andrezz-b committed Apr 7, 2024
2 parents 4cad7f3 + 728efe2 commit c0a9bf6
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 184 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public class MatchController {
@Autowired
private InfobipClient infobipClient;

@Value("${infobip.sender-email}")
private String SENDER_EMAIL_ADDRESS;

@Value("${infobip.active}")
private boolean isInfobipActive;

Expand All @@ -59,7 +62,7 @@ public ResponseEntity<Match> accept(HttpServletRequest request, @Valid @RequestB

if (match != null) {
if(isInfobipActive) {
var recepients = new ArrayList<>(List.of("dominikkovacevic6@gmail.com"));
var recepients = new ArrayList<>(List.of(SENDER_EMAIL_ADDRESS));
infobipClient.sendEmail(recepients, "You got a match!", user.getFirstName());
infobipClient.sendSms(matchedUser.get().getFirstName(), user.getFirstName());
}
Expand Down
241 changes: 60 additions & 181 deletions frontend/src/components/MatchCard.tsx
Original file line number Diff line number Diff line change
@@ -1,193 +1,72 @@
import ClearIcon from "@mui/icons-material/Clear";
import CheckIcon from "@mui/icons-material/Check";
import { UsersService } from "@/api/users";
import cx from "classnames";
import { useEffect, useMemo, useState } from "react";
import ProfileCard from "./ProfileCard";
import { useQueryClient } from "@tanstack/react-query";
import { CircularProgress } from "@mui/material";

const PAGE_SIZE = 5;

const MatchCard = () => {
const [lodaing, setLoading] = useState(false);
const [page, setPage] = useState(0);
const [currentUserIndex, setCurrentUserIndex] = useState(0);
const result = UsersService.useGetPotentailUsers(page);
const acceptMatch = UsersService.useAcceptMatch();
const rejectMatch = UsersService.useRejectMatch();
const [isProfileOpen, setIsProfileOpen] = useState(false);
const prefetchPotential = UsersService.usePrefetchPotentialUsers(page);
const queryClient = useQueryClient();
import { ProjectedUser } from "@/types/User";
import { useMemo } from "react";
import LocationOnIcon from "@mui/icons-material/LocationOnOutlined";
import InfoIcon from "@mui/icons-material/Info";
interface MatchCardProps {
user: ProjectedUser;
loading: boolean;
handleNextUser: (matchAccept: boolean, userId: string) => void;
openDetailedProfile: () => void;
}

const handleNextUser = (matchAccept: boolean, userId: string) => {
setLoading(true);
const match = matchAccept ? acceptMatch.mutate : rejectMatch.mutate;
if (currentUserIndex === PAGE_SIZE - 3) {
prefetchPotential();
const MatchCard = ({
user,
loading,
handleNextUser,
openDetailedProfile,
}: MatchCardProps) => {
const truncatedDescription = useMemo(() => {
if (user.description.length > 50) {
return user.description.slice(0, 50) + "...";
}
match(
{ userId },
{
onSettled: () => {
if (currentUserIndex === PAGE_SIZE - 1) {
setPage((prev) => prev + 1);
setCurrentUserIndex(0);
} else {
setCurrentUserIndex((prev) => prev + 1);
}
setLoading(false);
},
}
);
};

// When the component unmounts it is necessary to invalidate the query
// because the query is cached and it will return the same data
// but the new component will start from index 0 of the array
useEffect(() => {
return () => {
queryClient.invalidateQueries({
queryKey: ["UsersService.getPotentialUsers"],
});
};
}, [queryClient]);

const user = useMemo(() => {
if (!result.data) return undefined;
if (result.data.length <= currentUserIndex) return undefined;
return result.data[currentUserIndex];
}, [currentUserIndex, result.data]);

if (result.isLoading || result.isFetching) {
return (
<MatchCardContainer>
<div className="flex justify-center items-center h-full w-full text-red-500">
<CircularProgress size="3rem" color="inherit" />
</div>
</MatchCardContainer>
);
}

if (result.isError || !result.isSuccess) {
return (
<MatchCardContainer>
<div className="flex justify-center flex-col items-center h-full w-full text-center px-4">
<h2 className="text-red-500 mb-2 text-3xl">Whoops.</h2>
<span className="text-lg">
Something went wrong, try refreshing the page
</span>
</div>
</MatchCardContainer>
);
}

if (result.data.length === 0 || !user) {
return (
<MatchCardContainer>
<div className="flex justify-center items-center h-full w-full text-center px-4 text-lg sm:text-2xl">
No more users? Impossible! Maybe they&apos;re just hiding...
</div>
</MatchCardContainer>
);
}

const openProfile = () => {
setIsProfileOpen(true);
};

const closeProfile = () => {
setIsProfileOpen(false);
};
const truncatedDescription = user.description.slice(0, 100);
return user.description;
}, [user.description]);
return (
<>
{!isProfileOpen && (
<MatchCardContainer>
<div className="flex w-full justify-center rounded-xl mt-4 mb-6">
<div
className={cx(
"flex h-[185px] w-[185px] items-center justify-center rounded-full",
{
"bg-pink-400": user.gender === "F",
"bg-blue-400": user.gender === "M",
}
)}
>
<img
className="h-44 w-44 rounded-full"
src={user.profileImageUrl || "/Default_pfp.svg"}
alt=""
/>
</div>
</div>
<div className="flex flex-col items-center w-full justify-between flex-grow gap-6 md:max-h-[22rem]">
<div className="w-full flex flex-col justify-center items-center">
<h4 className="text-5xl font-bold text-navy-700 dark:text-white">
{`${user.firstName}, ${user.age}`}
</h4>
<p className="font-normal text-lg dark:text-gray-200 mt-4">
{user.location}
</p>
<div className="flex justify-center items-center mt-4 text-gray-400">
<p className="flex align-middle text-center text-lg overflow-hidden overflow-ellipsis">
{truncatedDescription}
</p>
</div>
<div>
<button
className="mt-3 text-gray-500 hover:text-gray-300 text-lg"
onClick={() => openProfile()}
>
View profile
</button>
</div>
</div>
<div className="flex justify-between w-full text-white pb-4 px-2">
<button
className=" hover:bg-green-600 bg-green-500 transition-color duration-300 sm:ml-4 border-green-700 rounded-full w-24 h-24 shadow-md shadow-black"
onClick={() => handleNextUser(true, user.id)}
disabled={lodaing}
>
<CheckIcon fontSize="large" />
</button>
<button
className="bg-red-500 hover:bg-red-600 transition-color duration-300 rounded-full sm:mr-2 border-red-700 btn-circle w-24 h-24 shadow-md shadow-black"
onClick={() => handleNextUser(false, user.id)}
disabled={lodaing}
>
<ClearIcon fontSize="large" />
</button>
</div>
</div>
</MatchCardContainer>
)}
{isProfileOpen && (
<div className="flex w-full flex-grow justify-center md:pb-8">
<ProfileCard user={user} onClose={closeProfile} />
</div>
)}
</>
);
};

interface MatchCardContainerProps {
children?: React.ReactNode;
}

const MatchCardContainer = ({
children,
...props
}: MatchCardContainerProps) => {
return (
<div className="flex flex-col items-center flex-grow sm:pb-12 min-h-fit max-h-[800px] md:max-h-[700px]">
<div
className="flex flex-col items-center rounded-[25px] border-[1px] border-black-200 flex-grow w-full sm:w-[24rem] p-4 bg-white dark:bg-[#343030] bg-clip-border border-[#acabab33] shadow-xl shadow-black"
{...props}
className="flex flex-col justify-end gap-2 rounded-xl relative bg-cover bg-center w-[95%] sm:w-[90%]
min-h-[60vh] sm:min-h-[32rem] mb-4 flex-grow"
style={{ backgroundImage: `url(${user.profileImageUrl})` }}
>
{children}
<div className="absolute h-full w-full bg-gradient-to-t rounded-xl from-black from-0% to-40%"></div>
<div className="top-5 left-4 absolute flex items-center justify-center gap-1 bg-gray-500/60 p-2 rounded-lg text-sm text-white">
<LocationOnIcon color="inherit" fontSize="inherit" />
<span>{user.location}</span>
</div>
<div
className="flex z-10 items-center gap-2 px-5 pb-4 text-white cursor-pointer"
onClick={openDetailedProfile}
>
<div className="flex flex-col gap-2">
<h2 className="text-white font-bold text-2xl">
{user.firstName}, {user.age}
</h2>
<p className="m-0 text-sm text-gray-300">{truncatedDescription}</p>
</div>
<InfoIcon />
</div>
</div>
</div>
<div className="flex flex-col items-center w-full justify-center flex-grow">
<div className="flex justify-between w-full text-white px-6 sm:p-6">
<button
className=" hover:bg-green-600 bg-green-500 transition-color duration-300 sm:ml-4 border-green-700 rounded-full w-24 h-24 shadow-md shadow-black"
onClick={() => handleNextUser(true, user.id)}
disabled={loading}
>
<CheckIcon fontSize="large" />
</button>
<button
className="bg-red-500 hover:bg-red-600 transition-color duration-300 rounded-full sm:mr-2 border-red-700 btn-circle w-24 h-24 shadow-md shadow-black"
onClick={() => handleNextUser(false, user.id)}
disabled={loading}
>
<ClearIcon fontSize="large" />
</button>
</div>
</div>
</>
);
};

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import ReactDOM from "react-dom/client";
import "./index.css";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { AuthProvider } from "./context/AuthProvider.tsx";
import MatchCard from "./components/MatchCard.tsx";
import ErrorPage from "./ErrorPage.tsx";
import LoginForm from "./views/LoginForm.tsx";
import RegisterForm from "./views/RegisterForm.tsx";
Expand All @@ -23,6 +22,7 @@ import SettingsProfile from "./views/settings/SettingsProfile.tsx";
import SettingsProfilePicture from "./views/settings/SettingsProfilePicture.tsx";
import { ThemeProvider } from "./context/ThemeProvider.tsx";
import SettingsTheme from "./views/settings/SettingsTheme.tsx";
import PotentialUsers from "./views/PotentialUsers.tsx";
import { StompSessionProvider } from "react-stomp-hooks";
import ChatLayout from "./views/chat/ChatLayout.tsx";

Expand All @@ -32,7 +32,7 @@ const router = createBrowserRouter([
element: <ProtectedRoutes layout={<Root />} />,
errorElement: <ErrorPage />,
children: [
{ index: true, element: <MatchCard /> },
{ index: true, element: <PotentialUsers /> },
{
path: "matches",
errorElement: <ErrorPage />,
Expand Down
Loading

0 comments on commit c0a9bf6

Please sign in to comment.