Set descriptive document titles

Document titles represent the pages, for example in lists
with bookmarks. They are important for navigation and
orientation in websites. If the offer or the content of the
page is not labeled, orientation is impaired.

This changes the behavior for setting document titles.
The functionality has been removed from the Page and
Title components and is now represented by `useDocumentTitle`
hook to better describe the content of inividual pages.

Co-authored-by: Anna Vetcininova<anna.vetcininova@cloudogu.com>
This commit is contained in:
Florian Scholdei
2024-12-10 16:41:01 +01:00
parent 3ed457a891
commit 3c0ad46f07
74 changed files with 812 additions and 445 deletions

View File

@@ -114,12 +114,14 @@
"repositoryRole": {
"navLink": "Berechtigungsrollen",
"title": "Berechtigungsrollen",
"titleWithPage": "Berechtigungsrollen Seite {{page}} von {{total}}",
"errorTitle": "Fehler",
"errorSubtitle": "Unbekannter Berechtigungsrollen Fehler",
"detailsTitle": "Berechtigungsrolle",
"createSubtitle": "Berechtigungsrolle erstellen",
"editSubtitle": "Berechtigungsrolle bearbeiten",
"overview": {
"title": "Übersicht aller verfügbaren Berechtigungsrollen",
"subtitle": "Übersicht aller verfügbaren Berechtigungsrollen",
"noPermissionRoles": "Keine Berechtigungsrollen gefunden.",
"createButton": "Berechtigungsrolle erstellen"
},

View File

@@ -67,6 +67,7 @@
"loading": "Lade Daten ..."
},
"logout": {
"title": "Abmeldung",
"error": {
"title": "Abmeldung fehlgeschlagen",
"subtitle": "Während der Abmeldung ist ein Fehler aufgetreten."
@@ -135,6 +136,7 @@
"previous": "Zurück"
},
"profile": {
"subtitle": "Information",
"navigationLabel": "Profil",
"informationNavLink": "Information",
"changePasswordNavLink": "Passwort ändern",
@@ -263,6 +265,7 @@
"ariaLabel": "Globale Suche",
"placeholder": "Suche...",
"title": "Suche",
"titleWithPage": "Suche Seite {{page}} von {{total}}",
"subtitle": "{{type}} Ergebnisse",
"subtitleWithContext": "{{type}} Ergebnisse für in \"{{context}}\"",
"withQueryType": " mit {{queryType}}",

View File

@@ -16,6 +16,7 @@
"createButton": "Gruppe erstellen"
},
"singleGroup": {
"settingsTitle": "Generelle Einstellungen",
"errorTitle": "Fehler",
"errorSubtitle": "Unbekannter Gruppen Fehler",
"menu": {

View File

@@ -34,6 +34,7 @@
"skipLfsHelpText": "Ist diese Option aktiviert, werden beim Import keine (potentiell vorhandenen) LFS-Dateien in den SCM-Manager geladen. Diese Option ist nur für Git Repositories relevant."
},
"repositoryRoot": {
"settingsTitle": "Generelle Einstellungen",
"errorTitle": "Fehler",
"errorSubtitle": "Unbekannter Repository Fehler",
"menu": {
@@ -56,6 +57,9 @@
},
"overview": {
"title": "Repositories",
"titleWithPage": "Repositories Seite {{page}} von {{total}}",
"titleWithNamespace": "Repositories in {{namespace}}",
"titleWithNamespaceAndPage": "Repositories Seite {{page}} von {{total}} in {{namespace}}",
"subtitle": "Übersicht aller verfügbaren Repositories",
"noRepositories": "Keine Repositories gefunden.",
"invalidNamespace": "Keine Repositories gefunden. Möglicherweise existiert der ausgewählte Namespace nicht.",
@@ -172,7 +176,8 @@
"cancel": "Nein",
"submit": "Ja"
}
}
},
"branchWithNamespaceName": "Branch {{branch}} in {{namespace}}/{{name}}"
},
"compare": {
"title": "Vergleiche Änderungen",
@@ -183,6 +188,16 @@
"target": "Target",
"with": "Vergleiche Änderungen mit...",
"filter": "Auswahl filtern...",
"typeTitle": {
"b": "Branch",
"t": "Tag",
"r": "Revision"
},
"type": {
"b": "Branch",
"t": "Tag",
"r": "Revision"
},
"emptyResult": "Es wurden keine dem Filter entsprechenden Ergebnisse gefunden.",
"tabs": {
"b": "Branches",
@@ -197,7 +212,8 @@
"tabs": {
"diff": "Diff",
"commits": "Commits"
}
},
"compareSourceAndTargetWithNamespaceName": "Vergleiche {{sourceType}} {{source}} mit {{targetType}} {{target}} in {{namespace}}/{{name}}"
},
"tags": {
"overview": {
@@ -249,7 +265,8 @@
"cancel": "Nein",
"submit": "Ja"
}
}
},
"tagWithNamespaceName": "Tag {{tag}} in {{namespace}}/{{name}}"
},
"code": {
"sources": "Sources",
@@ -258,6 +275,11 @@
"noBranches": "Keine Sources für das Repository gefunden."
},
"changesets": {
"commitsWithNamespaceName": "Commits in {{namespace}}/{{name}}",
"commitsWithPageAndNamespaceName": "Commits Seite {{page}} von {{total}} in {{namespace}}/{{name}}",
"commitsWithPageRevisionAndNamespaceName": "Commits Seite {{page}} von {{total}} auf {{revision}} in {{namespace}}/{{name}}",
"commitsWithRevisionAndNamespaceName": "Commits auf {{revision}} in {{namespace}}/{{name}}",
"idWithNamespaceName": "{{id}} in {{namespace}}/{{name}}",
"errorTitle": "Fehler",
"errorSubtitle": "Changesets konnten nicht abgerufen werden",
"noChangesets": "Keine Changesets in diesem Branch gefunden. Die Commits könnten gelöscht worden sein.",
@@ -420,7 +442,9 @@
"notBound": "Keine Erweiterung angebunden."
},
"loadMore": "Laden",
"moreFilesAvailable": "Es werden nur die ersten {{count}} Dateien angezeigt. Es sind weitere Dateien vorhanden."
"moreFilesAvailable": "Es werden nur die ersten {{count}} Dateien angezeigt. Es sind weitere Dateien vorhanden.",
"pathWithRevisionAndNamespaceName": "{{path}} von {{revision}} in {{namespace}}/{{name}}",
"sourcesWithRevisionAndNamespaceName": "Sources von {{revision}} in {{namespace}}/{{name}}"
},
"permission": {
"title": "Berechtigungen",
@@ -547,6 +571,13 @@
"started": "Die Reindizierung wurde erfolgreich gestartet. Dies ist eine asynchrone Operation und kann einige Zeit in Anspruch nehmen."
},
"diff": {
"changes": {
"add": "added",
"delete": "deleted",
"modify": "modified",
"rename": "renamed",
"copy": "copied"
},
"jumpToSource": "Zur Quelldatei springen",
"jumpToTarget": "Zur vorherigen Version der Datei springen",
"sideBySide": "Zur zweispaltigen Ansicht wechseln",
@@ -587,7 +618,8 @@
"notifications": {
"queryToShort": "Tippe mindestens 2 Zeichen ein, um die Suche zu starten",
"emptyResult": "Es wurden keine Ergebnisse für <0>{{query}}</0> gefunden"
}
},
"searchWithRevisionAndNamespaceName": "Suche auf {{revision}} in {{namespace}}/{{name}}"
},
"shortcuts": {
"info": "Wechsel zur Repository-Info",

View File

@@ -31,6 +31,7 @@
"createButton": "Benutzer erstellen"
},
"singleUser": {
"settingsTitle": "Generelle Einstellungen",
"errorTitle": "Fehler",
"errorSubtitle": "Unbekannter Benutzer Fehler",
"menu": {

View File

@@ -114,12 +114,14 @@
"repositoryRole": {
"navLink": "Permission Roles",
"title": "Permission Roles",
"titleWithPage": "Permission Roles page {{page}} of {{total}}",
"errorTitle": "Error",
"errorSubtitle": "Unknown Permission Role Error",
"detailsTitle": "Permission Role",
"createSubtitle": "Create Permission Role",
"editSubtitle": "Edit Permission Role",
"overview": {
"title": "Overview of all Permission Roles",
"subtitle": "Overview of all Permission Roles",
"noPermissionRoles": "No Permission Roles found.",
"createButton": "Create Permission Role"
},

View File

@@ -68,6 +68,7 @@
"error": "Error"
},
"logout": {
"title": "Logout",
"error": {
"title": "Logout Failed",
"subtitle": "Something went wrong during logout"
@@ -136,6 +137,7 @@
"previous": "Previous"
},
"profile": {
"subtitle": "Information",
"navigationLabel": "Profile",
"informationNavLink": "Information",
"changePasswordNavLink": "Change Password",
@@ -264,6 +266,7 @@
"ariaLabel": "Global search",
"placeholder": "Search...",
"title": "Search",
"titleWithPage": "Search page {{page}} of {{total}}",
"subtitle": "{{type}} results",
"subtitleWithContext": "{{type}} results in \"{{context}}\"",
"withQueryType": " with {{queryType}}",

View File

@@ -16,6 +16,7 @@
"createButton": "Create Group"
},
"singleGroup": {
"settingsTitle": "General Settings",
"errorTitle": "Error",
"errorSubtitle": "Unknown group error",
"menu": {

View File

@@ -34,6 +34,7 @@
"skipLfsHelpText": "Check this if (potentially available) LFS files shall not be loaded into SCM-Manager during the import. This option is relevant only for git repositories."
},
"repositoryRoot": {
"settingsTitle": "General Settings",
"errorTitle": "Error",
"errorSubtitle": "Unknown repository error",
"menu": {
@@ -56,6 +57,9 @@
},
"overview": {
"title": "Repositories",
"titleWithPage": "Repositories page {{page}} of {{total}}",
"titleWithNamespace": "Repositories in {{namespace}}",
"titleWithNamespaceAndPage": "Repositories page {{page}} of {{total}} in {{namespace}}",
"subtitle": "Overview of available repositories",
"noRepositories": "No repositories found.",
"invalidNamespace": "No repositories found. It's likely that the selected namespace does not exist.",
@@ -172,7 +176,8 @@
"cancel": "No",
"submit": "Yes"
}
}
},
"branchWithNamespaceName": "Branch {{branch}} in {{namespace}}/{{name}}"
},
"compare": {
"title": "Compare Changes",
@@ -183,6 +188,16 @@
"target": "Target",
"with": "Compare changes with...",
"filter": "Filter selection...",
"typeTitle": {
"b": "Branch",
"t": "Tag",
"r": "Revision"
},
"type": {
"b": "branch",
"t": "tag",
"r": "revision"
},
"emptyResult": "No results matching the filter were found.",
"tabs": {
"b": "Branches",
@@ -197,7 +212,8 @@
"tabs": {
"diff": "Diff",
"commits": "Commits"
}
},
"compareSourceAndTargetWithNamespaceName": "Compare {{sourceType}} {{source}} with {{targetType}} {{target}} in {{namespace}}/{{name}}"
},
"tags": {
"overview": {
@@ -249,7 +265,8 @@
"cancel": "No",
"submit": "Yes"
}
}
},
"tagWithNamespaceName": "Tag {{tag}} in {{namespace}}/{{name}}"
},
"code": {
"sources": "Sources",
@@ -258,6 +275,11 @@
"noBranches": "No sources found for this repository."
},
"changesets": {
"commitsWithNamespaceName": "Commits in {{namespace}}/{{name}}",
"commitsWithPageAndNamespaceName": "Commits page {{page}} of {{total}} in {{namespace}}/{{name}}",
"commitsWithPageRevisionAndNamespaceName": "Commits page {{page}} of {{total}} on {{revision}} in {{namespace}}/{{name}}",
"commitsWithRevisionAndNamespaceName": "Commits on {{revision}} in {{namespace}}/{{name}}",
"idWithNamespaceName": "{{id}} in {{namespace}}/{{name}}",
"errorTitle": "Error",
"errorSubtitle": "Could not fetch changesets",
"noChangesets": "No changesets found for this branch. The commits could have been removed.",
@@ -420,7 +442,9 @@
"notBound": "No extension bound."
},
"loadMore": "Load",
"moreFilesAvailable": "These are just the first {{count}} files. There are more files available."
"moreFilesAvailable": "These are just the first {{count}} files. There are more files available.",
"pathWithRevisionAndNamespaceName": "{{path}} on {{revision}} in {{namespace}}/{{name}}",
"sourcesWithRevisionAndNamespaceName": "Sources on {{revision}} in {{namespace}}/{{name}}"
},
"permission": {
"title": "Permissions",
@@ -523,6 +547,17 @@
"cancel": "No"
}
},
"archive": {
"tooltip": "Read only. The archive cannot be changed."
},
"exporting": {
"tooltip": "Read only. The repository is currently being exported."
},
"healthCheckFailure": {
"tooltip": "This repository has health check failures. Click to get details.",
"title": "Health Check Failures",
"close": "Close"
},
"runHealthCheck": {
"button": "Run Health Checks",
"subtitle": "Health Checks",
@@ -535,17 +570,6 @@
"description": "Deletes all existing search indices for this repository and recreates them from scratch. This may take a while.",
"started": "Reindexing has been started successfully. This is an asynchronous operation and may take a while."
},
"archive": {
"tooltip": "Read only. The archive cannot be changed."
},
"exporting": {
"tooltip": "Read only. The repository is currently being exported."
},
"healthCheckFailure": {
"tooltip": "This repository has health check failures. Click to get details.",
"title": "Health Check Failures",
"close": "Close"
},
"diff": {
"changes": {
"add": "added",
@@ -594,7 +618,8 @@
"notifications": {
"queryToShort": "Type at least two characters to start the search",
"emptyResult": "Nothing found for query <0>{{query}}</0>"
}
},
"searchWithRevisionAndNamespaceName": "Search on {{revision}} in {{namespace}}/{{name}}"
},
"shortcuts": {
"info": "Switch to repository info",

View File

@@ -31,6 +31,7 @@
"createButton": "Create User"
},
"singleUser": {
"settingsTitle": "General Settings",
"errorTitle": "Error",
"errorSubtitle": "Unknown user error",
"menu": {

View File

@@ -19,6 +19,7 @@ import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { devices, ErrorNotification, Image, Loading, Subtitle, Title } from "@scm-manager/ui-components";
import { useUpdateInfo, useVersion } from "@scm-manager/ui-api";
import { useDocumentTitle } from "@scm-manager/ui-core";
const BoxShadowBox = styled.div`
box-shadow: 0 2px 3px rgba(40, 177, 232, 0.1), 0 0 0 2px rgba(40, 177, 232, 0.2);
@@ -41,6 +42,7 @@ const MobileWrapped = styled.article`
const AdminDetails: FC = () => {
const [t] = useTranslation("admin");
useDocumentTitle(t("admin.info.title"));
const version = useVersion();
const { data: updateInfo, error, isLoading } = useUpdateInfo();
@@ -64,7 +66,7 @@ const AdminDetails: FC = () => {
<h3 className="has-text-weight-medium">{t("admin.info.newRelease.title")}</h3>
<p>
{t("admin.info.newRelease.description", {
version: updateInfo?.latestVersion
version: updateInfo?.latestVersion,
})}
</p>
<a className="button is-warning is-pulled-right" target="_blank" href={updateInfo?.link} rel="noreferrer">

View File

@@ -20,6 +20,7 @@ import { Link } from "@scm-manager/ui-types";
import { ErrorNotification, Loading, Title } from "@scm-manager/ui-components";
import ConfigForm from "../components/form/ConfigForm";
import { useConfig, useIndexLinks, useNamespaceStrategies, useUpdateConfig } from "@scm-manager/ui-api";
import { useDocumentTitle } from "@scm-manager/ui-core";
const GlobalConfig: FC = () => {
const indexLinks = useIndexLinks();
@@ -31,6 +32,7 @@ const GlobalConfig: FC = () => {
isLoading: isLoadingNamespaceStrategies,
} = useNamespaceStrategies();
const [t] = useTranslation("config");
useDocumentTitle(t("config.title"));
const error = configLoadingError || namespaceStrategiesLoadingError || updateError || undefined;
const isLoading = isLoadingNamespaceStrategies || isLoadingConfig;
const canUpdateConfig = !!(config && (config._links.update as Link).href);

View File

@@ -36,6 +36,7 @@ import CloudoguPlatformBanner from "../components/CloudoguPlatformBanner";
import PluginCenterAuthInfo from "../components/PluginCenterAuthInfo";
import styled from "styled-components";
import { Button } from "@scm-manager/ui-buttons";
import { useDocumentTitle } from "@scm-manager/ui-core";
export enum PluginAction {
INSTALL = "install",
@@ -70,6 +71,7 @@ const StickyHeader = styled.div`
const PluginsOverview: FC<Props> = ({ installed }) => {
const [t] = useTranslation("admin");
useDocumentTitle(installed ? t("plugins.installedSubtitle") : t("plugins.availableSubtitle"));
const {
data: availablePlugins,
isLoading: isLoadingAvailablePlugins,

View File

@@ -14,49 +14,54 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
import { RepositoryRole } from "@scm-manager/ui-types";
import { Button, Level } from "@scm-manager/ui-components";
import { Level, LinkButton, Title, useDocumentTitle } from "@scm-manager/ui-core";
import PermissionRoleDetailsTable from "./PermissionRoleDetailsTable";
type Props = WithTranslation & {
type Props = {
role: RepositoryRole;
url: string;
};
class PermissionRoleDetails extends React.Component<Props> {
renderEditButton() {
const { t, url } = this.props;
if (!!this.props.role._links.update) {
const PermissionRoleDetails: FC<Props> = ({ role, url }) => {
const [t] = useTranslation("admin");
useDocumentTitle(t("repositoryRole.detailsTitle"));
const renderEditButton = () => {
if (!!role._links.update) {
return (
<>
<hr />
<Level right={<Button label={t("repositoryRole.editButton")} link={`${url}/edit`} color="primary" />} />
<Level
right={
<LinkButton to={`${url}/edit`} variant="primary" color="primary">
{t("repositoryRole.editButton")}
</LinkButton>
}
/>
</>
);
}
return null;
}
};
render() {
const { role } = this.props;
return (
<>
<Title>{t("repositoryRole.detailsTitle")}</Title>
<PermissionRoleDetailsTable role={role} />
{renderEditButton()}
<ExtensionPoint<extensionPoints.RepositoryRoleDetailsInformation>
name="repositoryRole.role-details.information"
renderAll={true}
props={{
role,
}}
/>
</>
);
};
return (
<>
<PermissionRoleDetailsTable role={role} />
{this.renderEditButton()}
<ExtensionPoint<extensionPoints.RepositoryRoleDetailsInformation>
name="repositoryRole.role-details.information"
renderAll={true}
props={{
role
}}
/>
</>
);
}
}
export default withTranslation("admin")(PermissionRoleDetails);
export default PermissionRoleDetails;

View File

@@ -20,9 +20,11 @@ import { ErrorNotification, Loading, Subtitle, Title } from "@scm-manager/ui-com
import RepositoryRoleForm from "./RepositoryRoleForm";
import { useCreateRepositoryRole } from "@scm-manager/ui-api";
import { Redirect } from "react-router-dom";
import { useDocumentTitle } from "@scm-manager/ui-core";
const CreateRepositoryRole: FC = () => {
const [t] = useTranslation("admin");
useDocumentTitle(t("repositoryRole.createSubtitle"));
const { error, isLoading: loading, create, repositoryRole: created } = useCreateRepositoryRole();
if (created) {

View File

@@ -17,11 +17,12 @@
import React, { FC } from "react";
import RepositoryRoleForm from "./RepositoryRoleForm";
import { useTranslation } from "react-i18next";
import { ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-components";
import { ErrorNotification, Loading, Subtitle, Title } from "@scm-manager/ui-components";
import { RepositoryRole } from "@scm-manager/ui-types";
import DeleteRepositoryRole from "./DeleteRepositoryRole";
import { useUpdateRepositoryRole } from "@scm-manager/ui-api";
import { Redirect } from "react-router-dom";
import { useDocumentTitle } from "@scm-manager/ui-core";
type Props = {
role: RepositoryRole;
@@ -29,6 +30,7 @@ type Props = {
const EditRepositoryRole: FC<Props> = ({ role }) => {
const [t] = useTranslation("admin");
useDocumentTitle(t("repositoryRole.editSubtitle"));
const { isUpdated, update, error, isLoading: loading } = useUpdateRepositoryRole();
if (isUpdated) {
@@ -43,6 +45,7 @@ const EditRepositoryRole: FC<Props> = ({ role }) => {
return (
<>
<Title title={t("repositoryRole.detailsTitle")} />
<Subtitle subtitle={t("repositoryRole.editSubtitle")} />
<RepositoryRoleForm role={role} submitForm={update} />
<DeleteRepositoryRole role={role} />

View File

@@ -30,6 +30,7 @@ import {
} from "@scm-manager/ui-components";
import PermissionRoleTable from "../components/PermissionRoleTable";
import { useRepositoryRoles } from "@scm-manager/ui-api";
import { useDocumentTitle } from "@scm-manager/ui-core";
type RepositoryRolesPageProps = {
data?: RepositoryRoleCollection;
@@ -62,6 +63,11 @@ const RepositoryRoles: FC<Props> = ({ baseUrl }) => {
const page = urls.getPageFromMatch({ params });
const { isLoading: loading, error, data } = useRepositoryRoles({ page: page - 1 });
const [t] = useTranslation("admin");
useDocumentTitle(
data?.pageTotal && data.pageTotal > 1 && page
? t("repositoryRole.titleWithPage", { page, total: data.pageTotal })
: t("repositoryRole.title")
);
const canAddRoles = !!data?._links.create;
if (error) {
@@ -79,7 +85,7 @@ const RepositoryRoles: FC<Props> = ({ baseUrl }) => {
return (
<>
<Title title={t("repositoryRole.title")} />
<Subtitle subtitle={t("repositoryRole.overview.title")} />
<Subtitle subtitle={t("repositoryRole.overview.subtitle")} />
<RepositoryRolesPage data={data} page={page} baseUrl={baseUrl} />
{canAddRoles ? (
<CreateButton label={t("repositoryRole.overview.createButton")} link={`${baseUrl}/create`} />

View File

@@ -44,12 +44,11 @@ const SingleRepositoryRole: FC = () => {
const extensionProps = {
role,
url: escapedUrl
url: escapedUrl,
};
return (
<>
<Title title={t("repositoryRole.title")} />
<Route path={`${escapedUrl}/info`}>
<PermissionRoleDetail role={role} url={url} />
</Route>

View File

@@ -15,13 +15,14 @@
*/
import React, { FC } from "react";
import InfoBox from "./InfoBox";
import LoginForm from "./LoginForm";
import { Image, Loading } from "@scm-manager/ui-components";
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { useLoginInfo } from "@scm-manager/ui-api";
import { useTranslation } from "react-i18next";
import { Image, Loading } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
import InfoBox from "./InfoBox";
import LoginForm from "./LoginForm";
const TopMarginBox = styled.div`
margin-top: 5rem;
@@ -57,6 +58,7 @@ type Props = {
const LoginInfo: FC<Props> = (props) => {
const { isLoading: isLoadingLoginInfo, data: info } = useLoginInfo();
const [t] = useTranslation("commons");
useDocumentTitle(t("login.title"));
if (isLoadingLoginInfo) {
return <Loading />;

View File

@@ -15,13 +15,20 @@
*/
import React, { FC } from "react";
import { ButtonGroup, Checkbox, SubmitButton, Subtitle } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import { useForm } from "react-hook-form";
import { Me } from "@scm-manager/ui-types";
import { useDocumentTitle } from "@scm-manager/ui-core";
import { ButtonGroup, Checkbox, SubmitButton, Subtitle } from "@scm-manager/ui-components";
import { AccessibilityConfig, useAccessibilityConfig } from "../accessibilityConfig";
const Accessibility: FC = () => {
type Props = {
me: Me;
};
const Accessibility: FC<Props> = ({ me }) => {
const [t] = useTranslation("commons");
useDocumentTitle(t("profile.accessibility.subtitle"), me.displayName);
const { value: accessibilityConfig, setValue: setAccessibilityConfig, isLoading } = useAccessibilityConfig();
const {
register,

View File

@@ -15,6 +15,7 @@
*/
import React, { FC, FormEvent, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import {
ErrorNotification,
InputField,
@@ -22,9 +23,9 @@ import {
Notification,
PasswordConfirmation,
SubmitButton,
Subtitle
Subtitle,
} from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import { useDocumentTitle } from "@scm-manager/ui-core";
import { Me } from "@scm-manager/ui-types";
import { useChangeUserPassword } from "@scm-manager/ui-api";
@@ -34,6 +35,7 @@ type Props = {
const ChangeUserPassword: FC<Props> = ({ me }) => {
const [t] = useTranslation("commons");
useDocumentTitle(t("password.subtitle"), me.displayName);
const { isLoading, error, passwordChanged, changePassword, reset } = useChangeUserPassword(me);
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");

View File

@@ -18,6 +18,7 @@ import React from "react";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Notification, Page } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
type LocationState = {
code: string;
@@ -26,6 +27,7 @@ type LocationState = {
const ExternalError = () => {
const { code } = useParams<LocationState>();
const [t] = useTranslation(["commons", "plugins"]);
useDocumentTitle(`${t("app.error.title")}: ${t(`plugins:errors.${code}.displayName`)}`);
return (
<Page title={t("app.error.title")} subtitle={t(`plugins:errors.${code}.displayName`)}>

View File

@@ -16,7 +16,8 @@
import React, { FC, useState } from "react";
import App from "./App";
import { ErrorBoundary, Header, Loading } from "@scm-manager/ui-components";
import { ErrorBoundary, Header } from "@scm-manager/ui-components";
import { Loading } from "@scm-manager/ui-core";
import PluginLoader from "./PluginLoader";
import ScrollToTop from "./ScrollToTop";
import IndexErrorPage from "./IndexErrorPage";

View File

@@ -15,15 +15,17 @@
*/
import React, { FC, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { ErrorPage, Loading } from "@scm-manager/ui-components";
import { Redirect } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useLogout } from "@scm-manager/ui-api";
import { ErrorPage, Loading } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
const Logout: FC = () => {
const { error, logout } = useLogout();
const [t] = useTranslation("commons");
useDocumentTitle(t("logout.title"));
useEffect(() => {
if (logout) {
logout();

View File

@@ -75,10 +75,10 @@ const Profile: FC = () => {
<ProfileInfo me={me} />
</Route>
<Route path={`${url}/settings/theme`} exact>
<Theme />
<Theme me={me} />
</Route>
<Route path={`${url}/settings/accessibility`} exact>
<Accessibility />
<Accessibility me={me} />
</Route>
{mayChangePassword && (
<Route path={`${url}/settings/password`}>

View File

@@ -22,8 +22,9 @@ import {
AvatarWrapper,
createAttributesForTesting,
InfoTable,
MailLink
MailLink,
} from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
type Props = {
me: Me;
@@ -31,6 +32,7 @@ type Props = {
const ProfileInfo: FC<Props> = ({ me }) => {
const [t] = useTranslation("commons");
useDocumentTitle(t("profile.subtitle"), me.displayName);
const renderGroups = () => {
let groups = null;
if (me.groups.length > 0) {
@@ -39,7 +41,7 @@ const ProfileInfo: FC<Props> = ({ me }) => {
<th>{t("profile.groups")}</th>
<td className="p-0">
<ul>
{me.groups.map(group => {
{me.groups.map((group) => {
return <li>{group}</li>;
})}
</ul>

View File

@@ -20,6 +20,8 @@ import { useForm } from "react-hook-form";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { Me } from "@scm-manager/ui-types";
import { useDocumentTitle } from "@scm-manager/ui-core";
const LS_KEY = "scm.theme";
@@ -36,6 +38,10 @@ export const useThemeState = () => {
return { theme, setTheme, isLoading };
};
type Props = {
me: Me;
};
type ThemeForm = {
theme: string;
};
@@ -47,21 +53,22 @@ const RadioColumn = styled.div`
width: 2rem;
`;
const Theme: FC = () => {
const Theme: FC<Props> = ({ me }) => {
const { theme, setTheme, isLoading } = useThemeState();
const {
register,
setValue,
handleSubmit,
formState: { isDirty },
watch
watch,
} = useForm<ThemeForm>({
mode: "onChange",
defaultValues: {
theme
}
theme,
},
});
const [t] = useTranslation("commons");
useDocumentTitle(t("profile.theme.subtitle"), me.displayName);
const onSubmit = (values: ThemeForm) => {
setTheme(values.theme);
@@ -71,7 +78,7 @@ const Theme: FC = () => {
<>
<Subtitle>{t("profile.theme.subtitle")}</Subtitle>
<form className="is-flex is-flex-direction-column" onSubmit={handleSubmit(onSubmit)}>
{themes.map(theme => {
{themes.map((theme) => {
const a11yId = createA11yId("theme");
return (
<div

View File

@@ -14,63 +14,23 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { Group } from "@scm-manager/ui-types";
import { useDocumentTitle } from "@scm-manager/ui-core";
import { Checkbox, DateFromNow, InfoTable } from "@scm-manager/ui-components";
import GroupMember from "./GroupMember";
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
type Props = WithTranslation & {
type Props = {
group: Group;
};
class Details extends React.Component<Props> {
render() {
const { group, t } = this.props;
return (
<InfoTable className="content">
<tbody>
<tr>
<th>{t("group.name")}</th>
<td>{group.name}</td>
</tr>
<tr>
<th>{t("group.description")}</th>
<td>{group.description}</td>
</tr>
<tr>
<th>{t("group.external")}</th>
<td>
<Checkbox checked={group.external} readOnly={true} />
</td>
</tr>
<tr>
<th>{t("group.type")}</th>
<td>{group.type}</td>
</tr>
<tr>
<th>{t("group.creationDate")}</th>
<td>
<DateFromNow date={group.creationDate} />
</td>
</tr>
<tr>
<th>{t("group.lastModified")}</th>
<td>
<DateFromNow date={group.lastModified} />
</td>
</tr>
{this.renderMembers()}
<ExtensionPoint<extensionPoints.GroupInformationTableBottom> name="group.information.table.bottom" props={{group}} renderAll={true} />
</tbody>
</InfoTable>
);
}
renderMembers() {
const { group, t } = this.props;
const Details: FC<Props> = ({ group }) => {
const [t] = useTranslation("groups");
useDocumentTitle(t("singleGroup.menu.informationNavLink"), group.name);
const renderMembers = () => {
let member = null;
if (group.members.length > 0) {
member = (
@@ -87,7 +47,50 @@ class Details extends React.Component<Props> {
);
}
return member;
}
}
};
export default withTranslation("groups")(Details);
return (
<InfoTable className="content">
<tbody>
<tr>
<th>{t("group.name")}</th>
<td>{group.name}</td>
</tr>
<tr>
<th>{t("group.description")}</th>
<td>{group.description}</td>
</tr>
<tr>
<th>{t("group.external")}</th>
<td>
<Checkbox checked={group.external} readOnly={true} />
</td>
</tr>
<tr>
<th>{t("group.type")}</th>
<td>{group.type}</td>
</tr>
<tr>
<th>{t("group.creationDate")}</th>
<td>
<DateFromNow date={group.creationDate} />
</td>
</tr>
<tr>
<th>{t("group.lastModified")}</th>
<td>
<DateFromNow date={group.lastModified} />
</td>
</tr>
{renderMembers()}
<ExtensionPoint<extensionPoints.GroupInformationTableBottom>
name="group.information.table.bottom"
props={{ group }}
renderAll={true}
/>
</tbody>
</InfoTable>
);
};
export default Details;

View File

@@ -18,11 +18,13 @@ import React, { FC } from "react";
import { Redirect, useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useCreateGroup, urls } from "@scm-manager/ui-api";
import { useDocumentTitle } from "@scm-manager/ui-core";
import { Page } from "@scm-manager/ui-components";
import GroupForm from "../components/GroupForm";
const CreateGroup: FC = () => {
const [t] = useTranslation("groups");
useDocumentTitle(t("addGroup.title"));
const { isLoading, create, error, group } = useCreateGroup();
const location = useLocation();

View File

@@ -15,18 +15,22 @@
*/
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { Group } from "@scm-manager/ui-types";
import { useUpdateGroup, useUserSuggestions } from "@scm-manager/ui-api";
import { useUpdateGroup } from "@scm-manager/ui-api";
import { ErrorNotification } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
import UpdateNotification from "../../components/UpdateNotification";
import GroupForm from "../components/GroupForm";
import DeleteGroup from "./DeleteGroup";
import UpdateNotification from "../../components/UpdateNotification";
type Props = {
group: Group;
};
const EditGroup: FC<Props> = ({ group }) => {
const [t] = useTranslation("groups");
useDocumentTitle(t("singleGroup.settingsTitle"), group.name);
const { error, isLoading, update, isUpdated } = useUpdateGroup();
return (

View File

@@ -17,8 +17,9 @@
import React, { FC } from "react";
import { Redirect, useLocation, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useGroups } from "@scm-manager/ui-api";
import { Group, GroupCollection } from "@scm-manager/ui-types";
import { useGroups } from "@scm-manager/ui-api";
import { useDocumentTitle } from "@scm-manager/ui-core";
import {
CreateButton,
LinkPaginator,
@@ -59,6 +60,7 @@ const Groups: FC = () => {
const page = urls.getPageFromMatch({ params });
const { isLoading, error, data } = useGroups({ search, page: page - 1 });
const [t] = useTranslation("groups");
useDocumentTitle(t("groups.title"));
const groups = data?._embedded?.groups;
const canCreateGroups = !!data?._links.create;
if (data && data.pageTotal < page && page > 1) {

View File

@@ -14,24 +14,30 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import { Group } from "@scm-manager/ui-types";
import React, { FC } from "react";
import SetPermissions from "./SetPermissions";
import { useTranslation } from "react-i18next";
import { Group } from "@scm-manager/ui-types";
import { useGroupPermissions, useSetGroupPermissions } from "@scm-manager/ui-api";
import { useDocumentTitle } from "@scm-manager/ui-core";
import SetPermissions from "./SetPermissions";
type Props = {
group: Group;
};
const SetGroupPermissions: FC<Props> = ({ group }) => {
const { data: selectedPermissions, isLoading: loadingPermissions, error: permissionsLoadError } = useGroupPermissions(
group
);
const [t] = useTranslation("groups");
useDocumentTitle(t("singleGroup.menu.setPermissionsNavLink"), group.name);
const {
data: selectedPermissions,
isLoading: loadingPermissions,
error: permissionsLoadError,
} = useGroupPermissions(group);
const {
isLoading: isUpdatingPermissions,
isUpdated: permissionsUpdated,
setPermissions,
error: permissionsUpdateError
error: permissionsUpdateError,
} = useSetGroupPermissions(group, selectedPermissions);
return (
<SetPermissions

View File

@@ -16,22 +16,28 @@
import { User } from "@scm-manager/ui-types";
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import SetPermissions from "./SetPermissions";
import { useSetUserPermissions, useUserPermissions } from "@scm-manager/ui-api";
import { useDocumentTitle } from "@scm-manager/ui-core";
type Props = {
user: User;
};
const SetUserPermissions: FC<Props> = ({ user }) => {
const { data: selectedPermissions, isLoading: loadingPermissions, error: permissionsLoadError } = useUserPermissions(
user
);
const [t] = useTranslation("users");
useDocumentTitle(t("singleUser.menu.setPermissionsNavLink"), user.displayName);
const {
data: selectedPermissions,
isLoading: loadingPermissions,
error: permissionsLoadError,
} = useUserPermissions(user);
const {
isLoading: isUpdatingPermissions,
isUpdated: permissionsUpdated,
setPermissions,
error: permissionsUpdateError
error: permissionsUpdateError,
} = useSetUserPermissions(user, selectedPermissions);
return (
<SetPermissions

View File

@@ -18,7 +18,8 @@ import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { Branch, Repository } from "@scm-manager/ui-types";
import {SmallLoadingSpinner, Subtitle, useGeneratedId} from "@scm-manager/ui-components";
import { SmallLoadingSpinner, Subtitle, useGeneratedId } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
import BranchButtonGroup from "./BranchButtonGroup";
import DefaultBranchTag from "./DefaultBranchTag";
import AheadBehindTag from "./AheadBehindTag";
@@ -33,6 +34,13 @@ type Props = {
const BranchDetail: FC<Props> = ({ repository, branch }) => {
const [t] = useTranslation("repos");
useDocumentTitle(
t("branch.branchWithNamespaceName", {
branch: branch.name,
namespace: repository.namespace,
name: repository.name,
})
);
const { data, isLoading } = useBranchDetails(repository, branch);
const labelId = useGeneratedId();
let aheadBehind;
@@ -70,7 +78,10 @@ const BranchDetail: FC<Props> = ({ repository, branch }) => {
<BranchButtonGroup repository={repository} branch={branch} />
</div>
</div>
<span id={labelId} className="is-size-7 has-text-secondary">{t("branch.aheadBehind.label")}</span>{aheadBehind}
<span id={labelId} className="is-size-7 has-text-secondary">
{t("branch.aheadBehind.label")}
</span>
{aheadBehind}
</>
);
};

View File

@@ -14,7 +14,7 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import React from "react";
import React, { FC } from "react";
import BranchDetail from "./BranchDetail";
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
import { Branch, Repository } from "@scm-manager/ui-types";
@@ -25,27 +25,24 @@ type Props = {
branch: Branch;
};
class BranchView extends React.Component<Props> {
render() {
const { repository, branch } = this.props;
return (
<>
<BranchDetail repository={repository} branch={branch} />
<hr />
<div className="content">
<ExtensionPoint<extensionPoints.ReposBranchDetailsInformation>
name="repos.branch-details.information"
renderAll={true}
props={{
repository,
branch
}}
/>
</div>
<BranchDangerZone repository={repository} branch={branch} />
</>
);
}
}
const BranchView: FC<Props> = ({ repository, branch }) => {
return (
<>
<BranchDetail repository={repository} branch={branch} />
<hr />
<div className="content">
<ExtensionPoint<extensionPoints.ReposBranchDetailsInformation>
name="repos.branch-details.information"
renderAll={true}
props={{
repository,
branch,
}}
/>
</div>
<BranchDangerZone repository={repository} branch={branch} />
</>
);
};
export default BranchView;

View File

@@ -15,8 +15,10 @@
*/
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { Repository } from "@scm-manager/ui-types";
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
import { useDocumentTitleForRepository } from "@scm-manager/ui-core";
import { useBranches } from "@scm-manager/ui-api";
import BranchTableWrapper from "./BranchTableWrapper";
@@ -27,6 +29,8 @@ type Props = {
const BranchesOverview: FC<Props> = ({ repository, baseUrl }) => {
const { isLoading, error, data } = useBranches(repository);
const [t] = useTranslation("repos");
useDocumentTitleForRepository(repository, t("branches.overview.title"));
if (error) {
return <ErrorNotification error={error} />;

View File

@@ -19,10 +19,11 @@ import { Redirect, useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next";
import queryString from "query-string";
import { Repository } from "@scm-manager/ui-types";
import { ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-components";
import BranchForm from "../components/BranchForm";
import { useBranches, useCreateBranch } from "@scm-manager/ui-api";
import { ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-components";
import { useDocumentTitleForRepository } from "@scm-manager/ui-core";
import { encodePart } from "../../sources/components/content/FileLink";
import BranchForm from "../components/BranchForm";
type Props = {
repository: Repository;
@@ -33,6 +34,7 @@ const CreateBranch: FC<Props> = ({ repository }) => {
const { isLoading: isLoadingList, error: errorList, data: branches } = useBranches(repository);
const location = useLocation();
const [t] = useTranslation("repos");
useDocumentTitleForRepository(repository, t("branches.create.title"));
const transmittedName = (url: string): string | undefined => {
const paramsName = queryString.parse(url).name;
@@ -48,7 +50,9 @@ const CreateBranch: FC<Props> = ({ repository }) => {
if (createdBranch) {
return (
<Redirect
to={`/repo/${repository.namespace}/${repository.name}/branch/${encodeURIComponent(encodePart(createdBranch.name))}/info`}
to={`/repo/${repository.namespace}/${repository.name}/branch/${encodeURIComponent(
encodePart(createdBranch.name)
)}/info`}
/>
);
}

View File

@@ -22,6 +22,7 @@ import styled from "styled-components";
import { Branch, Repository } from "@scm-manager/ui-types";
import { urls, usePaths } from "@scm-manager/ui-api";
import { createA11yId, ErrorNotification, FilterInput, Help, Icon, Loading } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
import CodeActionBar from "../components/CodeActionBar";
import FileSearchResults from "../components/FileSearchResults";
import { filepathSearch } from "../utils/filepathSearch";
@@ -60,7 +61,14 @@ const FileSearch: FC<Props> = ({ repository, baseUrl, branches, selectedBranch }
const query = urls.getQueryStringFromLocation(location) || "";
const prevSourcePath = urls.getPrevSourcePathFromLocation(location) || "";
const [t] = useTranslation("repos");
const [firstSelectedBranch, setBranchChanged] = useState<string | undefined>(selectedBranch);
useDocumentTitle(
t("fileSearch.searchWithRevisionAndNamespaceName", {
revision: decodeURIComponent(revision),
namespace: repository.namespace,
name: repository.name,
})
);
const [firstSelectedBranch] = useState<string | undefined>(selectedBranch);
useEffect(() => {
if (query.length > 1 && data) {

View File

@@ -20,6 +20,7 @@ import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { Repository } from "@scm-manager/ui-types";
import { devices, Icon } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
import CompareSelector from "./CompareSelector";
import { CompareBranchesParams } from "./CompareView";
@@ -59,12 +60,22 @@ const CompareSelectBar: FC<Props> = ({ repository, baseUrl }) => {
const history = useHistory();
const [source, setSource] = useState<CompareProps>({
type: params?.sourceType,
name: decodeURIComponent(params?.sourceName)
name: decodeURIComponent(params?.sourceName),
});
const [target, setTarget] = useState<CompareProps>({
type: params?.targetType,
name: decodeURIComponent(params?.targetName)
name: decodeURIComponent(params?.targetName),
});
useDocumentTitle(
t("compare.compareSourceAndTargetWithNamespaceName", {
sourceType: t(`compare.selector.type.${source.type}`),
source: source.name,
targetType: t(`compare.selector.type.${target.type}`),
target: target.name,
namespace: repository.namespace,
name: repository.name,
})
);
useEffect(() => {
const tabUriComponent = location.pathname.split("/")[9];

View File

@@ -85,17 +85,6 @@ const CompareSelector: FC<Props> = ({ onSelect, selected, label, repository }) =
};
});
const getActionTypeName = (type: CompareTypes) => {
switch (type) {
case "b":
return "Branch";
case "t":
return "Tag";
case "r":
return "Revision";
}
};
return (
<ResponsiveWrapper className="field mb-0 is-flex is-flex-direction-column is-fullwidth">
<label className="label">{label}</label>
@@ -107,7 +96,7 @@ const CompareSelector: FC<Props> = ({ onSelect, selected, label, repository }) =
onClick={() => setShowDropdown(!showDropdown)}
>
<span className="is-ellipsis-overflow">
<strong>{getActionTypeName(selection.type)}:</strong> {selection.name}
<strong>{t(`compare.selector.typeTitle.${selection.type}`)}:</strong> {selection.name}
</span>
<span className="icon is-small">
<Icon>angle-down</Icon>

View File

@@ -14,34 +14,36 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import React from "react";
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { Repository } from "@scm-manager/ui-types";
import RepositoryDetailTable from "./RepositoryDetailTable";
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
import { useDocumentTitleForRepository } from "@scm-manager/ui-core";
import RepositoryDetailTable from "./RepositoryDetailTable";
type Props = {
repository: Repository;
};
class RepositoryDetails extends React.Component<Props> {
render() {
const { repository } = this.props;
return (
<div>
<RepositoryDetailTable repository={repository} />
<hr />
<div className="content">
<ExtensionPoint<extensionPoints.RepositoryDetailsInformation>
name="repos.repository-details.information"
renderAll={true}
props={{
repository
}}
/>
</div>
const RepositoryDetails: FC<Props> = ({ repository }) => {
const [t] = useTranslation("repos");
useDocumentTitleForRepository(repository, t("repositoryRoot.menu.informationNavLink"));
return (
<div>
<RepositoryDetailTable repository={repository} />
<hr />
<div className="content">
<ExtensionPoint<extensionPoints.RepositoryDetailsInformation>
name="repos.repository-details.information"
renderAll={true}
props={{
repository,
}}
/>
</div>
);
}
}
</div>
);
};
export default RepositoryDetails;

View File

@@ -15,13 +15,14 @@
*/
import React, { FC } from "react";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Changeset, Repository } from "@scm-manager/ui-types";
import { ErrorPage, Loading } from "@scm-manager/ui-components";
import ChangesetDetails from "../components/changesets/ChangesetDetails";
import { FileControlFactory } from "@scm-manager/ui-components";
import { RepositoryRevisionContextProvider, useChangeset } from "@scm-manager/ui-api";
import { useParams } from "react-router-dom";
import { useDocumentTitle } from "@scm-manager/ui-core";
type Props = {
repository: Repository;
@@ -36,6 +37,18 @@ const ChangesetView: FC<Props> = ({ repository, fileControlFactoryFactory }) =>
const { id } = useParams<Params>();
const { isLoading, error, data: changeset } = useChangeset(repository, id);
const [t] = useTranslation("repos");
useDocumentTitle(
changeset?.id
? t("changesets.idWithNamespaceName", {
id: changeset.id.slice(0, 7),
namespace: repository.namespace,
name: repository.name,
})
: t("changesets.changesetsWithNamespaceName", {
namespace: repository.namespace,
name: repository.name,
})
);
if (error) {
return <ErrorPage title={t("changesets.errorTitle")} subtitle={t("changesets.errorSubtitle")} error={error} />;

View File

@@ -27,6 +27,7 @@ import {
Notification,
urls,
} from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
export const usePage = () => {
const match = useRouteMatch();
@@ -39,12 +40,54 @@ type Props = {
url: string;
};
const Changesets: FC<Props> = ({ repository, branch, url }) => {
const Changesets: FC<Props> = ({ repository, branch, ...props }) => {
const page = usePage();
const { isLoading, error, data } = useChangesets(repository, { branch, page: page - 1 });
const [t] = useTranslation("repos");
const getDocumentTitle = () => {
if (data?.pageTotal && data.pageTotal > 1 && page) {
if (branch) {
return t("changesets.commitsWithPageRevisionAndNamespaceName", {
page,
total: data.pageTotal,
revision: branch.name,
namespace: repository.namespace,
name: repository.name,
});
} else {
return t("changesets.commitsWithPageAndNamespaceName", {
page,
total: data.pageTotal,
namespace: repository.namespace,
name: repository.name,
});
}
} else if (branch) {
return t("changesets.commitsWithRevisionAndNamespaceName", {
revision: branch.name,
namespace: repository.namespace,
name: repository.name,
});
} else {
return t("changesets.commitsWithNamespaceName", {
namespace: repository.namespace,
name: repository.name,
});
}
};
useDocumentTitle(getDocumentTitle());
return <ChangesetsPanel repository={repository} error={error} isLoading={isLoading} data={data} url={url} />;
return (
<ChangesetsPanel
isLoading={isLoading}
error={error}
data={data}
repository={repository}
branch={branch}
{...props}
/>
);
};
type ChangesetsPanelProps = Props & {
@@ -53,7 +96,7 @@ type ChangesetsPanelProps = Props & {
data?: ChangesetCollection;
};
export const ChangesetsPanel: FC<ChangesetsPanelProps> = ({ repository, error, isLoading, data, url }) => {
export const ChangesetsPanel: FC<ChangesetsPanelProps> = ({ repository, error, isLoading, data, url, branch }) => {
const page = usePage();
const [t] = useTranslation("repos");
const changesets = data?._embedded?.changesets;

View File

@@ -16,16 +16,16 @@
import React, { FC } from "react";
import { Route, Switch } from "react-router-dom";
import CreateRepository from "./CreateRepository";
import ImportRepository from "./ImportRepository";
import { useBinder } from "@scm-manager/ui-extensions";
import { useTranslation } from "react-i18next";
import { Notification, Page, urls } from "@scm-manager/ui-components";
import RepositoryFormSwitcher from "../components/form/RepositoryFormSwitcher";
import { useIndex, useNamespaceStrategies, useRepositoryTypes } from "@scm-manager/ui-api";
import { Page, urls } from "@scm-manager/ui-components";
import { Notification, useDocumentTitle } from "@scm-manager/ui-core";
import { extensionPoints, useBinder } from "@scm-manager/ui-extensions";
import NamespaceAndNameFields from "../components/NamespaceAndNameFields";
import RepositoryInformationForm from "../components/RepositoryInformationForm";
import { extensionPoints } from "@scm-manager/ui-extensions/";
import RepositoryFormSwitcher from "../components/form/RepositoryFormSwitcher";
import CreateRepository from "./CreateRepository";
import ImportRepository from "./ImportRepository";
type CreatorRouteProps = {
creator: extensionPoints.RepositoryCreatorExtension;
@@ -41,13 +41,14 @@ const useCreateRepositoryData = () => {
pageLoadingError: errorNS || errorRT || errorIdx || undefined,
namespaceStrategies,
repositoryTypes,
index
index,
};
};
const CreatorRoute: FC<CreatorRouteProps> = ({ creator, creators }) => {
const { isPageLoading, pageLoadingError, namespaceStrategies, repositoryTypes, index } = useCreateRepositoryData();
const [t] = useTranslation(["repos", "plugins"]);
useDocumentTitle(creator.subtitle);
const Component = creator.component;
@@ -88,15 +89,15 @@ const CreateRepositoryRoot: FC = () => {
path: "",
icon: "plus",
label: t("repositoryForm.createButton"),
component: CreateRepository
component: CreateRepository,
},
{
subtitle: t("import.subtitle"),
path: "import",
icon: "file-upload",
label: t("repositoryForm.importButton"),
component: ImportRepository
}
component: ImportRepository,
},
];
const extCreators = binder.getExtensions<extensionPoints.RepositoryCreator>("repos.creator");
@@ -106,7 +107,7 @@ const CreateRepositoryRoot: FC = () => {
return (
<Switch>
{creators.map(creator => (
{creators.map((creator) => (
<Route key={creator.path} exact path={urls.concat("/repos/create", creator.path)}>
<CreatorRoute creator={creator} creators={creators} />
</Route>

View File

@@ -16,18 +16,19 @@
import React, { FC } from "react";
import { useRouteMatch } from "react-router-dom";
import RepositoryForm from "../components/form";
import { Repository } from "@scm-manager/ui-types";
import { ErrorNotification, Subtitle, urls } from "@scm-manager/ui-components";
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
import RepositoryDangerZone from "./RepositoryDangerZone";
import { useTranslation } from "react-i18next";
import ExportRepository from "./ExportRepository";
import { Repository } from "@scm-manager/ui-types";
import { useUpdateRepository } from "@scm-manager/ui-api";
import { ErrorNotification, Subtitle, urls } from "@scm-manager/ui-components";
import { useDocumentTitleForRepository } from "@scm-manager/ui-core";
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
import UpdateNotification from "../../components/UpdateNotification";
import RepositoryForm from "../components/form";
import Reindex from "../components/Reindex";
import RepositoryDangerZone from "./RepositoryDangerZone";
import ExportRepository from "./ExportRepository";
import HealthCheckWarning from "./HealthCheckWarning";
import RunHealthCheck from "./RunHealthCheck";
import UpdateNotification from "../../components/UpdateNotification";
import Reindex from "../components/Reindex";
type Props = {
repository: Repository;
@@ -37,11 +38,12 @@ const EditRepo: FC<Props> = ({ repository }) => {
const match = useRouteMatch();
const { isLoading, error, update, isUpdated } = useUpdateRepository();
const [t] = useTranslation("repos");
useDocumentTitleForRepository(repository, t("repositoryRoot.settingsTitle"));
const url = urls.matchedUrlFromMatch(match);
const extensionProps = {
repository,
url
url,
};
return (

View File

@@ -29,6 +29,7 @@ import {
PageActions,
urls,
} from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
import RepositoryList from "../components/list";
import { useNamespaceAndNameContext, useNamespaces, useRepositories } from "@scm-manager/ui-api";
import { NamespaceCollection, RepositoryCollection } from "@scm-manager/ui-types";
@@ -170,6 +171,20 @@ const Overview: FC = () => {
const { isLoading, error, namespace, namespaces, repositories, search, page } = useOverviewData();
const history = useHistory();
const [t] = useTranslation("repos");
const getDocumentTitle = () => {
if (repositories?.pageTotal && repositories.pageTotal > 1 && page) {
if (namespace) {
return t("overview.titleWithNamespaceAndPage", { page, total: repositories.pageTotal, namespace });
} else {
return t("overview.titleWithPage", { page, total: repositories.pageTotal });
}
} else if (namespace) {
return t("overview.titleWithNamespace", { namespace });
} else {
return t("overview.title");
}
};
useDocumentTitle(getDocumentTitle());
const binder = useBinder();
const context = useNamespaceAndNameContext();
useEffect(() => {

View File

@@ -15,10 +15,11 @@
*/
import React, { FC } from "react";
import { useImportLog } from "@scm-manager/ui-api";
import { useParams } from "react-router-dom";
import { Page } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import { useImportLog } from "@scm-manager/ui-api";
import { Page } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
type Params = {
logId: string;
@@ -28,6 +29,7 @@ const ImportLog: FC = () => {
const { logId } = useParams<Params>();
const { isLoading, data, error } = useImportLog(logId);
const [t] = useTranslation("commons");
useDocumentTitle(t("importLog.title"));
return (
<Page title={t("importLog.title")} loading={isLoading} error={error}>

View File

@@ -14,13 +14,13 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import { Namespace } from "@scm-manager/ui-types";
import React, { FC } from "react";
import { ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-core";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useRepositories } from "@scm-manager/ui-api";
import { DateFromNow } from "@scm-manager/ui-components";
import { Link } from "react-router-dom";
import { ErrorNotification, Loading, Subtitle, useDocumentTitle } from "@scm-manager/ui-core";
import { Namespace } from "@scm-manager/ui-types";
type Props = {
namespace: Namespace;
@@ -28,6 +28,7 @@ type Props = {
const NamespaceInformation: FC<Props> = ({ namespace }) => {
const [t] = useTranslation("namespaces");
useDocumentTitle(t("namespaceRoot.infoPage.subtitle"), namespace.namespace);
const { data: repositories, error, isLoading } = useRepositories({ namespace: namespace, pageSize: 9999, page: 0 });
if (error) {

View File

@@ -16,11 +16,12 @@
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { useAvailablePermissions, usePermissions } from "@scm-manager/ui-api";
import { ErrorPage, Loading, Subtitle } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
import { Namespace, Repository } from "@scm-manager/ui-types";
import CreatePermissionForm from "./CreatePermissionForm";
import PermissionsTable from "../components/PermissionsTable";
import { useAvailablePermissions, usePermissions } from "@scm-manager/ui-api";
type Props = {
namespaceOrRepository: Namespace | Repository;
@@ -37,13 +38,17 @@ const usePermissionData = (namespaceOrRepository: Namespace | Repository) => {
isLoading: permissions.isLoading || availablePermissions.isLoading,
error: permissions.error || availablePermissions.error,
permissions: permissions.data,
availablePermissions: availablePermissions.data
availablePermissions: availablePermissions.data,
};
};
const Permissions: FC<Props> = ({ namespaceOrRepository }) => {
const { isLoading, error, permissions, availablePermissions } = usePermissionData(namespaceOrRepository);
const [t] = useTranslation("repos");
useDocumentTitle(
t("repositoryRoot.menu.permissionsNavLink"),
namespaceOrRepository.namespace + (isRepository(namespaceOrRepository) ? "/" + namespaceOrRepository.name : "")
);
if (error) {
return <ErrorPage title={t("permission.error-title")} subtitle={t("permission.error-subtitle")} error={error} />;

View File

@@ -20,7 +20,7 @@ import { useHistory, useLocation, useParams } from "react-router-dom";
import { RepositoryRevisionContextProvider, urls, useSources } from "@scm-manager/ui-api";
import { Branch, Repository } from "@scm-manager/ui-types";
import { Breadcrumb } from "@scm-manager/ui-components";
import { Notification, ErrorNotification, Loading } from "@scm-manager/ui-core";
import { Notification, ErrorNotification, Loading, useDocumentTitle } from "@scm-manager/ui-core";
import FileTree from "../components/FileTree";
import Content from "./Content";
import CodeActionBar from "../../codeSection/components/CodeActionBar";
@@ -57,6 +57,30 @@ const Sources: FC<Props> = ({ repository, branches, selectedBranch, baseUrl }) =
const history = useHistory();
const location = useLocation();
const [t] = useTranslation("repos");
const getDocumentTitle = () => {
if (revision) {
const getRevision = () => {
return branches?.some((branch) => branch.name === revision) ? revision : revision.slice(0, 7);
};
if (path) {
return t("sources.pathWithRevisionAndNamespaceName", {
path: path,
revision: getRevision(),
namespace: repository.namespace,
name: repository.name,
});
} else {
return t("sources.sourcesWithRevisionAndNamespaceName", {
revision: getRevision(),
namespace: repository.namespace,
name: repository.name,
});
}
} else {
return repository.namespace + "/" + repository.name;
}
};
useDocumentTitle(getDocumentTitle());
const [contentRef, setContentRef] = useState<HTMLElement | null>();
useScrollToElement(contentRef, () => location.hash, location.hash);
@@ -72,7 +96,6 @@ const Sources: FC<Props> = ({ repository, branches, selectedBranch, baseUrl }) =
);
}
}, [branches, selectedBranch, history, baseUrl, location.hash]);
const {
isLoading,
error,

View File

@@ -19,6 +19,7 @@ import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { Repository, Tag } from "@scm-manager/ui-types";
import { Subtitle, DateFromNow, SignatureIcon } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
import TagButtonGroup from "./TagButtonGroup";
import CompareLink from "../../compare/CompareLink";
@@ -29,6 +30,13 @@ type Props = {
const TagDetail: FC<Props> = ({ repository, tag }) => {
const [t] = useTranslation("repos");
useDocumentTitle(
t("tag.tagWithNamespaceName", {
tag: tag.name,
namespace: repository.namespace,
name: repository.name,
})
);
const encodedTag = encodeURIComponent(tag.name);

View File

@@ -17,6 +17,7 @@
import React, { FC, useMemo, useState } from "react";
import { Repository } from "@scm-manager/ui-types";
import { ErrorNotification, Loading, Notification, Subtitle } from "@scm-manager/ui-components";
import { useDocumentTitleForRepository } from "@scm-manager/ui-core";
import { useTranslation } from "react-i18next";
import orderTags, { SORT_OPTIONS, SortOption } from "../orderTags";
import TagTable from "../components/TagTable";
@@ -31,6 +32,7 @@ type Props = {
const TagsOverview: FC<Props> = ({ repository, baseUrl }) => {
const { isLoading, error, data } = useTags(repository);
const [t] = useTranslation("repos");
useDocumentTitleForRepository(repository, t("tags.overview.title"));
const [sort, setSort] = useState<SortOption | undefined>();
const tags = useMemo(() => orderTags(data?._embedded?.tags || [], sort), [data, sort]);

View File

@@ -24,7 +24,7 @@ import {
Tag,
urls,
} from "@scm-manager/ui-components";
import { Notification, Level } from "@scm-manager/ui-core";
import { Notification, Level, useDocumentTitle } from "@scm-manager/ui-core";
import { Link, useLocation, useParams } from "react-router-dom";
import { useIndex, useNamespaceAndNameContext, useSearch, useSearchCounts, useSearchTypes } from "@scm-manager/ui-api";
import Results from "./Results";
@@ -163,9 +163,22 @@ const InvalidSearch: FC = () => {
const Search: FC = () => {
const { data: index } = useIndex();
const [t] = useTranslation(["commons", "plugins"]);
const [showHelp, setShowHelp] = useState(false);
const { query, selectedType, page, namespace, name } = usePageParams();
const searchOptions = {
type: selectedType,
page: page - 1,
pageSize: 25,
namespaceContext: namespace,
repositoryNameContext: name,
};
const { data, isLoading, error } = useSearch(query, searchOptions);
const [t] = useTranslation(["commons", "plugins"]);
useDocumentTitle(
data?.pageTotal && data.pageTotal > 1 && page
? t("search.titleWithPage", { page, total: data.pageTotal })
: t("search.title")
);
const [showHelp, setShowHelp] = useState(false);
const context = useNamespaceAndNameContext();
useEffect(() => {
context.setNamespace(namespace || "");
@@ -176,14 +189,6 @@ const Search: FC = () => {
context.setName("");
};
}, [namespace, name, context]);
const searchOptions = {
type: selectedType,
page: page - 1,
pageSize: 25,
namespaceContext: namespace,
repositoryNameContext: name,
};
const { data, isLoading, error } = useSearch(query, searchOptions);
const types = useSearchTypes(searchOptions);
types.sort(orderTypes(t));

View File

@@ -15,13 +15,13 @@
*/
import React, { FC, useState } from "react";
import { useSearchableTypes, useSearchSyntaxContent } from "@scm-manager/ui-api";
import { useTranslation } from "react-i18next";
import { copyToClipboard, InputField, Loading, MarkdownView, Page } from "@scm-manager/ui-components";
import { ErrorNotification, Icon, Tooltip, Button } from "@scm-manager/ui-core";
import classNames from "classnames";
import { parse } from "date-fns";
import styled from "styled-components";
import classNames from "classnames";
import { useSearchableTypes, useSearchSyntaxContent } from "@scm-manager/ui-api";
import { copyToClipboard, InputField, MarkdownView, Page } from "@scm-manager/ui-components";
import { ErrorNotification, Icon, Loading, Tooltip, Button, useDocumentTitle } from "@scm-manager/ui-core";
import { SearchableType } from "@scm-manager/ui-types";
const StyledTooltip = styled(Tooltip)`
@@ -41,7 +41,9 @@ type ExpandableProps = {
const Expandable: FC<ExpandableProps> = ({ header, children, className }) => {
const [t] = useTranslation("commons");
useDocumentTitle(t("search.syntax.title"));
const [expanded, setExpanded] = useState(false);
return (
<div className={classNames("card search-syntax-accordion", className)}>
<header>
@@ -88,18 +90,20 @@ const Examples: FC<ExampleProps> = ({ searchableType }) => {
<h5 className="title mt-5">{t("search.syntax.exampleQueries.title")}</h5>
<div className="mb-2">{t("search.syntax.exampleQueries.description")}</div>
<table>
<tr>
<th>{t("search.syntax.exampleQueries.table.description")}</th>
<th>{t("search.syntax.exampleQueries.table.query")}</th>
<th>{t("search.syntax.exampleQueries.table.explanation")}</th>
</tr>
{examples.map((example) => (
<tr key={example.description}>
<td>{example.description}</td>
<td>{example.query}</td>
<td>{example.explanation}</td>
<tbody>
<tr>
<th>{t("search.syntax.exampleQueries.table.description")}</th>
<th>{t("search.syntax.exampleQueries.table.query")}</th>
<th>{t("search.syntax.exampleQueries.table.explanation")}</th>
</tr>
))}
{examples.map((example) => (
<tr key={example.description}>
<td>{example.description}</td>
<td>{example.query}</td>
<td>{example.explanation}</td>
</tr>
))}
</tbody>
</table>
</>
);
@@ -126,28 +130,30 @@ const SearchableTypes: FC = () => {
header={t(`plugins:search.types.${searchableType.name}.title`, searchableType.name)}
>
<table>
<tr>
<th>{t("search.syntax.fields.name")}</th>
<th>{t("search.syntax.fields.type")}</th>
<th>{t("search.syntax.fields.exampleValue")}</th>
<th>{t("search.syntax.fields.hints")}</th>
</tr>
{searchableType.fields.map((searchableField) => (
<tr key={searchableField.name}>
<th>{searchableField.name}</th>
<td>{searchableField.type}</td>
<td>
{t(`plugins:search.types.${searchableType.name}.fields.${searchableField.name}.exampleValue`, {
defaultValue: "",
})}
</td>
<td>
{t(`plugins:search.types.${searchableType.name}.fields.${searchableField.name}.hints`, {
defaultValue: "",
})}
</td>
<tbody>
<tr>
<th>{t("search.syntax.fields.name")}</th>
<th>{t("search.syntax.fields.type")}</th>
<th>{t("search.syntax.fields.exampleValue")}</th>
<th>{t("search.syntax.fields.hints")}</th>
</tr>
))}
{searchableType.fields.map((searchableField) => (
<tr key={searchableField.name}>
<th>{searchableField.name}</th>
<td>{searchableField.type}</td>
<td>
{t(`plugins:search.types.${searchableType.name}.fields.${searchableField.name}.exampleValue`, {
defaultValue: "",
})}
</td>
<td>
{t(`plugins:search.types.${searchableType.name}.fields.${searchableField.name}.hints`, {
defaultValue: "",
})}
</td>
</tr>
))}
</tbody>
</table>
<Examples searchableType={searchableType} />
</Expandable>

View File

@@ -23,9 +23,10 @@ import {
Notification,
PasswordConfirmation,
SubmitButton,
Subtitle
Subtitle,
} from "@scm-manager/ui-components";
import { useSetUserPassword } from "@scm-manager/ui-api";
import { useDocumentTitle } from "@scm-manager/ui-core";
type Props = {
user: User;
@@ -33,6 +34,7 @@ type Props = {
const SetUserPassword: FC<Props> = ({ user }) => {
const [t] = useTranslation("users");
useDocumentTitle(t("singleUser.menu.setPasswordNavLink"), user.displayName);
const { passwordOverwritten, setPassword, error, isLoading, reset } = useSetUserPassword(user);
const [newPassword, setNewPassword] = useState("");
const [passwordValid, setPasswordValid] = useState(false);

View File

@@ -22,6 +22,7 @@ import AddApiKey from "./AddApiKey";
import { useTranslation } from "react-i18next";
import { useApiKeys, useDeleteApiKey } from "@scm-manager/ui-api";
import { Link as RouterLink } from "react-router-dom";
import { useDocumentTitle } from "@scm-manager/ui-core";
type Props = {
user: User | Me;
@@ -29,6 +30,7 @@ type Props = {
const SetApiKeys: FC<Props> = ({ user }) => {
const [t] = useTranslation("users");
useDocumentTitle(t("singleUser.menu.setApiKeyNavLink"), user.displayName);
const { isLoading, data: apiKeys, error: fetchError } = useApiKeys(user);
const { error: deletionError, remove } = useDeleteApiKey(user);
const error = deletionError || fetchError;

View File

@@ -21,6 +21,7 @@ import AddPublicKey from "./AddPublicKey";
import PublicKeyTable from "./PublicKeyTable";
import { ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-components";
import { useDeletePublicKey, usePublicKeys } from "@scm-manager/ui-api";
import { useDocumentTitle } from "@scm-manager/ui-core";
type Props = {
user: User | Me;
@@ -28,6 +29,7 @@ type Props = {
const SetPublicKeys: FC<Props> = ({ user }) => {
const [t] = useTranslation("users");
useDocumentTitle(t("singleUser.menu.setPublicKeyNavLink"), user.displayName);
const { error: fetchingError, isLoading, data: publicKeys } = usePublicKeys(user);
const { error: deletionError, remove } = useDeletePublicKey(user);
const error = fetchingError || deletionError;

View File

@@ -15,7 +15,7 @@
*/
import React, { FC, useState } from "react";
import { useTranslation, WithTranslation } from "react-i18next";
import { useTranslation } from "react-i18next";
import { User } from "@scm-manager/ui-types";
import {
Checkbox,
@@ -23,18 +23,20 @@ import {
DateFromNow,
Help,
InfoTable,
MailLink
MailLink,
} from "@scm-manager/ui-components";
import { Icon } from "@scm-manager/ui-components";
import PermissionOverview from "../PermissionOverview";
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
import { useDocumentTitle } from "@scm-manager/ui-core";
type Props = WithTranslation & {
type Props = {
user: User;
};
const Details: FC<Props> = ({ user }) => {
const [t] = useTranslation("users");
useDocumentTitle(t("singleUser.menu.informationNavLink"), user.displayName);
const [collapsed, setCollapsed] = useState(true);
const toggleCollapse = () => setCollapsed(!collapsed);
@@ -96,7 +98,11 @@ const Details: FC<Props> = ({ user }) => {
<DateFromNow date={user.lastModified} />
</td>
</tr>
<ExtensionPoint<extensionPoints.UserInformationTableBottom> name="user.information.table.bottom" props={{user}} renderAll={true} />
<ExtensionPoint<extensionPoints.UserInformationTableBottom>
name="user.information.table.bottom"
props={{ user }}
renderAll={true}
/>
</tbody>
</InfoTable>
{permissionOverview}

View File

@@ -21,7 +21,7 @@ import { Page } from "@scm-manager/ui-components";
import { Form, useCreateResource } from "@scm-manager/ui-forms";
import * as userValidator from "../components/userValidation";
import { Link, User, UserCollection, UserCreation } from "@scm-manager/ui-types";
import { ErrorNotification, Loading, Notification } from "@scm-manager/ui-core";
import { ErrorNotification, Loading, Notification, useDocumentTitle } from "@scm-manager/ui-core";
import { useUsers } from "@scm-manager/ui-api";
type UserCreationForm = Pick<UserCreation, "password" | "name" | "displayName" | "active" | "external" | "mail"> & {
@@ -30,6 +30,7 @@ type UserCreationForm = Pick<UserCreation, "password" | "name" | "displayName" |
const CreateUserForm: FC<{ users: UserCollection }> = ({ users }) => {
const [t] = useTranslation("users");
useDocumentTitle(t("createUser.title"));
const { submit, submissionResult: createdUser } = useCreateResource<UserCreationForm, User>(
(users._links.create as Link).href,
["user", "users"],

View File

@@ -15,13 +15,14 @@
*/
import React, { FC } from "react";
import DeleteUser from "./DeleteUser";
import { User } from "@scm-manager/ui-types";
import UserConverter from "../components/UserConverter";
import { Form, useUpdateResource } from "@scm-manager/ui-forms";
import * as userValidator from "../components/userValidation";
import { Subtitle } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import { User } from "@scm-manager/ui-types";
import { Subtitle } from "@scm-manager/ui-components";
import { useDocumentTitle } from "@scm-manager/ui-core";
import { Form, useUpdateResource } from "@scm-manager/ui-forms";
import UserConverter from "../components/UserConverter";
import * as userValidator from "../components/userValidation";
import DeleteUser from "./DeleteUser";
type Props = {
user: User;
@@ -29,6 +30,7 @@ type Props = {
const EditUser: FC<Props> = ({ user }) => {
const [t] = useTranslation("users");
useDocumentTitle(t("singleUser.settingsTitle"), user.displayName);
const { submit } = useUpdateResource<User>(user, (user) => user.name, {
contentType: "application/vnd.scmm-user+json;v=2",
collectionName: ["user", "users"],

View File

@@ -20,7 +20,7 @@ import { useTranslation } from "react-i18next";
import { useUsers } from "@scm-manager/ui-api";
import { User, UserCollection } from "@scm-manager/ui-types";
import { CreateButton, LinkPaginator, OverviewPageActions, Page, PageActions, urls } from "@scm-manager/ui-components";
import { Notification } from "@scm-manager/ui-core";
import { Notification, useDocumentTitle } from "@scm-manager/ui-core";
import { UserTable } from "./../components/table";
type UserPageProps = {
@@ -52,6 +52,7 @@ const Users: FC = () => {
const page = urls.getPageFromMatch({ params });
const { isLoading, error, data } = useUsers({ page: page - 1, search });
const [t] = useTranslation("users");
useDocumentTitle(t("users.title"));
const users = data?._embedded?.users;
const canAddUsers = !!data?._links.create;