Skip to content

Commit

Permalink
feat: add group page design
Browse files Browse the repository at this point in the history
  • Loading branch information
andre-code committed Dec 10, 2024
1 parent 23bfb1c commit 9ef2c8a
Show file tree
Hide file tree
Showing 16 changed files with 753 additions and 396 deletions.
43 changes: 43 additions & 0 deletions client/src/components/EntityWatermark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*!
* 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 { CSSProperties } from "react";
import { Folder, People, Person } from "react-bootstrap-icons";

interface EntityWatermarkProps {
type: "project" | "user" | "group";
}
export function EntityWatermark({ type }: EntityWatermarkProps) {
const watermarkStyles: CSSProperties = {
right: "0",
fontSize: "150px",
lineHeight: "0",
color: "rgba(0, 0, 0, 0.1)",
};
return (
<div
style={watermarkStyles}
className={cx("d-none", "d-lg-block", "position-absolute", "top-0")}
>
{type === "group" && <People />}
{type === "user" && <Person />}
{type === "project" && <Folder />}
</div>
);
}
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>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,11 @@ function DataConnectorBoxContent({
{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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import cx from "classnames";
import { useCallback, useState } 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";
Expand All @@ -33,23 +33,41 @@ 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,
} = dataConnector;

const [showDetails, setShowDetails] = useState(false);
const toggleDetails = useCallback(() => {
setShowDetails((open) => !open);
}, []);

const type = `${storage?.configuration?.type?.toString() ?? ""} ${
storage?.configuration?.provider?.toString() ?? ""
}`;
const readOnly = 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 @@ -63,6 +81,7 @@ export default function DataConnectorBoxListDisplay({
{name}
</span>
{description && <ClampedParagraph>{description}</ClampedParagraph>}
{extendedPreview && <div className="text-muted">{type}</div>}
<div
className={cx(
"align-items-center",
Expand All @@ -73,18 +92,27 @@ export default function DataConnectorBoxListDisplay({
"mt-auto"
)}
>
<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>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
import { Suspense, lazy } from "react";
import PageLoader from "../../components/PageLoader";

const GroupV2Show = lazy(() => import("./show/GroupV2Show"));
const GroupV2Overview = lazy(() => import("./show/GroupV2Show"));

export default function LazyGroupV2Show() {
export default function LazyGroupV2Overview() {
return (
<Suspense fallback={<PageLoader />}>
<GroupV2Show />
<GroupV2Overview />
</Suspense>
);
}
72 changes: 33 additions & 39 deletions client/src/features/groupsV2/members/GroupV2MemberListDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@
import cx from "classnames";
import { capitalize } from "lodash-es";
import { useMemo } from "react";
import { People } from "react-bootstrap-icons";
import { Link, generatePath } from "react-router-dom-v5-compat";
import { ListGroup } from "reactstrap";
import { Badge } from "reactstrap";

import { Loader } from "../../../components/Loader";
import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert";
import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants";
import { toSortedMembers } from "../../ProjectPageV2/utils/roleUtils";
import type { ProjectMemberResponse } from "../../projectsV2/api/projectV2.api";
import { useGetGroupsByGroupSlugMembersQuery } from "../../projectsV2/api/projectV2.enhanced-api";
import UserAvatar from "../../usersV2/show/UserAvatar";
import { GroupInformationBox } from "../show/GroupV2Information";

interface GroupV2MemberListDisplayProps {
group: string;
Expand All @@ -48,30 +49,33 @@ export default function GroupV2MemberListDisplay({
[members]
);

if (isLoading)
return (
<div className={cx("d-flex", "justify-content-center", "w-100")}>
<div className={cx("d-flex", "flex-column")}>
<Loader />
<div>Retrieving group members...</div>
</div>
</div>
);

if (error || sortedMembers == null) {
return <RtkOrNotebooksError error={error} dismissible={false} />;
}

if (!sortedMembers.length) {
return <p>There are no members in this group.</p>;
}

return (
<ListGroup className="mb-3">
<GroupInformationBox
icon={<People className="bi" />}
title={
<>
<span>Members</span>
<Badge>{sortedMembers.length ?? 0}</Badge>
</>
}
>
{!sortedMembers.length && <p>There are no members in this group.</p>}
{isLoading && (
<div className={cx("d-flex", "justify-content-center", "w-100")}>
<div className={cx("d-flex", "flex-column")}>
<Loader />
<div>Retrieving group members...</div>
</div>
</div>
)}
{sortedMembers?.map((member) => (
<GroupV2Member key={member.id} member={member} />
))}
</ListGroup>
</GroupInformationBox>
);
}

Expand All @@ -95,32 +99,22 @@ function GroupV2Member({ member }: GroupV2MemberProps) {
return (
<>
<Link
className={cx("list-group-item-action", "list-group-item")}
className={cx("mb-0")}
to={generatePath(ABSOLUTE_ROUTES.v2.users.show, { username })}
>
<div
className={cx(
"align-items-center",
"d-flex",
"flex-wrap",
"gap-2",
"justify-content-between"
)}
>
<div className={cx("d-flex", "gap-2")}>
<div
className={cx("align-items-center", "d-flex", "flex-wrap", "gap-2")}
className={cx(
"d-flex",
"flex-column",
"justify-content-center",
"text-truncate"
)}
>
<div className={cx("align-items-center", "d-flex", "gap-2")}>
<UserAvatar
firstName={firstName}
lastName={lastName}
username={username}
/>
<span className={cx("fw-bold")}>{name ?? "Unknown user"}</span>{" "}
</div>
<p className="m-0">{`@${username}`}</p>
<p className={cx("m-0", "text-truncate")}>
{name ?? "Unknown user"} ({capitalize(role)})
</p>
</div>
<p className="m-0">{capitalize(role)}</p>
</div>
</Link>
</>
Expand Down
Loading

0 comments on commit 9ef2c8a

Please sign in to comment.