|
@@ -18,44 +18,107 @@ import Heading from "components/values-form/Heading";
|
|
|
import CopyToClipboard from "components/CopyToClipboard";
|
|
import CopyToClipboard from "components/CopyToClipboard";
|
|
|
import { Column } from "react-table";
|
|
import { Column } from "react-table";
|
|
|
import Table from "components/Table";
|
|
import Table from "components/Table";
|
|
|
|
|
+import RadioSelector from "components/RadioSelector";
|
|
|
|
|
|
|
|
type Props = {};
|
|
type Props = {};
|
|
|
|
|
|
|
|
|
|
+export type Collaborator = {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ user_id: string;
|
|
|
|
|
+ project_id: string;
|
|
|
|
|
+ email: string;
|
|
|
|
|
+ kind: string;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
- const { currentProject } = useContext(Context);
|
|
|
|
|
|
|
+ const { currentProject, setCurrentModal, user } = useContext(Context);
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
const [invites, setInvites] = useState<Array<InviteType>>([]);
|
|
const [invites, setInvites] = useState<Array<InviteType>>([]);
|
|
|
const [email, setEmail] = useState("");
|
|
const [email, setEmail] = useState("");
|
|
|
|
|
+ const [role, setRole] = useState("developer");
|
|
|
|
|
+ const [roleList, setRoleList] = useState([]);
|
|
|
const [isInvalidEmail, setIsInvalidEmail] = useState(false);
|
|
const [isInvalidEmail, setIsInvalidEmail] = useState(false);
|
|
|
const [isHTTPS] = useState(() => window.location.protocol === "https:");
|
|
const [isHTTPS] = useState(() => window.location.protocol === "https:");
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
- getInviteData();
|
|
|
|
|
- }, []);
|
|
|
|
|
|
|
+ api
|
|
|
|
|
+ .getAvailableRoles("<token>", {}, { project_id: currentProject.id })
|
|
|
|
|
+ .then(({ data }: { data: string[] }) => {
|
|
|
|
|
+ const availableRoleList = data?.map((role) => ({
|
|
|
|
|
+ value: role,
|
|
|
|
|
+ label: capitalizeFirstLetter(role),
|
|
|
|
|
+ }));
|
|
|
|
|
+ setRoleList(availableRoleList);
|
|
|
|
|
+ setRole("developer");
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ getData();
|
|
|
|
|
+ }, [currentProject]);
|
|
|
|
|
+
|
|
|
|
|
+ const capitalizeFirstLetter = (string: string) => {
|
|
|
|
|
+ return string.charAt(0).toUpperCase() + string.slice(1);
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- const getInviteData = () => {
|
|
|
|
|
|
|
+ const getData = async () => {
|
|
|
setIsLoading(true);
|
|
setIsLoading(true);
|
|
|
-
|
|
|
|
|
- api
|
|
|
|
|
- .getInvites(
|
|
|
|
|
|
|
+ let invites = [];
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await api.getInvites(
|
|
|
"<token>",
|
|
"<token>",
|
|
|
{},
|
|
{},
|
|
|
{
|
|
{
|
|
|
id: currentProject.id,
|
|
id: currentProject.id,
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
- .then((res) => {
|
|
|
|
|
- setInvites(res.data);
|
|
|
|
|
- setIsLoading(false);
|
|
|
|
|
- })
|
|
|
|
|
- .catch((err) => console.log(err));
|
|
|
|
|
|
|
+ );
|
|
|
|
|
+ invites = response.data.filter((i: InviteType) => !i.accepted);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.log(err);
|
|
|
|
|
+ }
|
|
|
|
|
+ let collaborators: any = [];
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await api.getCollaborators(
|
|
|
|
|
+ "<token>",
|
|
|
|
|
+ {},
|
|
|
|
|
+ {
|
|
|
|
|
+ project_id: currentProject.id,
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+ collaborators = parseCollaboratorsResponse(response.data);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.log(err);
|
|
|
|
|
+ }
|
|
|
|
|
+ setInvites([...invites, ...collaborators]);
|
|
|
|
|
+ setIsLoading(false);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const parseCollaboratorsResponse = (
|
|
|
|
|
+ collaborators: Array<Collaborator>
|
|
|
|
|
+ ): Array<InviteType> => {
|
|
|
|
|
+ return (
|
|
|
|
|
+ collaborators
|
|
|
|
|
+ // Parse role id to number
|
|
|
|
|
+ .map((c) => ({ ...c, id: Number(c.id) }))
|
|
|
|
|
+ // Sort them so the owner will be first allways
|
|
|
|
|
+ .sort((curr, prev) => curr.id - prev.id)
|
|
|
|
|
+ // Remove the owner from list
|
|
|
|
|
+ .slice(1)
|
|
|
|
|
+ // Parse the remainings to InviteType
|
|
|
|
|
+ .map((c) => ({
|
|
|
|
|
+ email: c.email,
|
|
|
|
|
+ expired: false,
|
|
|
|
|
+ id: Number(c.user_id),
|
|
|
|
|
+ kind: c.kind,
|
|
|
|
|
+ accepted: true,
|
|
|
|
|
+ token: "",
|
|
|
|
|
+ }))
|
|
|
|
|
+ );
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const createInvite = () => {
|
|
const createInvite = () => {
|
|
|
api
|
|
api
|
|
|
- .createInvite("<token>", { email }, { id: currentProject.id })
|
|
|
|
|
|
|
+ .createInvite("<token>", { email, kind: role }, { id: currentProject.id })
|
|
|
.then(() => {
|
|
.then(() => {
|
|
|
- getInviteData();
|
|
|
|
|
|
|
+ getData();
|
|
|
setEmail("");
|
|
setEmail("");
|
|
|
})
|
|
})
|
|
|
.catch((err) => console.log(err));
|
|
.catch((err) => console.log(err));
|
|
@@ -71,15 +134,19 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
invId: inviteId,
|
|
invId: inviteId,
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
- .then(getInviteData)
|
|
|
|
|
|
|
+ .then(getData)
|
|
|
.catch((err) => console.log(err));
|
|
.catch((err) => console.log(err));
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const replaceInvite = (inviteEmail: string, inviteId: number) => {
|
|
|
|
|
|
|
+ const replaceInvite = (
|
|
|
|
|
+ inviteEmail: string,
|
|
|
|
|
+ inviteId: number,
|
|
|
|
|
+ kind: string
|
|
|
|
|
+ ) => {
|
|
|
api
|
|
api
|
|
|
.createInvite(
|
|
.createInvite(
|
|
|
"<token>",
|
|
"<token>",
|
|
|
- { email: inviteEmail },
|
|
|
|
|
|
|
+ { email: inviteEmail, kind },
|
|
|
{ id: currentProject.id }
|
|
{ id: currentProject.id }
|
|
|
)
|
|
)
|
|
|
.then(() =>
|
|
.then(() =>
|
|
@@ -92,7 +159,7 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
)
|
|
)
|
|
|
- .then(getInviteData)
|
|
|
|
|
|
|
+ .then(getData)
|
|
|
.catch((err) => console.log(err));
|
|
.catch((err) => console.log(err));
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -107,12 +174,37 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
createInvite();
|
|
createInvite();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ const openEditModal = (user: any) => {
|
|
|
|
|
+ if (setCurrentModal) {
|
|
|
|
|
+ console.log(user);
|
|
|
|
|
+ setCurrentModal("EditInviteOrCollaboratorModal", {
|
|
|
|
|
+ user,
|
|
|
|
|
+ isInvite: user.status !== "accepted",
|
|
|
|
|
+ refetchCallerData: getData,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const removeCollaborator = (user_id: number) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ api.removeCollaborator(
|
|
|
|
|
+ "<token>",
|
|
|
|
|
+ {},
|
|
|
|
|
+ { project_id: currentProject.id, user_id }
|
|
|
|
|
+ );
|
|
|
|
|
+ getData();
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.log(error);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
const columns = useMemo<
|
|
const columns = useMemo<
|
|
|
Column<{
|
|
Column<{
|
|
|
email: string;
|
|
email: string;
|
|
|
id: number;
|
|
id: number;
|
|
|
status: string;
|
|
status: string;
|
|
|
invite_link: string;
|
|
invite_link: string;
|
|
|
|
|
+ kind: string;
|
|
|
}>[]
|
|
}>[]
|
|
|
>(
|
|
>(
|
|
|
() => [
|
|
() => [
|
|
@@ -120,6 +212,15 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
Header: "Mail address",
|
|
Header: "Mail address",
|
|
|
accessor: "email",
|
|
accessor: "email",
|
|
|
},
|
|
},
|
|
|
|
|
+ {
|
|
|
|
|
+ Header: "Role",
|
|
|
|
|
+ accessor: "kind",
|
|
|
|
|
+ Cell: ({ row }) => {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Status status={"accepted"}>{row.values.kind || "Admin"}</Status>
|
|
|
|
|
+ );
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
{
|
|
{
|
|
|
Header: "Status",
|
|
Header: "Status",
|
|
|
accessor: "status",
|
|
accessor: "status",
|
|
@@ -136,7 +237,13 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
if (row.values.status === "expired") {
|
|
if (row.values.status === "expired") {
|
|
|
return (
|
|
return (
|
|
|
<NewLinkButton
|
|
<NewLinkButton
|
|
|
- onClick={() => replaceInvite(row.values.email, row.values.id)}
|
|
|
|
|
|
|
+ onClick={() =>
|
|
|
|
|
+ replaceInvite(
|
|
|
|
|
+ row.values.email,
|
|
|
|
|
+ row.values.id,
|
|
|
|
|
+ row.values.kind
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
>
|
|
>
|
|
|
<u>Generate a new link</u>
|
|
<u>Generate a new link</u>
|
|
|
</NewLinkButton>
|
|
</NewLinkButton>
|
|
@@ -157,14 +264,37 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- accessor: "id",
|
|
|
|
|
- Cell: ({ row }) => {
|
|
|
|
|
|
|
+ id: "edit_action",
|
|
|
|
|
+ Cell: ({ row }: any) => {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <CopyButton
|
|
|
|
|
+ invis={row.original.currentUser}
|
|
|
|
|
+ onClick={() => openEditModal(row.original)}
|
|
|
|
|
+ >
|
|
|
|
|
+ Edit
|
|
|
|
|
+ </CopyButton>
|
|
|
|
|
+ );
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: "remove_invite_action",
|
|
|
|
|
+ Cell: ({ row }: any) => {
|
|
|
if (row.values.status === "accepted") {
|
|
if (row.values.status === "accepted") {
|
|
|
- return <CopyButton invis={true}>Remove</CopyButton>;
|
|
|
|
|
|
|
+ return (
|
|
|
|
|
+ <CopyButton
|
|
|
|
|
+ invis={row.original.currentUser}
|
|
|
|
|
+ onClick={() => removeCollaborator(row.original.id)}
|
|
|
|
|
+ >
|
|
|
|
|
+ Remove
|
|
|
|
|
+ </CopyButton>
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
return (
|
|
return (
|
|
|
<>
|
|
<>
|
|
|
- <CopyButton onClick={() => deleteInvite(row.values.id)}>
|
|
|
|
|
|
|
+ <CopyButton
|
|
|
|
|
+ invis={row.original.currentUser}
|
|
|
|
|
+ onClick={() => deleteInvite(row.original.id)}
|
|
|
|
|
+ >
|
|
|
Delete Invite
|
|
Delete Invite
|
|
|
</CopyButton>
|
|
</CopyButton>
|
|
|
</>
|
|
</>
|
|
@@ -187,10 +317,13 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
|
|
|
|
|
const mappedInviteList = inviteList.map(
|
|
const mappedInviteList = inviteList.map(
|
|
|
({ accepted, expired, token, ...rest }) => {
|
|
({ accepted, expired, token, ...rest }) => {
|
|
|
|
|
+ const currentUser: boolean = user.email === rest.email;
|
|
|
|
|
+ console.log(currentUser, user, rest);
|
|
|
if (accepted) {
|
|
if (accepted) {
|
|
|
return {
|
|
return {
|
|
|
status: "accepted",
|
|
status: "accepted",
|
|
|
invite_link: buildInviteLink(token),
|
|
invite_link: buildInviteLink(token),
|
|
|
|
|
+ currentUser,
|
|
|
...rest,
|
|
...rest,
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
@@ -199,6 +332,7 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
return {
|
|
return {
|
|
|
status: "expired",
|
|
status: "expired",
|
|
|
invite_link: buildInviteLink(token),
|
|
invite_link: buildInviteLink(token),
|
|
|
|
|
+ currentUser,
|
|
|
...rest,
|
|
...rest,
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
@@ -206,18 +340,19 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
return {
|
|
return {
|
|
|
status: "pending",
|
|
status: "pending",
|
|
|
invite_link: buildInviteLink(token),
|
|
invite_link: buildInviteLink(token),
|
|
|
|
|
+ currentUser,
|
|
|
...rest,
|
|
...rest,
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
return mappedInviteList || [];
|
|
return mappedInviteList || [];
|
|
|
- }, [invites, currentProject?.id, window?.location?.host, isHTTPS]);
|
|
|
|
|
|
|
+ }, [invites, currentProject?.id, window?.location?.host, isHTTPS, user?.id]);
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<>
|
|
<>
|
|
|
<Heading isAtTop={true}>Share Project</Heading>
|
|
<Heading isAtTop={true}>Share Project</Heading>
|
|
|
- <Helper>Generate a project invite for another admin user.</Helper>
|
|
|
|
|
|
|
+ <Helper>Generate a project invite for another user.</Helper>
|
|
|
<InputRowWrapper>
|
|
<InputRowWrapper>
|
|
|
<InputRow
|
|
<InputRow
|
|
|
value={email}
|
|
value={email}
|
|
@@ -227,6 +362,14 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
placeholder="ex: mrp@getporter.dev"
|
|
placeholder="ex: mrp@getporter.dev"
|
|
|
/>
|
|
/>
|
|
|
</InputRowWrapper>
|
|
</InputRowWrapper>
|
|
|
|
|
+ <Helper>Select the role the user will have.</Helper>
|
|
|
|
|
+ <RoleSelectorWrapper>
|
|
|
|
|
+ <RadioSelector
|
|
|
|
|
+ selected={role}
|
|
|
|
|
+ setSelected={setRole}
|
|
|
|
|
+ options={roleList}
|
|
|
|
|
+ />
|
|
|
|
|
+ </RoleSelectorWrapper>
|
|
|
<ButtonWrapper>
|
|
<ButtonWrapper>
|
|
|
<InviteButton disabled={false} onClick={() => validateEmail()}>
|
|
<InviteButton disabled={false} onClick={() => validateEmail()}>
|
|
|
Create Invite
|
|
Create Invite
|
|
@@ -259,6 +402,10 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
|
|
|
|
|
|
|
|
export default InvitePage;
|
|
export default InvitePage;
|
|
|
|
|
|
|
|
|
|
+const RoleSelectorWrapper = styled.div`
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+`;
|
|
|
|
|
+
|
|
|
const Placeholder = styled.div`
|
|
const Placeholder = styled.div`
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
height: 200px;
|
|
height: 200px;
|