Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(favorite): add / remove instrument cards to a favorite list #81

Merged
merged 6 commits into from
Aug 15, 2024
Merged
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
17 changes: 17 additions & 0 deletions client/src/pages/catalogue/api/fetch-favorite-instrument-ids.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import axios from "axios";
import { SERVER_URL } from "shared/config";

export const fetchFavoriteInstrumentIds = async () => {
const { data, status } = await axios.get<number[]>(
`${SERVER_URL}/api/favorite/list`,
{
withCredentials: true,
},
);

if (status !== 200) {
throw new Error("Failed to extract favorite instruments");
}

return data;
};
26 changes: 26 additions & 0 deletions client/src/pages/catalogue/api/fetch-instruments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import axios from "axios";
import { Page } from "domain/model/page";
import {
API_INSTRUMENTS,
CATALOGUE_DEFAULT_PAGE_NUMBER,
CATALOGUE_DEFAULT_PAGE_SIZE,
SERVER_URL,
} from "shared/config";

export const fetchInstruments = async () => {
const { data, status } = await axios.post<Page>(
`${SERVER_URL}${API_INSTRUMENTS}`,
{},
{
params: {
pageNumber: CATALOGUE_DEFAULT_PAGE_NUMBER,
pageSize: CATALOGUE_DEFAULT_PAGE_SIZE,
},
},
);
if (status !== 200) {
throw new Error(`Failed to extract instruments`);
}

return data;
};
33 changes: 15 additions & 18 deletions client/src/pages/catalogue/api/loader.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import axios from "axios";
import { Instruments } from "domain/model/instrument";
import { Page } from "domain/model/page";
import { API_INSTRUMENTS, SERVER_URL } from "shared/config/backend";
import { fetchFavoriteInstrumentIds } from "pages/catalogue";
import { fetchInstruments } from "pages/catalogue/api/fetch-instruments";

export const loader = async (): Promise<Instruments> => {
const { data, status } = await axios.post<Page>(
`${SERVER_URL}${API_INSTRUMENTS}`,
{},
{
params: {
pageNumber: 1,
pageSize: 3,
},
},
);
if (status !== 200) {
throw new Error(`Failed to extract instruments`);
}
return data.content;
export interface CatalogueLoader {
instrumentPage: Page;
favoriteInstrumentIds: number[];
}

export const loader = async (): Promise<CatalogueLoader> => {
const instruments = await fetchInstruments();
const favoriteInstrumentIds = await fetchFavoriteInstrumentIds();

return {
instrumentPage: instruments,
favoriteInstrumentIds: favoriteInstrumentIds,
};
};
6 changes: 4 additions & 2 deletions client/src/pages/catalogue/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Catalogue } from "./ui/Catalogue";
import { loader } from "./api/loader";
import { CatalogueLoader } from "./api/loader";
import { fetchFavoriteInstrumentIds } from "./api/fetch-favorite-instrument-ids";

export { Catalogue };
export { loader };
export { Catalogue, loader, fetchFavoriteInstrumentIds };
export type { CatalogueLoader };
4 changes: 2 additions & 2 deletions client/src/pages/catalogue/ui/Catalogue.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
}
}

#catalogue-filter-serp-wrapper {
#catalogue-wrapper {
display: flex;
flex-direction: row;
background-color: darkorchid;
Expand All @@ -21,7 +21,7 @@
flex: 1;
}

#catalogue-serp-and-navbar-wrapper {
#catalogue-serp-navbar-wrapper {
background-color: aqua;
flex: 2;

Expand Down
54 changes: 26 additions & 28 deletions client/src/pages/catalogue/ui/Catalogue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,29 @@ import {
} from "shared/config/frontend";
import { getInstrumentsByCriteria } from "shared/api/list-instruments-by-criteria";
import { Page } from "domain/model/page";
import { SearchBarForm } from "./SearchBarForm";
import { NavigationBar } from "./NavigationBar";
import { CatalogueLoader, fetchFavoriteInstrumentIds } from "pages/catalogue";

export function Catalogue() {
useJwt();
const initialInstruments = useLoaderData() as Instruments; // https://github.com/remix-run/react-router/discussions/9792
const [instruments, setInstruments] =
useState<Instruments>(initialInstruments);
const loader = useLoaderData() as CatalogueLoader; // https://github.com/remix-run/react-router/discussions/9792
const [instruments, setInstruments] = useState<Instruments>(
loader.instrumentPage.content,
);
const [instrumentName, setInstrumentName] = useState<string | null>(null);
const [filters, setFilters] = useState<Filters>(DEFAULT_FILTER);
const [pageNumber, setPageNumber] = useState<number>(
CATALOGUE_DEFAULT_PAGE_NUMBER,
);
const totalPages = useRef<number>(0);
const [favoriteInstrumentIds, setFavoriteInstrumentIds] = useState<number[]>(
loader.favoriteInstrumentIds,
);

useEffect(() => {
fetchFavoriteInstrumentIds().then((ids) => setFavoriteInstrumentIds(ids));

if (instrumentName === "") {
filters.instrumentName = null;
}
Expand All @@ -42,6 +51,7 @@ export function Catalogue() {
pageNumber: pageNumber,
pageSize: CATALOGUE_DEFAULT_PAGE_SIZE,
} as Page;

getInstrumentsByCriteria(filters, page).then((r) => {
setInstruments(r.content);
totalPages.current = r.totalPages;
Expand All @@ -52,38 +62,26 @@ export function Catalogue() {
<div id="catalogue">
<Header />

<div id="catalogue-search-bar-form">
<input
type="text"
placeholder={"Search..."}
onChange={(e) => {
setInstrumentName(e.target.value);
}}
/>
</div>
<SearchBarForm setInstrumentName={setInstrumentName} />

<div id="catalogue-filter-serp-wrapper">
<div id="catalogue-wrapper">
<CatalogueFilterWidget
onFilterChange={(newFilters: Filters) => {
setFilters(newFilters);
console.log("Called catalogue filter widget");
}}
role={Jwt.extractFromLocalStorage()?.toRole()}
/>
<div id="catalogue-serp-and-navbar-wrapper">
<CatalogueSerpWidget instruments={instruments} />
<div id="pages-navigation-bar">
{pageNumber > 1 && (
<button onClick={() => setPageNumber(pageNumber - 1)}>
Previous
</button>
)}
{pageNumber < totalPages.current && (
<button onClick={() => setPageNumber(pageNumber + 1)}>
Next
</button>
)}
</div>

<div id="catalogue-serp-navbar-wrapper">
<CatalogueSerpWidget
instruments={instruments}
favoriteInstrumentIds={favoriteInstrumentIds}
/>
<NavigationBar
pageNumber={pageNumber}
totalPages={totalPages.current}
setPageNumber={setPageNumber}
/>
</div>
</div>

Expand Down
24 changes: 24 additions & 0 deletions client/src/pages/catalogue/ui/NavigationBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";

interface Props {
pageNumber: number;
totalPages: number;
setPageNumber: (pageNumber: number) => void;
}

export const NavigationBar = (props: Props) => {
return (
<div id="pages-navigation-bar">
{props.pageNumber > 1 && (
<button onClick={() => props.setPageNumber(props.pageNumber - 1)}>
Previous
</button>
)}
{props.pageNumber < props.totalPages && (
<button onClick={() => props.setPageNumber(props.pageNumber + 1)}>
Next
</button>
)}
</div>
);
};
19 changes: 19 additions & 0 deletions client/src/pages/catalogue/ui/SearchBarForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";

interface Props {
setInstrumentName: (name: string) => void;
}

export const SearchBarForm = (props: Props) => {
return (
<div id="catalogue-search-bar-form">
<input
type="text"
placeholder={"Search..."}
onChange={(e) => {
props.setInstrumentName(e.target.value);
}}
/>
</div>
);
};
16 changes: 10 additions & 6 deletions client/src/widgets/catalogue-serp/ui/CatalogueSerpWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import React from "react";
import "./CatalogueSerpWidget.css";
import { InstrumentDetails } from "./InstrumentDetails";
import { InstrumentActions } from "./InstrumentActions";
import { InstrumentActions } from "./actions/InstrumentActions";
import { Instruments } from "domain/model/instrument";

interface Props {
instruments: Instruments;
favoriteInstrumentIds: number[];
}

export const CatalogueSerpWidget = ({ instruments }: Props) => {
console.log("instruments type:", typeof instruments);
console.log("instruments value:", instruments);

export const CatalogueSerpWidget = ({
instruments,
favoriteInstrumentIds,
}: Props) => {
return (
<div id="catalogue-serp">
{instruments.map((instrument) => (
<div key={instrument.id.toString()} className="instrument-card">
<InstrumentDetails instrument={instrument} />
<InstrumentActions instrument={instrument} />
<InstrumentActions
instrument={instrument}
favorite={favoriteInstrumentIds.includes(instrument.id)}
/>
</div>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
import { Instrument } from "domain/model/instrument";
import axios from "axios";
import { SERVER_URL } from "shared/config";
import { API_FAVORITE } from "shared/config/backend";
import { useState } from "react";

interface Props {
instrument: Instrument;
instrumentId: number;
favorite: boolean;
}

export const AddToFavoriteButton = (props: Props) => {
const handleAddToFavorite = () => {
axios.post(`${SERVER_URL}${API_FAVORITE}`, {
instrumentId: props.instrument.id,
});
const [favorite, setFavorite] = useState<boolean>(props.favorite);

const toggleFavorite = async () => {
if (favorite) {
await axios.post(
`${SERVER_URL}/api/favorite/remove`,
{
instrumentId: props.instrumentId,
},
{
withCredentials: true,
},
);
} else {
await axios.post(
`${SERVER_URL}/api/favorite/add`,
{
instrumentId: props.instrumentId,
},
{
withCredentials: true,
},
);
}
setFavorite(!favorite);
};

return (
<button
key={props.instrument.id}
className={"add-to-favorite-button"}
onClick={handleAddToFavorite}
>
Favorite
<button onClick={toggleFavorite}>
{favorite ? "Remove from Favorite" : "Add to Favorite"}
</button>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import Jwt from "domain/model/jwt";
import { Modal } from "widgets/modal";
import "./InstrumentActions.css";
import { Role } from "domain/model/role";
import { RemoveInstrumentButton } from "./actions/RemoveInstrumentButton";
import { GoToInstrumentButton } from "./actions/GoToInstrumentButton";
import { EditInstrumentButton } from "./actions/EditInstrumentButton";
import { AddToFavoriteButton } from "./actions/AddToFavoriteButton";
import { RemoveInstrumentButton } from "./RemoveInstrumentButton";
import { GoToInstrumentButton } from "./GoToInstrumentButton";
import { EditInstrumentButton } from "./EditInstrumentButton";
import { AddToFavoriteButton } from "./AddToFavoriteButton";

interface Props {
instrument: Instrument;
favorite: boolean;
}

export const InstrumentActions = ({ instrument }: Props) => {
export const InstrumentActions = ({ instrument, favorite }: Props) => {
const [successModal, setSuccessModal] = useState<boolean>(false);

return (
Expand All @@ -28,7 +29,7 @@ export const InstrumentActions = ({ instrument }: Props) => {
</>
)}

<AddToFavoriteButton instrument={instrument} />
<AddToFavoriteButton instrumentId={instrument.id} favorite={favorite} />
<GoToInstrumentButton instrument={instrument} />

<Modal
Expand Down
1 change: 1 addition & 0 deletions server/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dependencies {
implementation("io.jsonwebtoken:jjwt-jackson:0.11.5")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation(kotlin("stdlib-jdk8"))
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.2")
}

tasks.named<Test>("test") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mu.muse.application.muse

import mu.muse.rest.HelloEndpoint
import mu.muse.rest.country.GetCountriesEndpoint
import mu.muse.rest.favorite.FavoriteEndpoint
import mu.muse.rest.instruments.CreateInstrumentEndpoint
import mu.muse.rest.instruments.DeleteInstrumentByIdEndpoint
import mu.muse.rest.instruments.EditInstrumentEndpoint
Expand Down Expand Up @@ -78,4 +79,7 @@ class RestConfiguration {

@Bean
fun registrationEndpoint(registerUser: RegisterUser) = RegistrationEndpoint(registerUser)

@Bean
fun favoriteEndpoint() = FavoriteEndpoint()
}
Loading
Loading