mirror of
https://github.com/wassname/talk.git
synced 2026-09-10 12:43:11 +08:00
[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:
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
)}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user