From f409f4b50f561b2969f95c64b495ea71716ca368 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 23 Jul 2021 13:01:51 +0200 Subject: [PATCH 1/3] Fix favorite repository layout / use react-query for favorite repository toggle --- CHANGELOG.md | 1 + build.gradle | 2 +- src/main/js/FavoriteRepositoryToggleIcon.tsx | 41 +++++------- src/main/js/data/FavoriteRepositoryCard.tsx | 9 +-- src/main/js/data/MyData.tsx | 19 +++++- .../js/data/MyFavoriteRepositoriesData.tsx | 2 +- src/main/js/favoriteRepository.ts | 65 +++++++++++++++++++ 7 files changed, 103 insertions(+), 36 deletions(-) create mode 100644 src/main/js/favoriteRepository.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b9c5b6..cb922c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Unreleased ### Changed +- Fix favorite repository layout to match new repository entry ([#35](https://github.com/scm-manager/scm-landingpage-plugin/pull/35)) - Use react-query instead plain api calls ([#34](https://github.com/scm-manager/scm-landingpage-plugin/pull/34)) ## 1.5.0 - 2021-04-22 diff --git a/build.gradle b/build.gradle index 50b66c1..83b139b 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ dependencies { } scmPlugin { - scmVersion = "2.20.0" + scmVersion = "2.21.1-SNAPSHOT" displayName = "Landingpage" description = "Creates a personal landingpage for each user" author = "Cloudogu GmbH" diff --git a/src/main/js/FavoriteRepositoryToggleIcon.tsx b/src/main/js/FavoriteRepositoryToggleIcon.tsx index 58e9094..c92af17 100644 --- a/src/main/js/FavoriteRepositoryToggleIcon.tsx +++ b/src/main/js/FavoriteRepositoryToggleIcon.tsx @@ -21,11 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { FC, useEffect, useState } from "react"; +import React, { FC } from "react"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; -import { Link, Repository } from "@scm-manager/ui-types"; -import { apiClient, Icon } from "@scm-manager/ui-components"; +import { Repository } from "@scm-manager/ui-types"; +import { Icon } from "@scm-manager/ui-components"; +import { useFavoriteRepository } from "./favoriteRepository"; type Props = { repository: Repository; @@ -35,45 +36,33 @@ type Props = { const SpanWithPointer = styled.span` pointer-events: all; cursor: pointer; + z-index: 1; `; const FavoriteRepositoryToggleIcon: FC = ({ repository, classes }) => { const [t] = useTranslation("plugins"); - const [favorite, setFavorite] = useState(false); - const [link, setLink] = useState(""); - - useEffect(() => { - setFavorite(!!repository?._links?.unfavorize); - setLink(getLink(repository)); - }, []); - - const getLink: (repository: Repository) => string = (repository: Repository) => { - const currentLink = (repository?._links?.unfavorize || repository?._links?.favorize) as Link; - return currentLink.href; - }; + const { favorize, unfavorize } = useFavoriteRepository(repository); const toggleFavoriteStatus = () => { - apiClient - .post(link) - .then(() => apiClient.get((repository._links.self as Link).href)) - .then(response => response.json()) - .then(updatedRepository => { - setFavorite(!favorite); - setLink(getLink(updatedRepository)); - }); + if (favorize) { + favorize(); + } + if (unfavorize) { + unfavorize(); + } }; return ( toggleFavoriteStatus()}> diff --git a/src/main/js/data/FavoriteRepositoryCard.tsx b/src/main/js/data/FavoriteRepositoryCard.tsx index 78db193..81ed89c 100644 --- a/src/main/js/data/FavoriteRepositoryCard.tsx +++ b/src/main/js/data/FavoriteRepositoryCard.tsx @@ -22,7 +22,6 @@ * SOFTWARE. */ import React, { FC } from "react"; -import classNames from "classnames"; import styled from "styled-components"; import { RepositoryEntry } from "@scm-manager/ui-components"; import { RepositoryDataType } from "../types"; @@ -43,11 +42,9 @@ const RepositoryEntryWrapper = styled.div` const FavoriteRepositoryCard: FC = ({ data }) => { return ( -
- - - -
+ + + ); }; diff --git a/src/main/js/data/MyData.tsx b/src/main/js/data/MyData.tsx index afd089e..4c0c74f 100644 --- a/src/main/js/data/MyData.tsx +++ b/src/main/js/data/MyData.tsx @@ -50,12 +50,21 @@ export type ExtensionProps = { emptyMessage?: string; }; +const Separator = styled.div` + border-bottom: 1px solid rgb(219, 219, 219, 0.5); + margin: 0 1rem; +`; + +const Box = styled.div` + padding: 0.5rem; +`; + const MyData: FC = ({ links }) => { const [t] = useTranslation("plugins"); const { data, error, isLoading } = useMyData((links?.landingpageData as Link)?.href); const renderExtension: (extension: ExtensionProps) => any = extension => { - const dataForExtension = data?._embedded.data.filter(data => data.type === extension.type); + const dataForExtension = data?._embedded.data.filter(entry => entry.type === extension.type); if (dataForExtension?.length === 0 && !extension.emptyMessage) { return null; @@ -66,7 +75,13 @@ const MyData: FC = ({ links }) => { separatedEntries={extension.separatedEntries} emptyMessage={extension.emptyMessage} > - {dataForExtension?.map((dataEntry, key) => extension.render(dataEntry, key))} + + {dataForExtension?.map((dataEntry, key) => ( + <> + {extension.render(dataEntry, key)} {key + 1 !== dataForExtension.length ? : null} + + ))} + ); } diff --git a/src/main/js/data/MyFavoriteRepositoriesData.tsx b/src/main/js/data/MyFavoriteRepositoriesData.tsx index 8b39da7..dcc5d7b 100644 --- a/src/main/js/data/MyFavoriteRepositoriesData.tsx +++ b/src/main/js/data/MyFavoriteRepositoriesData.tsx @@ -26,7 +26,7 @@ import React from "react"; import { binder } from "@scm-manager/ui-extensions"; import { RepositoryDataType } from "../types"; import FavoriteRepositoryCard from "./FavoriteRepositoryCard"; -import {ExtensionProps} from "./MyData"; +import { ExtensionProps } from "./MyData"; const favoriteRepositoryData: ExtensionProps = { render: (data: RepositoryDataType, key: any) => data.repository && , diff --git a/src/main/js/favoriteRepository.ts b/src/main/js/favoriteRepository.ts new file mode 100644 index 0000000..282aff6 --- /dev/null +++ b/src/main/js/favoriteRepository.ts @@ -0,0 +1,65 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { apiClient } from "@scm-manager/ui-components"; +import { Link, Repository } from "@scm-manager/ui-types"; +import { useMutation, useQueryClient } from "react-query"; + +export const useFavoriteRepository = (repository: Repository) => { + const queryClient = useQueryClient(); + + const invalidateQueries = () => { + queryClient.invalidateQueries(["repository", repository.namespace, repository.name]); + queryClient.invalidateQueries(["landingpage", "myData"]); + return queryClient.invalidateQueries(["repositories"]); + }; + + const { mutate: favorize, isLoading: isLoadingFavorize, error: favorizeError } = useMutation( + () => { + return apiClient.post((repository?._links?.favorize as Link).href, {}); + }, + { + onSuccess: invalidateQueries + } + ); + + const { mutate: unfavorize, isLoading: isLoadingUnfavorize, error: unfavorizeError } = useMutation< + unknown, + Error + >( + () => { + return apiClient.post((repository?._links?.unfavorize as Link).href, {}); + }, + { + onSuccess: invalidateQueries + } + ); + + return { + favorize: repository._links.favorize ? () => favorize() : undefined, + unfavorize: repository._links.unfavorize ? () => unfavorize() : undefined, + isLoading: isLoadingFavorize || isLoadingUnfavorize, + error: favorizeError || unfavorizeError + }; +}; From 8f88e904df60a26f19d8fc6e5ebc4db39a15ce10 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 28 Jul 2021 13:22:01 +0200 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Sebastian Sdorra --- src/main/js/FavoriteRepositoryToggleIcon.tsx | 1 + src/main/js/favoriteRepository.ts | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/js/FavoriteRepositoryToggleIcon.tsx b/src/main/js/FavoriteRepositoryToggleIcon.tsx index c92af17..e3945f6 100644 --- a/src/main/js/FavoriteRepositoryToggleIcon.tsx +++ b/src/main/js/FavoriteRepositoryToggleIcon.tsx @@ -36,6 +36,7 @@ type Props = { const SpanWithPointer = styled.span` pointer-events: all; cursor: pointer; + margin-right: 0.25rem; z-index: 1; `; diff --git a/src/main/js/favoriteRepository.ts b/src/main/js/favoriteRepository.ts index 282aff6..2b528c4 100644 --- a/src/main/js/favoriteRepository.ts +++ b/src/main/js/favoriteRepository.ts @@ -30,9 +30,11 @@ export const useFavoriteRepository = (repository: Repository) => { const queryClient = useQueryClient(); const invalidateQueries = () => { - queryClient.invalidateQueries(["repository", repository.namespace, repository.name]); - queryClient.invalidateQueries(["landingpage", "myData"]); - return queryClient.invalidateQueries(["repositories"]); + return Promise.all([ + queryClient.invalidateQueries(["landingpage", "myData"]), + queryClient.invalidateQueries(["repository", repository.namespace, repository.name]), + queryClient.invalidateQueries(["repositories"]), + ]); }; const { mutate: favorize, isLoading: isLoadingFavorize, error: favorizeError } = useMutation( From f1b6c270f747e2015baf8f29761cd1cab6c1ba0e Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 28 Jul 2021 14:00:13 +0200 Subject: [PATCH 3/3] Apply review suggestions --- src/main/js/data/MyData.tsx | 22 ++++++----- src/main/js/data/{myData.ts => useMyData.ts} | 0 src/main/js/events/MyEvents.tsx | 2 +- .../js/events/{myEvents.ts => useMyEvents.ts} | 0 src/main/js/favoriteRepository.ts | 39 ++++++------------- src/main/js/tasks/MyTasks.tsx | 2 +- .../js/tasks/{myTasks.ts => useMyTasks.ts} | 0 7 files changed, 26 insertions(+), 39 deletions(-) rename src/main/js/data/{myData.ts => useMyData.ts} (100%) rename src/main/js/events/{myEvents.ts => useMyEvents.ts} (100%) rename src/main/js/tasks/{myTasks.ts => useMyTasks.ts} (100%) diff --git a/src/main/js/data/MyData.tsx b/src/main/js/data/MyData.tsx index 4c0c74f..6ced0e1 100644 --- a/src/main/js/data/MyData.tsx +++ b/src/main/js/data/MyData.tsx @@ -23,12 +23,12 @@ */ import React, { FC, ReactElement } from "react"; import { useTranslation } from "react-i18next"; -import { ErrorNotification, Loading } from "@scm-manager/ui-components"; +import { ErrorNotification, Loading, Notification } from "@scm-manager/ui-components"; import styled from "styled-components"; import { binder } from "@scm-manager/ui-extensions"; import CollapsibleContainer from "../CollapsibleContainer"; import { Link, Links } from "@scm-manager/ui-types"; -import { useMyData } from "./myData"; +import { useMyData } from "./useMyData"; const Headline = styled.h3` font-size: 1.25rem; @@ -75,13 +75,17 @@ const MyData: FC = ({ links }) => { separatedEntries={extension.separatedEntries} emptyMessage={extension.emptyMessage} > - - {dataForExtension?.map((dataEntry, key) => ( - <> - {extension.render(dataEntry, key)} {key + 1 !== dataForExtension.length ? : null} - - ))} - + {(dataForExtension?.length || 0) > 0 ? ( + + {dataForExtension?.map((dataEntry, key) => ( + <> + {extension.render(dataEntry, key)} {key + 1 !== dataForExtension.length ? : null} + + ))} + + ) : ( + {t("scm-landingpage-plugin.favoriteRepository.noData")} + )} ); } diff --git a/src/main/js/data/myData.ts b/src/main/js/data/useMyData.ts similarity index 100% rename from src/main/js/data/myData.ts rename to src/main/js/data/useMyData.ts diff --git a/src/main/js/events/MyEvents.tsx b/src/main/js/events/MyEvents.tsx index 03d2d25..1a97ca6 100644 --- a/src/main/js/events/MyEvents.tsx +++ b/src/main/js/events/MyEvents.tsx @@ -27,7 +27,7 @@ import CollapsibleContainer from "../CollapsibleContainer"; import { ErrorNotification, Loading } from "@scm-manager/ui-components"; import MyEvent from "./MyEvent"; import { Link, Links } from "@scm-manager/ui-types"; -import { useMyEvents } from "./myEvents"; +import { useMyEvents } from "./useMyEvents"; type Props = { links: Links; diff --git a/src/main/js/events/myEvents.ts b/src/main/js/events/useMyEvents.ts similarity index 100% rename from src/main/js/events/myEvents.ts rename to src/main/js/events/useMyEvents.ts diff --git a/src/main/js/favoriteRepository.ts b/src/main/js/favoriteRepository.ts index 2b528c4..d97e90b 100644 --- a/src/main/js/favoriteRepository.ts +++ b/src/main/js/favoriteRepository.ts @@ -31,37 +31,20 @@ export const useFavoriteRepository = (repository: Repository) => { const invalidateQueries = () => { return Promise.all([ - queryClient.invalidateQueries(["landingpage", "myData"]), - queryClient.invalidateQueries(["repository", repository.namespace, repository.name]), - queryClient.invalidateQueries(["repositories"]), - ]); + queryClient.invalidateQueries(["landingpage", "myData"]), + queryClient.invalidateQueries(["repository", repository.namespace, repository.name]), + queryClient.invalidateQueries(["repositories"]) + ]).then(() => undefined); }; - const { mutate: favorize, isLoading: isLoadingFavorize, error: favorizeError } = useMutation( - () => { - return apiClient.post((repository?._links?.favorize as Link).href, {}); - }, - { - onSuccess: invalidateQueries - } - ); - - const { mutate: unfavorize, isLoading: isLoadingUnfavorize, error: unfavorizeError } = useMutation< - unknown, - Error - >( - () => { - return apiClient.post((repository?._links?.unfavorize as Link).href, {}); - }, - { - onSuccess: invalidateQueries - } - ); + const { mutate, isLoading, error } = useMutation(link => apiClient.post(link.href, {}), { + onSuccess: invalidateQueries + }); return { - favorize: repository._links.favorize ? () => favorize() : undefined, - unfavorize: repository._links.unfavorize ? () => unfavorize() : undefined, - isLoading: isLoadingFavorize || isLoadingUnfavorize, - error: favorizeError || unfavorizeError + favorize: repository._links.favorize ? () => mutate(repository._links.favorize as Link) : undefined, + unfavorize: repository._links.unfavorize ? () => mutate(repository._links.unfavorize as Link) : undefined, + isLoading, + error }; }; diff --git a/src/main/js/tasks/MyTasks.tsx b/src/main/js/tasks/MyTasks.tsx index 4e845d6..a1ae01c 100644 --- a/src/main/js/tasks/MyTasks.tsx +++ b/src/main/js/tasks/MyTasks.tsx @@ -27,7 +27,7 @@ import { useTranslation } from "react-i18next"; import { ErrorNotification, Loading } from "@scm-manager/ui-components"; import MyTask from "./MyTask"; import { Link, Links } from "@scm-manager/ui-types"; -import { useMyTasks } from "./myTasks"; +import { useMyTasks } from "./useMyTasks"; type Props = { links: Links; diff --git a/src/main/js/tasks/myTasks.ts b/src/main/js/tasks/useMyTasks.ts similarity index 100% rename from src/main/js/tasks/myTasks.ts rename to src/main/js/tasks/useMyTasks.ts