Skip to content

Commit

Permalink
feat: update group and user page design (#3428)
Browse files Browse the repository at this point in the history
* minor: improve avatar component for Renku 2.0 (#3437)

fix #3441 
---------

Co-authored-by: Flora Thiebaut <[email protected]>
  • Loading branch information
andre-code and leafty authored Dec 18, 2024
1 parent 91d6b15 commit 987ee95
Show file tree
Hide file tree
Showing 30 changed files with 1,144 additions and 571 deletions.
56 changes: 56 additions & 0 deletions client/src/components/PageNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import cx from "classnames";
import { Eye, Sliders } from "react-bootstrap-icons";
import { Nav, NavItem } from "reactstrap";
import RenkuNavLinkV2 from "./RenkuNavLinkV2";

export interface PageNavOptions {
overviewUrl: string;
settingsUrl: string;
}
export default function PageNav({ options }: { options: PageNavOptions }) {
return (
<>
<Nav tabs>
<NavItem>
<RenkuNavLinkV2
end
to={options.overviewUrl}
title="Overview"
data-cy="nav-link-overview"
>
<Eye className={cx("bi", "me-1")} />
Overview
</RenkuNavLinkV2>
</NavItem>
<NavItem>
<RenkuNavLinkV2
end
to={options.settingsUrl}
title="Settings"
data-cy="nav-link-settings"
>
<Sliders className={cx("bi", "me-1")} />
Settings
</RenkuNavLinkV2>
</NavItem>
</Nav>
</>
);
}
44 changes: 44 additions & 0 deletions client/src/components/entityWatermark/EntityWatermark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import cx from "classnames";
import { People, Person } from "react-bootstrap-icons";
import styles from "./entityWatermark.module.scss";

interface EntityWatermarkProps {
type: "user" | "group";
}
export function EntityWatermark({ type }: EntityWatermarkProps) {
return (
<div className="position-relative">
<div
className={cx(
"d-none",
"d-lg-block",
"position-absolute",
"top-0",
"end-0",
styles.EntityWatermark
)}
>
{type === "group" && <People />}
{type === "user" && <Person />}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.EntityWatermark {
font-size: 150px;
line-height: 0;
color: rgba(0, 0, 0, 0.1);
}
25 changes: 14 additions & 11 deletions client/src/features/dashboardV2/DashboardV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,27 +42,30 @@ import {
ListGroup,
Row,
} from "reactstrap";
import { useLoginUrl } from "../../authentication/useLoginUrl.hook.ts";

import { useLoginUrl } from "../../authentication/useLoginUrl.hook";
import { RtkOrNotebooksError } from "../../components/errors/RtkErrorAlert";
import { Loader } from "../../components/Loader";
import { ABSOLUTE_ROUTES } from "../../routing/routes.constants.ts";
import useLegacySelector from "../../utils/customHooks/useLegacySelector.hook.ts";
import { ABSOLUTE_ROUTES } from "../../routing/routes.constants";
import useLegacySelector from "../../utils/customHooks/useLegacySelector.hook";
import CreateGroupButton from "../groupsV2/new/CreateGroupButton";
import {
GetGroupsApiResponse,
GetProjectsApiResponse,
useGetProjectsQuery,
useGetGroupsQuery,
useGetProjectsQuery,
} from "../projectsV2/api/projectV2.enhanced-api";
import CreateGroupButton from "../groupsV2/new/CreateGroupButton";
import CreateProjectV2Button from "../projectsV2/new/CreateProjectV2Button";
import GroupShortHandDisplay from "../projectsV2/show/GroupShortHandDisplay";
import ProjectShortHandDisplay from "../projectsV2/show/ProjectShortHandDisplay";
import SearchV2Bar from "../searchV2/components/SearchV2Bar.tsx";
import { useGetSessionsQuery as useGetSessionsQueryV2 } from "../sessionsV2/sessionsV2.api.ts";
import { useGetUserQuery } from "../usersV2/api/users.api.ts";
import UserAvatar, { UserAvatarSize } from "../usersV2/show/UserAvatar.tsx";
import DashboardStyles from "./DashboardV2.module.scss";
import SearchV2Bar from "../searchV2/components/SearchV2Bar";
import { useGetSessionsQuery as useGetSessionsQueryV2 } from "../sessionsV2/sessionsV2.api";
import { useGetUserQuery } from "../usersV2/api/users.api";
import UserAvatar from "../usersV2/show/UserAvatar";
import DashboardV2Sessions from "./DashboardV2Sessions";

import DashboardStyles from "./DashboardV2.module.scss";

export default function DashboardV2() {
const userLogged = useLegacySelector<boolean>(
(state) => state.stateModel.user.logged
Expand Down Expand Up @@ -373,7 +376,7 @@ function UserDashboard() {
"my-md-4"
)}
>
<UserAvatar username={userInfo.username} size={UserAvatarSize.large} />
<UserAvatar namespace={userInfo.username} size="lg" />
<h3 className={cx("text-center", "mb-0")}>
{userInfo.first_name} {userInfo.last_name}
</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,14 +392,14 @@ function DataConnectorViewMetadata({
<DataConnectorPropertyValue title="Owner">
<div className={cx("d-flex", "align-items-center")}>
<div className="me-1">
<UserAvatar username={dataConnector.namespace} />{" "}
<UserAvatar namespace={dataConnector.namespace} />{" "}
</div>
{namespaceUrl == null ? (
<div className="me-1">{dataConnector.namespace}</div>
<div className="me-1">@{dataConnector.namespace}</div>
) : (
<div>
<Link className="me-1" to={namespaceUrl}>
{dataConnector.namespace}
@{dataConnector.namespace}
</Link>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,20 @@ function DataConnectorBoxContent({
totalConnectors={data.total}
/>
<CardBody>
{data.total === 0 && (
<p className="m-0">
Add published datasets from data repositories, and connect to
cloud storage to read and write custom data.
</p>
{data.total === 0 && namespaceKind === "group" && (
<AddEmptyListForGroupNamespace namespace={namespace} />
)}
{data.total === 0 && namespaceKind === "user" && (
<AddEmptyListForUserNamespace namespace={namespace} />
)}
{data.total > 0 && (
<ListGroup flush>
{data.dataConnectors?.map((dc) => (
<DataConnectorBoxListDisplay key={dc.id} dataConnector={dc} />
<DataConnectorBoxListDisplay
key={dc.id}
dataConnector={dc}
extendedPreview={true}
/>
))}
</ListGroup>
)}
Expand Down Expand Up @@ -320,3 +324,35 @@ function DataConnectorLoadingBoxContent() {
</Card>
);
}

function AddEmptyListForGroupNamespace({ namespace }: { namespace: string }) {
const { permissions } = useGroupPermissions({ groupSlug: namespace });

return (
<PermissionsGuard
disabled={<p>This group has no visible data connectors.</p>}
enabled={
<p>
Add published datasets from data repositories, and connect to cloud
storage to read and write custom data.
</p>
}
requestedPermission="write"
userPermissions={permissions}
/>
);
}

function AddEmptyListForUserNamespace({ namespace }: { namespace: string }) {
const { data: currentUser } = useGetUserQuery();

if (currentUser?.isLoggedIn && currentUser.username === namespace) {
return (
<p>
Add published datasets from data repositories, and connect to cloud
storage to read and write custom data.
</p>
);
}
return <p>This user has no visible data connectors.</p>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@

import cx from "classnames";
import { useCallback, useMemo } from "react";
import { Globe2, Lock } from "react-bootstrap-icons";
import { EyeFill, Globe2, Lock, Pencil } from "react-bootstrap-icons";
import { Col, ListGroupItem, Row } from "reactstrap";

import ClampedParagraph from "../../../components/clamped/ClampedParagraph";
import { TimeCaption } from "../../../components/TimeCaption";
import useLocationHash from "../../../utils/customHooks/useLocationHash.hook";
import UserAvatar from "../../usersV2/show/UserAvatar";
import type {
DataConnector,
DataConnectorToProjectLink,
Expand All @@ -34,16 +35,20 @@ import DataConnectorView from "./DataConnectorView";
interface DataConnectorBoxListDisplayProps {
dataConnector: DataConnector;
dataConnectorLink?: DataConnectorToProjectLink;
extendedPreview?: boolean;
}
export default function DataConnectorBoxListDisplay({
dataConnector,
dataConnectorLink,
extendedPreview,
}: DataConnectorBoxListDisplayProps) {
const {
name,
description,
visibility,
creation_date: creationDate,
storage,
namespace,
} = dataConnector;

const [hash, setHash] = useLocationHash();
Expand All @@ -59,6 +64,23 @@ export default function DataConnectorBoxListDisplay({
});
}, [dcHash, setHash]);

const type = `${storage?.configuration?.type?.toString() ?? ""} ${
storage?.configuration?.provider?.toString() ?? ""
}`;
const readOnly =
extendedPreview &&
(storage?.readonly ? (
<div>
<EyeFill className={cx("bi", "me-1")} />
Read only
</div>
) : (
<div>
<Pencil className={cx("bi", "me-1")} />
Allow Read-write
</div>
));

return (
<>
<ListGroupItem
Expand All @@ -67,33 +89,56 @@ export default function DataConnectorBoxListDisplay({
onClick={toggleDetails}
>
<Row className={cx("align-items-center", "g-2")}>
<Col>
<Col className={cx("d-flex", "flex-column")}>
<span className="fw-bold" data-cy="data-connector-name">
{name}
</span>
<div
className={cx(
"d-flex",
"flex-row",
"text-truncate",
"gap-2",
"align-items-center"
)}
>
<UserAvatar namespace={namespace} size="sm" />
<p className={cx("mb-0", "text-truncate", "text-muted")}>
{namespace}
</p>
</div>
{description && <ClampedParagraph>{description}</ClampedParagraph>}
{extendedPreview && <div className="text-muted">{type}</div>}
<div
className={cx(
"align-items-center",
"d-flex",
"flex-wrap",
"gap-2",
"justify-content-between",
"mt-auto"
"justify-content-between"
)}
>
<div>
<div
className={cx(
"align-items-center",
"d-flex",
"flex-wrap",
"gap-3",
"mt-auto"
)}
>
{visibility.toLowerCase() === "private" ? (
<>
<div>
<Lock className={cx("bi", "me-1")} />
Private
</>
</div>
) : (
<>
<div>
<Globe2 className={cx("bi", "me-1")} />
Public
</>
</div>
)}
{extendedPreview && readOnly}
</div>
<TimeCaption
datetime={creationDate}
Expand Down
30 changes: 30 additions & 0 deletions client/src/features/groupsV2/LazyGroupContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Suspense, lazy } from "react";
import PageLoader from "../../components/PageLoader";

const GroupV2Container = lazy(() => import("./show/GroupPageContainer"));

export default function LazyGroupContainer() {
return (
<Suspense fallback={<PageLoader />}>
<GroupV2Container />
</Suspense>
);
}
Loading

0 comments on commit 987ee95

Please sign in to comment.