Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions frontend/src/components/plantCard/PlantCard.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@
height: var(--card-name-height);

margin: 0;
padding: 16px 28px;
padding: 5px 40px;

display: flex;
align-items: center;
justify-content: center;

color: var(--dark-green);

font-size: 1.65rem;
font-size: 1.5rem;
font-weight: 700;
line-height: 1.1;
text-align: center;
Expand Down Expand Up @@ -255,7 +255,7 @@
height: auto;
min-height: 62px;

padding: 0 10px;
padding: 0 24px;

color: var(--light-green);

Expand Down
4 changes: 3 additions & 1 deletion frontend/src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@
"pagination": "Page navigation",
"pageOf": "Page {{page}} of {{totalPages}}",
"previous": "Previous",
"next": "Next"
"next": "Next",
"addMatch": "Add to My Matches",
"alreadyMatched": "Already in My Matches"
},
"myMatchesPage": {
"noMatches": "You have no matches yet. Press the flower on the match page to match with a plant.",
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/i18n/locales/no/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@
"pagination": "Sidenavigasjon",
"pageOf": "Side {{page}} av {{totalPages}}",
"previous": "Forrige",
"next": "Neste"
"next": "Neste",
"addMatch": "Legg til i Mine Matches",
"alreadyMatched": "Allerede i Mine Matches"
},
"myMatchesPage": {
"noMatches": "Du har ingen matcher ennå. Trykk på blomsten på match-siden for å matche med en plante.",
Expand Down
142 changes: 119 additions & 23 deletions frontend/src/pages/explorePlants/ExplorePlants.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,40 @@
width: 100%;
}

.explore-search {
position: relative;
display: block;
}

.explore-search-icon {
position: absolute;
top: 50%;
left: 1rem;
font-size: 1.25rem;
transform: translateY(-50%);
color: var(--dark-pink);
pointer-events: none;
}

.explore-search input {
width: 100%;
padding: 0.75rem 1rem;
font-size: 1rem;
border: 1px solid var(--border-soft);
padding: 0.75rem 1rem 0.75rem 2.75rem;
font-size: 1.25rem;
color: var(--text-dark);
background-color: var(--light-pink);
border: 1px solid var(--dark-pink);
border-radius: 8px;
box-sizing: border-box;
}

.explore-search input:focus {
outline: none;
border-color: var(--dark-green);
box-shadow: 0 0 0 2px var(--dark-green);
border-color: var(--dark-pink);
box-shadow: 0 0 0 2px var(--dark-pink);
}

.explore-search input::placeholder {
color: var(--text-dark);
}

.explore-filters {
Expand All @@ -56,42 +77,46 @@
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 1rem;
}

.filter select {
padding: 0.5rem;
border: 1px solid var(--border-soft);
border: 1px solid var(--dark-pink);
border-radius: 8px;
min-width: 160px;
font-size: 15px;
background-color: var(--light-background);
color: var(--text-dark);
}

.filter select:focus {
outline: none;
border-color: var(--dark-green);
box-shadow: 0 0 0 2px var(--dark-green);
border-color: var(--dark-pink);
box-shadow: 0 0 0 2px var(--dark-pink);
}

.reset-button {
align-self: flex-start;
width: 74px;
height: 38px;
border: 1px solid var(--border-soft);
border: 1px solid var(--dark-pink);
border-radius: 8px;
background-color: var(--light-green);
background-color: var(--light-background);
color: var(--text-dark);
font-size: 16px;
font-size: 15px;
cursor: pointer;
}

.reset-button:hover {
background-color: var(--dark-green);
background-color: var(--dark-pink);
color: var(--text-light);
}

.reset-button:focus-within {
outline: none;
border: var(--dark-green);
box-shadow: 0 0 0 2px var(--dark-green);
border: var(--dark-pink);
box-shadow: 0 0 0 2px var(--dark-pink);
}

.explore-results {
Expand All @@ -102,6 +127,12 @@
background-color: var(--light-pink);
}

.explore-results p {
font-size: 20px;
text-align: center;
font-family: Georgia, serif;
}

.plant-grid {
list-style: none;
margin: 0;
Expand All @@ -118,6 +149,7 @@
width: calc(390px * var(--card-scale));
height: calc(620px * var(--card-scale));
justify-self: center;
position: relative;
}

.explore-plant-item .plant-card-flipper {
Expand All @@ -138,7 +170,7 @@

.pagination button {
padding: 0.5rem 1rem;
border: 1px solid var(--border-soft);
border: 1px solid var(--dark-green);
border-radius: 8px;
background-color: var(--light-green);
color: var(--text-dark);
Expand Down Expand Up @@ -172,23 +204,23 @@
justify-content: center;
width: 40px;
height: 40px;
border: 1px solid var(--border-soft);
border: 1px solid var(--dark-pink);
border-radius: 8px;
background-color: var(--light-green);
background-color: var(--light-background);
color: var(--text-dark);
font-size: 1.4rem;
font-size: 1.5rem;
cursor: pointer;
}

.sort-button:hover {
background-color: var(--dark-green);
background-color: var(--dark-pink);
color: var(--text-light);
}

.sort-button:focus-visible,
.sort-option:focus-visible {
outline: none;
box-shadow: 0 0 0 2px var(--dark-green);
box-shadow: 0 0 0 2px var(--dark-pink);
}

.sort-options {
Expand All @@ -201,7 +233,7 @@
padding: 0.25rem;
list-style: none;
background-color: var(--light-background);
border: 1px solid var(--border-soft);
border: 1px solid var(--dark-pink);
border-radius: 8px;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
}
Expand All @@ -219,14 +251,73 @@
}

.sort-option:hover {
background-color: var(--light-green);
background-color: var(--light-pink);
}

.sort-option--active {
background-color: var(--light-green);
background-color: var(--light-pink);
font-weight: 700;
}

.add-match {
position: absolute;
top: -15px;
right: -15px;
z-index: 2;
}

.add-match-tooltip {
position: absolute;
top: calc(100% + 8px);
right: 0;
z-index: 20;
padding: 8px 12px;
background: var(--light-background);
color: var(--pink-text);
border-radius: 10px;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
font-size: 14px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition:
opacity 0.15s ease,
visibility 0.15s ease;
}

.add-match:hover .add-match-tooltip,
.add-match:focus-within .add-match-tooltip {
opacity: 1;
visibility: visible;
}

.add-match-button {
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 50%;
background-color: var(--dark-pink);
color: var(--text-light);
font-size: 18px;
line-height: 1;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}

.add-match-button:not(:disabled):hover {
transform: scale(1.1);
}

.add-match-button:disabled {
opacity: 0.5;
cursor: default;
pointer-events: none;
}

@media (max-width: 767px) {
.explore-page {
margin-left: 0;
Expand Down Expand Up @@ -255,4 +346,9 @@
height: 100%;
transform: none;
}

.add-match-button {
width: 35px;
height: 35px;
}
}
55 changes: 42 additions & 13 deletions frontend/src/pages/explorePlants/ExplorePlants.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from "react";
import { BiSortAlt2 } from "react-icons/bi";
import { FaSearch } from "react-icons/fa";
import { useTranslation } from "react-i18next";
import { useQuery } from "@apollo/client/react";
import { useDebounce } from "../../hooks/useDebounce";
Expand All @@ -9,6 +10,7 @@ import {
type ExplorePlantsData,
} from "./explorePlantsQuery.ts";
import PlantCard from "../../components/plantCard/PlantCard.tsx";
import { useMatchedPlants } from "../../hooks/useMatchedPlants.ts";
import "./ExplorePlants.css";

const PAGE_SIZE = 12;
Expand All @@ -18,6 +20,9 @@ const SORT_OPTIONS = ["name_asc", "name_desc", "watering_asc", "careLevel_asc"];
export default function ExplorePlants() {
const { t } = useTranslation();

// Same hook as PlantMatch, so both pages share the same matches
const { matches, addMatch } = useMatchedPlants();

const [search, setSearch] = useSessionState("ExplorePlants.search");
const debouncedSearch = useDebounce(search, 800);
const [watering, setWatering] = useSessionState("ExplorePlants.watering");
Expand Down Expand Up @@ -77,6 +82,7 @@ export default function ExplorePlants() {
>
<label className="explore-search">
<span className="visually-hidden">{t("ExplorePlants.search")}</span>
<FaSearch className="explore-search-icon" aria-hidden="true" />
<input
type="search"
placeholder={t("ExplorePlants.search")}
Expand Down Expand Up @@ -202,19 +208,42 @@ export default function ExplorePlants() {
)}

<ul className="plant-grid">
{plants.map((plant) => (
<li key={plant.id} className="explore-plant-item">
<PlantCard
name={plant.name}
imageURL={plant.imageURL}
scientificName={plant.scientificName}
description={plant.description}
watering={plant.watering}
sunlight={plant.sunlight}
careLevel={plant.careLevel}
/>
</li>
))}
{plants.map((plant) => {
// Disable the button if the plant is already in MyMatches
const isMatched = matches.some((match) => match.id === plant.id);
const addLabel = isMatched
? t("ExplorePlants.alreadyMatched")
: t("ExplorePlants.addMatch");

return (
<li key={plant.id} className="explore-plant-item">
<PlantCard
name={plant.name}
imageURL={plant.imageURL}
scientificName={plant.scientificName}
description={plant.description}
watering={plant.watering}
sunlight={plant.sunlight}
careLevel={plant.careLevel}
/>
{/* Sibling of the card (not inside it), so no nested buttons */}
<span className="add-match">
<button
type="button"
className="add-match-button"
onClick={() => addMatch(plant)}
disabled={isMatched}
aria-label={addLabel}
>
✿
</button>
<span className="add-match-tooltip" aria-hidden="true">
{addLabel}
</span>
</span>
</li>
);
})}
</ul>

{totalCount > 0 && (
Expand Down
Loading