|
|
@@ -1,19 +1,24 @@
|
|
|
import React, { useContext, useEffect, useMemo, useState } from "react";
|
|
|
+import { type Column } from "react-table";
|
|
|
import styled from "styled-components";
|
|
|
|
|
|
-import { type InviteType } from "shared/types";
|
|
|
-import api from "shared/api";
|
|
|
-import { Context } from "shared/Context";
|
|
|
-
|
|
|
-import Loading from "components/Loading";
|
|
|
-import InputRow from "components/form-components/InputRow";
|
|
|
-import Helper from "components/form-components/Helper";
|
|
|
-import Heading from "components/form-components/Heading";
|
|
|
import CopyToClipboard from "components/CopyToClipboard";
|
|
|
-import { type Column } from "react-table";
|
|
|
+import Heading from "components/form-components/Heading";
|
|
|
+import Helper from "components/form-components/Helper";
|
|
|
+import InputRow from "components/form-components/InputRow";
|
|
|
+import Loading from "components/Loading";
|
|
|
import Table from "components/OldTable";
|
|
|
+import Button from "components/porter/Button";
|
|
|
+import Spacer from "components/porter/Spacer";
|
|
|
import RadioSelector from "components/RadioSelector";
|
|
|
|
|
|
+import api from "shared/api";
|
|
|
+import { Context } from "shared/Context";
|
|
|
+import { type InviteType } from "shared/types";
|
|
|
+
|
|
|
+import PermissionGroup from "./PermissionGroup";
|
|
|
+import RoleModal from "./RoleModal";
|
|
|
+
|
|
|
type Props = {};
|
|
|
|
|
|
export type Collaborator = {
|
|
|
@@ -24,7 +29,7 @@ export type Collaborator = {
|
|
|
kind: string;
|
|
|
};
|
|
|
|
|
|
-const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
+const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
const {
|
|
|
currentProject,
|
|
|
setCurrentModal,
|
|
|
@@ -150,7 +155,9 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
}
|
|
|
)
|
|
|
.then(getData)
|
|
|
- .catch((err) => { console.log(err); });
|
|
|
+ .catch((err) => {
|
|
|
+ console.log(err);
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
const replaceInvite = (
|
|
|
@@ -164,15 +171,16 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
{ email: inviteEmail, kind },
|
|
|
{ id: currentProject.id }
|
|
|
)
|
|
|
- .then(async () =>
|
|
|
- await api.deleteInvite(
|
|
|
- "<token>",
|
|
|
- {},
|
|
|
- {
|
|
|
- id: currentProject.id,
|
|
|
- invId: inviteId,
|
|
|
- }
|
|
|
- )
|
|
|
+ .then(
|
|
|
+ async () =>
|
|
|
+ await api.deleteInvite(
|
|
|
+ "<token>",
|
|
|
+ {},
|
|
|
+ {
|
|
|
+ id: currentProject.id,
|
|
|
+ invId: inviteId,
|
|
|
+ }
|
|
|
+ )
|
|
|
)
|
|
|
.then(getData)
|
|
|
.catch((err) => {
|
|
|
@@ -188,7 +196,8 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
const trimmedEmail = email.trim();
|
|
|
setEmail(trimmedEmail);
|
|
|
|
|
|
- const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
|
+ const regex =
|
|
|
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
|
if (!regex.test(trimmedEmail.toLowerCase())) {
|
|
|
setIsInvalidEmail(true);
|
|
|
return;
|
|
|
@@ -222,13 +231,15 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
};
|
|
|
|
|
|
const columns = useMemo<
|
|
|
- Array<Column<{
|
|
|
- email: string;
|
|
|
- id: number;
|
|
|
- status: string;
|
|
|
- invite_link: string;
|
|
|
- kind: string;
|
|
|
- }>>
|
|
|
+ Array<
|
|
|
+ Column<{
|
|
|
+ email: string;
|
|
|
+ id: number;
|
|
|
+ status: string;
|
|
|
+ invite_link: string;
|
|
|
+ kind: string;
|
|
|
+ }>
|
|
|
+ >
|
|
|
>(
|
|
|
() => [
|
|
|
{
|
|
|
@@ -258,13 +269,13 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
if (row.values.status === "expired") {
|
|
|
return (
|
|
|
<NewLinkButton
|
|
|
- onClick={() =>
|
|
|
- { replaceInvite(
|
|
|
+ onClick={() => {
|
|
|
+ replaceInvite(
|
|
|
row.values.email,
|
|
|
row.original.id,
|
|
|
row.values.kind
|
|
|
- ); }
|
|
|
- }
|
|
|
+ );
|
|
|
+ }}
|
|
|
>
|
|
|
<u>Generate a new link</u>
|
|
|
</NewLinkButton>
|
|
|
@@ -298,13 +309,17 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
<Flex>
|
|
|
<SettingsButton
|
|
|
invis={row.original.currentUser}
|
|
|
- onClick={() => { openEditModal(row.original); }}
|
|
|
+ onClick={() => {
|
|
|
+ openEditModal(row.original);
|
|
|
+ }}
|
|
|
>
|
|
|
<i className="material-icons">more_vert</i>
|
|
|
</SettingsButton>
|
|
|
<DeleteButton
|
|
|
invis={row.original.currentUser}
|
|
|
- onClick={() => { removeCollaborator(row.original.id); }}
|
|
|
+ onClick={() => {
|
|
|
+ removeCollaborator(row.original.id);
|
|
|
+ }}
|
|
|
>
|
|
|
<i className="material-icons">delete</i>
|
|
|
</DeleteButton>
|
|
|
@@ -315,13 +330,17 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
<Flex>
|
|
|
<SettingsButton
|
|
|
invis={row.original.currentUser}
|
|
|
- onClick={() => { openEditModal(row.original); }}
|
|
|
+ onClick={() => {
|
|
|
+ openEditModal(row.original);
|
|
|
+ }}
|
|
|
>
|
|
|
<i className="material-icons">more_vert</i>
|
|
|
</SettingsButton>
|
|
|
<DeleteButton
|
|
|
invis={row.original.currentUser}
|
|
|
- onClick={() => { deleteInvite(row.original.id); }}
|
|
|
+ onClick={() => {
|
|
|
+ deleteInvite(row.original.id);
|
|
|
+ }}
|
|
|
>
|
|
|
<i className="material-icons">delete</i>
|
|
|
</DeleteButton>
|
|
|
@@ -338,7 +357,8 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
inviteList.sort((a: any, b: any) => (a.email > b.email ? 1 : -1));
|
|
|
inviteList.sort((a: any, b: any) => (a.accepted > b.accepted ? 1 : -1));
|
|
|
const buildInviteLink = (token: string) => `
|
|
|
- ${isHTTPS ? "https://" : ""}${window.location.host}/api/projects/${currentProject.id
|
|
|
+ ${isHTTPS ? "https://" : ""}${window.location.host}/api/projects/${
|
|
|
+ currentProject.id
|
|
|
}/invites/${token}
|
|
|
`;
|
|
|
|
|
|
@@ -404,18 +424,36 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
return (
|
|
|
<>
|
|
|
<>
|
|
|
+ {currentProject?.advanced_rbac_enabled && (
|
|
|
+ <>
|
|
|
+ <Heading isAtTop={true}>Permission groups</Heading>
|
|
|
+ <Helper>Manage permission groups for your organization.</Helper>
|
|
|
+ <PermissionGroup>Admin</PermissionGroup>
|
|
|
+ <PermissionGroup>Developer</PermissionGroup>
|
|
|
+ <PermissionGroup>Viewer</PermissionGroup>
|
|
|
+ <Spacer y={0.4} />
|
|
|
+ <Button alt>
|
|
|
+ <I className="material-icons">add</I>
|
|
|
+ New group
|
|
|
+ </Button>
|
|
|
+ <Spacer y={1.7} />
|
|
|
+ <RoleModal />
|
|
|
+ </>
|
|
|
+ )}
|
|
|
<Heading isAtTop={true}>Share project</Heading>
|
|
|
<Helper>Generate a project invite for another user.</Helper>
|
|
|
<InputRowWrapper>
|
|
|
<InputRow
|
|
|
value={email}
|
|
|
type="text"
|
|
|
- setValue={(newEmail: string) => { setEmail(newEmail); }}
|
|
|
+ setValue={(newEmail: string) => {
|
|
|
+ setEmail(newEmail);
|
|
|
+ }}
|
|
|
width="100%"
|
|
|
placeholder="ex: mrp@porter.run"
|
|
|
/>
|
|
|
</InputRowWrapper>
|
|
|
- <Helper>Specify a role for this user.</Helper>
|
|
|
+ <Helper>Specify a project role for this user.</Helper>
|
|
|
<RoleSelectorWrapper>
|
|
|
<RadioSelector
|
|
|
selected={role}
|
|
|
@@ -424,7 +462,12 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
/>
|
|
|
</RoleSelectorWrapper>
|
|
|
<ButtonWrapper>
|
|
|
- <InviteButton disabled={!hasSeats} onClick={() => { validateEmail(); }}>
|
|
|
+ <InviteButton
|
|
|
+ disabled={!hasSeats}
|
|
|
+ onClick={() => {
|
|
|
+ validateEmail();
|
|
|
+ }}
|
|
|
+ >
|
|
|
Create invite
|
|
|
</InviteButton>
|
|
|
{isInvalidEmail && (
|
|
|
@@ -462,6 +505,24 @@ const InvitePage: React.FunctionComponent<Props> = ({ }) => {
|
|
|
|
|
|
export default InvitePage;
|
|
|
|
|
|
+const I = styled.i`
|
|
|
+ margin-right: 10px;
|
|
|
+ font-size: 18px;
|
|
|
+`;
|
|
|
+
|
|
|
+const Group = styled.div`
|
|
|
+ display: inline-block;
|
|
|
+ border-radius: 5px;
|
|
|
+ margin-right: 10px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 13px;
|
|
|
+ padding: 7px 10px;
|
|
|
+ background: ${({ theme }) => theme.clickable.bg};
|
|
|
+ border: 1px solid ${({ theme }) => theme.border};
|
|
|
+ width: fit-content;
|
|
|
+ margin-bottom: 15px;
|
|
|
+`;
|
|
|
+
|
|
|
const Flex = styled.div`
|
|
|
display: flex;
|
|
|
align-items: center;
|