[next] Admin Configure (#2076)

* feat: Add RadioButton and CheckBox

* feat: configure facebook and google auth

* feat: configure sso, localAuth and displayName + some tests

* test: add integration tests for configure auth

* test: more integration tests

* feat: add oidc support

* test: add oidc integration test

* feat: generate sso key initially

* fix: import fetchQuery from correct package

* fix: admin url

* fix: set timezone to utc when testing

* refactor: improve route config

* fix: remove obsolete line

* fix: clientMutationId increment

* fix: oidc only create when enabled

* fix: copy

* test: update snapshots

* feat: fixed graphql logging extension

* Update src/locales/en-US/admin.ftl

Co-Authored-By: cvle <vinh@wikiwi.io>

* Apply suggestions from code review

Co-Authored-By: cvle <vinh@wikiwi.io>

* test: update snapshots

* fix: change Local Auth to Email Authentication

* fix: copy updates
This commit is contained in:
Kiwi
2018-11-19 22:47:32 +00:00
committed by Wyatt Johnson
parent 3f949b3712
commit 05350d651f
215 changed files with 7774 additions and 939 deletions
@@ -1,33 +1,9 @@
import * as React from "react";
import {
hoistStatics,
InferableComponentEnhancer,
wrapDisplayName,
} from "recompose";
import { createContextHOC } from "talk-framework/helpers";
import { TalkContext, TalkContextConsumer } from "./TalkContext";
/**
* withContext is a HOC wrapper around `TalkContextConsumer`.
* `propsCallback` must be provided which accepts the `TalkContext`
* and returns the props the should be injected.
*/
function withContext<T>(
propsCallback: (context: TalkContext) => T
): InferableComponentEnhancer<T> {
return hoistStatics<T>(
<U extends T>(WrappedComponent: React.ComponentType<U>) => {
const Component: React.StatelessComponent<any> = props => (
<TalkContextConsumer>
{context => (
<WrappedComponent {...props} {...propsCallback(context)} />
)}
</TalkContextConsumer>
);
Component.displayName = wrapDisplayName(WrappedComponent, "withContext");
return Component;
}
);
}
const withContext = createContextHOC<TalkContext>(
"withContext",
TalkContextConsumer
);
export default withContext;
@@ -0,0 +1,17 @@
import { FluentBundle } from "fluent/compat";
export default function getMessage(
bundles: FluentBundle[],
key: string,
defaultTo = ""
): string {
const res = bundles.reduce((val, bundle) => {
const got = bundle.getMessage(key);
if (!got && process.env.NODE_ENV !== "production") {
// tslint:disable-next-line:no-console
console.warn(`Translation ${key} was not found for ${bundle.locales}`);
}
return val || got;
}, "");
return res || defaultTo;
}
@@ -1,3 +1,4 @@
export { default as generateBundles } from "./generateBundles";
export { default as negotiateLanguages } from "./negotiateLanguages";
export { BundledLocales, LoadableLocales, LocalesData } from "./locales";
export { default as getMessage } from "./getMessage";
@@ -3,6 +3,8 @@ import { Environment, MutationConfig, OperationBase } from "relay-runtime";
import { Omit } from "talk-framework/types";
import extractPayload from "./extractPayload";
/**
* Like `MutationConfig` but omits `onCompleted` and `onError`
* because we are going to use a Promise API.
@@ -12,15 +14,6 @@ export type MutationPromiseConfig<T extends OperationBase> = Omit<
"onCompleted" | "onError"
>;
// Extract the payload from the response,
function getPayload(response: { [key: string]: any }): any {
const keys = Object.keys(response);
if (keys.length !== 1) {
return response;
}
return response[keys[0]];
}
/**
* Normalizes response and error from `commitMutationPromise`.
* Meaning `response` will directly contain the payload
@@ -33,7 +26,7 @@ export async function commitMutationPromiseNormalized<T extends OperationBase>(
): Promise<T["response"][keyof T["response"]]> {
try {
const response = await commitMutationPromise(environment, config);
return getPayload(response);
return extractPayload(response);
} catch (e) {
throw e;
}
@@ -57,7 +50,7 @@ export function commitMutationPromise<T extends OperationBase>(
reject(errors);
return;
}
resolve(getPayload(response));
resolve(extractPayload(response));
},
onError: error => {
reject(error);
@@ -0,0 +1,56 @@
import * as React from "react";
import {
compose,
hoistStatics,
InferableComponentEnhancer,
wrapDisplayName,
} from "recompose";
import { Environment } from "relay-runtime";
import { TalkContext, withContext } from "../bootstrap";
/**
* createFetchContainer creates a HOC that
* injects a property with the name specified in `propName`
* and the signature (input: I) => Promise<R>. Calling
* this will start a one off query.
*/
function createFetchContainer<T extends string, V, R>(
propName: T,
fetch: (
environment: Environment,
variables: V,
context: TalkContext
) => Promise<R>
): InferableComponentEnhancer<{ [P in T]: (variables: V) => Promise<R> }> {
return compose(
withContext(context => ({ context })),
hoistStatics((BaseComponent: React.ComponentType<any>) => {
class CreateFetchContainer extends React.Component<any> {
public static displayName = wrapDisplayName(
BaseComponent,
"createFetchContainer"
);
private fetch = (variables: V) => {
return fetch(
this.props.context.relayEnvironment,
variables,
this.props.context
);
};
public render() {
const { context: _, ...rest } = this.props;
const inject = {
[propName]: this.fetch,
};
return <BaseComponent {...rest} {...inject} />;
}
}
return CreateFetchContainer as React.ComponentType<any>;
})
);
}
export default createFetchContainer;
@@ -0,0 +1,8 @@
// Extract the payload from the response,
export default function extractPayload(response: { [key: string]: any }): any {
const keys = Object.keys(response);
if (keys.length !== 1) {
return response;
}
return response[keys[0]];
}
@@ -0,0 +1,24 @@
import { fetchQuery as relayFetchQuery } from "react-relay";
import {
CacheConfig,
Environment,
GraphQLTaggedNode,
Variables,
} from "relay-runtime";
import extractPayload from "./extractPayload";
export default async function fetchQuery<R = any>(
environment: Environment,
taggedNode: GraphQLTaggedNode,
variables: Variables,
cacheConfig?: CacheConfig
): Promise<R> {
const result = await relayFetchQuery(
environment,
taggedNode,
variables,
cacheConfig
);
return extractPayload(result);
}
@@ -6,6 +6,7 @@ export * from "./withLocalStateContainer";
export { default as QueryRenderer } from "./QueryRenderer";
export * from "./QueryRenderer";
export { default as createMutationContainer } from "./createMutationContainer";
export { default as createFetchContainer } from "./createFetchContainer";
export { default as createAndRetain } from "./createAndRetain";
export { default as wrapFetchWithLogger } from "./wrapFetchWithLogger";
export {
@@ -17,3 +18,4 @@ export {
default as commitLocalUpdatePromisified,
} from "./commitLocalUpdatePromisified";
export { initLocalBaseState, setAuthTokenInLocalState } from "./localState";
export { default as fetchQuery } from "./fetchQuery";
+2 -2
View File
@@ -10,7 +10,7 @@ import {
VALIDATION_REQUIRED,
} from "./messages";
type Validator<T, V> = (v: T, values: V) => ReactNode;
export type Validator<T = any, V = any> = (v: T, values: V) => ReactNode;
/**
* createValidator returns a Validator that returns given `error` when `condition` is falsey.
@@ -62,7 +62,7 @@ export const validateUsernameCharacters = createValidator(
*/
export const validateURL = createValidator(
v =>
/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/.test(
/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,5}(:[0-9]{1,5})?(\/.*)?$/.test(
v
),
INVALID_URL()