[CORL-183] Invite Users (#2349)

* feat: initial UI impl

* feat: attach react devtools hook in development

* feat: working mutations

* feat: polished the invite modal with mutation

Co-authored-by: Vinh <vinh@wikiwi.io>

* feat: added check

* feat: improve the invite server impl

* feat: admin invite interface improvements

* fix: update tests

* feat: moved invite UI to admin

* fix: include email enabled as condition for invite

* feat: added admin tests

* feat: added tests for invite complete flow

* fix: review
This commit is contained in:
Wyatt Johnson
2019-06-28 22:51:42 +00:00
committed by GitHub
co-authored by Vinh
parent 76033118e5
commit 51b142035e
96 changed files with 3647 additions and 847 deletions
@@ -0,0 +1,5 @@
export default function clearHash() {
if (window.location.hash) {
window.history.replaceState(null, document.title, location.pathname);
}
}
@@ -0,0 +1,14 @@
import { parseQuery } from "coral-common/utils";
export default function getParamsFromHash() {
try {
const params = window.location.hash
? parseQuery(window.location.hash.substr(1))
: {};
return params;
} catch (err) {
window.console.error(err);
return {};
}
}
@@ -1,15 +1,12 @@
import { parseQuery } from "coral-common/utils";
import clearHash from "./clearHash";
import getParamsFromHash from "./getParamsFromHash";
export default function getParamsFromHashAndClearIt() {
try {
const params = window.location.hash
? parseQuery(window.location.hash.substr(1))
: {};
const params = getParamsFromHash();
// Remove hash with token.
if (window.location.hash) {
window.history.replaceState(null, document.title, location.pathname);
}
// Clear the hash contents.
clearHash();
return params;
} catch (err) {
@@ -7,4 +7,6 @@ export { default as redirectOAuth2 } from "./redirectOAuth2";
export {
default as getParamsFromHashAndClearIt,
} from "./getParamsFromHashAndClearIt";
export { default as getParamsFromHash } from "./getParamsFromHash";
export { default as clearHash } from "./clearHash";
export { default as roleIsAtLeast } from "./roleIsAtLeast";
+1
View File
@@ -2,3 +2,4 @@ export { default as useEffectAfterMount } from "./useEffectAfterMount";
export { default as usePrevious } from "./usePrevious";
export { default as useEffectWhenChanged } from "./useEffectWhenChanged";
export { default as useUUID } from "./useUUID";
export { default as useToken } from "./useToken";
@@ -0,0 +1,57 @@
import { useEffect, useState } from "react";
import { InvalidRequestError } from "coral-framework/lib/errors";
import { useFetch } from "coral-framework/lib/relay";
import { Fetch } from "coral-framework/lib/relay/fetch";
type TokenState = "VALID" | "INVALID" | "MISSING" | "UNKNOWN" | "UNCHECKED";
interface Variables {
token: string;
}
export default function useToken(
fetcher: Fetch<string, Variables, Promise<void>>,
token: string | undefined
): [TokenState, string] {
const checkToken = useFetch(fetcher);
const [tokenState, setTokenState] = useState<TokenState>("UNCHECKED");
const [tokenError, setTokenError] = useState<string>("");
useEffect(() => {
let disposed = false;
function handleTokenState(state: TokenState, error?: Error) {
if (disposed) {
return;
}
setTokenState(state);
if (error) {
setTokenError(error.message);
}
}
if (token) {
checkToken({ token })
.then(() => {
handleTokenState("VALID");
})
.catch(error => {
if (error instanceof InvalidRequestError) {
handleTokenState("INVALID", error);
} else {
handleTokenState("UNKNOWN", error);
}
});
} else {
handleTokenState("MISSING");
}
return () => {
disposed = true;
};
}, [token]);
return [tokenState, tokenError];
}
@@ -1,3 +1,4 @@
import cn from "classnames";
import React, { FunctionComponent } from "react";
import styles from "./ExternalLink.css";
@@ -5,7 +6,8 @@ import styles from "./ExternalLink.css";
const ExternalLink: FunctionComponent<{
href?: string;
children?: string;
}> = ({ href, children }) => (
className?: string;
}> = ({ href, children, className }) => (
<a
href={href || children}
target="_blank"
@@ -15,7 +17,7 @@ const ExternalLink: FunctionComponent<{
* https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/
*/
rel="noopener noreferrer"
className={styles.root}
className={cn(styles.root, className)}
>
{children}
</a>
+1
View File
@@ -4,6 +4,7 @@ export interface JWT {
typ: string;
};
payload: {
[_: string]: any;
iat?: number;
exp?: number;
iss?: string;
@@ -1,4 +1,4 @@
export default function createAccessToken() {
export default function createAccessToken(payload = {}) {
return `${btoa(
JSON.stringify({
alg: "HS256",
@@ -7,6 +7,7 @@ export default function createAccessToken() {
)}.${btoa(
JSON.stringify({
jti: "31b26591-4e9a-4388-a7ff-e1bdc5d97cce",
...payload,
})
)}`;
}