mirror of
https://github.com/wassname/talk.git
synced 2026-07-16 11:22:16 +08:00
[CORL-156] Manage user suspension status (#2419)
* wire up suspension modal * show user suspended callout on stream * prevent comment actions for suspended users * set default to 3 hour suspension * add message field to suspension * allow custom message for suspension * show suspend success modal * fix type errors * remove unused code * update styles * fix fixture for streams * add suspension ui tests * fix types * remove warnings? * remove snapshot * fix merge conflicts * allow custom email message when banning users * fix typo * correct message type * use final-form in suspend modal * refactor suspend modal to use final-form * refactor ban modal to use final-form * refactor userStatusChangeContainer to use useCallback * feat: improve translated form * fix: addressed issue caused by i18n refactor * update getMessage to accept arguments, remove format method * translate suspend info * change hour format * make message a mandatory input for suspend and ban user * fix types in user table * Revert "fix types in user table" This reverts commit d396e90b88bb1bd354c5cdbdd72b6d8f1ab72929. * fix types for user table * fix: small review tweaks
This commit is contained in:
committed by
Wyatt Johnson
parent
4e548e8fbf
commit
5df2de6afc
@@ -1,6 +1,7 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { ChangeEvent, Component } from "react";
|
||||
|
||||
import { UNIT } from "coral-framework/lib/i18n";
|
||||
import { Flex, Option, SelectField, TextField } from "coral-ui/components";
|
||||
|
||||
import styles from "./DurationField.css";
|
||||
@@ -9,22 +10,16 @@ import styles from "./DurationField.css";
|
||||
* DURATION_UNIT are units that can be used in the
|
||||
* DurationField components.
|
||||
*/
|
||||
export enum DURATION_UNIT {
|
||||
SECONDS = 1,
|
||||
MINUTES = 60,
|
||||
HOURS = 3600,
|
||||
DAYS = 86400,
|
||||
WEEKS = 604800,
|
||||
}
|
||||
export const DURATION_UNIT = UNIT;
|
||||
|
||||
type UnitElementCallback = (
|
||||
currentValue: DURATION_UNIT,
|
||||
currentValue: UNIT,
|
||||
unitValue: string
|
||||
) => React.ReactElement<any>;
|
||||
|
||||
// This is used to render the Option elements to inlcude in the select field.
|
||||
const unitElementMap: Record<DURATION_UNIT, UnitElementCallback> = {
|
||||
[DURATION_UNIT.SECONDS]: (currentValue, unitValue) => (
|
||||
const unitElementMap: Record<UNIT, UnitElementCallback> = {
|
||||
[UNIT.SECONDS]: (currentValue, unitValue) => (
|
||||
<Localized
|
||||
id="framework-durationField-seconds"
|
||||
$value={currentValue}
|
||||
@@ -33,7 +28,7 @@ const unitElementMap: Record<DURATION_UNIT, UnitElementCallback> = {
|
||||
<Option value={unitValue}>Seconds</Option>
|
||||
</Localized>
|
||||
),
|
||||
[DURATION_UNIT.MINUTES]: (currentValue, unitValue) => (
|
||||
[UNIT.MINUTES]: (currentValue, unitValue) => (
|
||||
<Localized
|
||||
id="framework-durationField-minutes"
|
||||
$value={currentValue}
|
||||
@@ -42,7 +37,7 @@ const unitElementMap: Record<DURATION_UNIT, UnitElementCallback> = {
|
||||
<Option value={unitValue}>Minutes</Option>
|
||||
</Localized>
|
||||
),
|
||||
[DURATION_UNIT.HOURS]: (currentValue, unitValue) => (
|
||||
[UNIT.HOURS]: (currentValue, unitValue) => (
|
||||
<Localized
|
||||
id="framework-durationField-hours"
|
||||
$value={currentValue}
|
||||
@@ -51,7 +46,7 @@ const unitElementMap: Record<DURATION_UNIT, UnitElementCallback> = {
|
||||
<Option value={unitValue}>Hours</Option>
|
||||
</Localized>
|
||||
),
|
||||
[DURATION_UNIT.DAYS]: (currentValue, unitValue) => (
|
||||
[UNIT.DAYS]: (currentValue, unitValue) => (
|
||||
<Localized
|
||||
id="framework-durationField-days"
|
||||
$value={currentValue}
|
||||
@@ -60,7 +55,7 @@ const unitElementMap: Record<DURATION_UNIT, UnitElementCallback> = {
|
||||
<Option value={unitValue}>Days</Option>
|
||||
</Localized>
|
||||
),
|
||||
[DURATION_UNIT.WEEKS]: (currentValue, unitValue) => (
|
||||
[UNIT.WEEKS]: (currentValue, unitValue) => (
|
||||
<Localized
|
||||
id="framework-durationField-weeks"
|
||||
$value={currentValue}
|
||||
@@ -77,16 +72,16 @@ interface Props {
|
||||
disabled: boolean;
|
||||
onChange: (v: string) => void;
|
||||
/** Specifiy units to include */
|
||||
units?: ReadonlyArray<DURATION_UNIT>;
|
||||
units?: ReadonlyArray<UNIT>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
/** Current value */
|
||||
value: string;
|
||||
/** Current unit */
|
||||
unit?: DURATION_UNIT;
|
||||
unit?: UNIT;
|
||||
/** All available units */
|
||||
units: ReadonlyArray<DURATION_UNIT>;
|
||||
units: ReadonlyArray<UNIT>;
|
||||
/**
|
||||
* Element callbacks to generate the rendered
|
||||
* Option element for the select field
|
||||
@@ -100,11 +95,7 @@ interface State {
|
||||
* @param units The units that we use.
|
||||
* @param unit The current value if any otherwise the best matching unit will be used.
|
||||
*/
|
||||
function valueToState(
|
||||
value: string,
|
||||
units: ReadonlyArray<DURATION_UNIT>,
|
||||
unit?: DURATION_UNIT
|
||||
) {
|
||||
function valueToState(value: string, units: ReadonlyArray<UNIT>, unit?: UNIT) {
|
||||
const parsed = parseInt(value, 10);
|
||||
|
||||
// If value was a valid number..
|
||||
@@ -147,7 +138,7 @@ function stateToValue(state: State) {
|
||||
*/
|
||||
class DurationField extends Component<Props, State> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
units: [DURATION_UNIT.HOURS, DURATION_UNIT.DAYS, DURATION_UNIT.WEEKS],
|
||||
units: [UNIT.HOURS, UNIT.DAYS, UNIT.WEEKS],
|
||||
};
|
||||
|
||||
public state: State = valueToState(this.props.value, this.props.units!);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { FluentBundle } from "fluent/compat";
|
||||
|
||||
export default function format(
|
||||
bundles: FluentBundle[],
|
||||
key: string,
|
||||
args?: object
|
||||
): string {
|
||||
const res = bundles.reduce((val, bundle) => {
|
||||
const message = bundle.getMessage(key);
|
||||
const got = bundle.format(message, args);
|
||||
return val || got;
|
||||
}, "");
|
||||
if (res && Array.isArray(res)) {
|
||||
return res.join("");
|
||||
}
|
||||
|
||||
return res || "";
|
||||
}
|
||||
@@ -1,17 +1,21 @@
|
||||
import { FluentBundle } from "fluent/compat";
|
||||
|
||||
export default function getMessage(
|
||||
export default function getMessage<T extends {}>(
|
||||
bundles: FluentBundle[],
|
||||
key: string,
|
||||
defaultTo = ""
|
||||
defaultTo: string,
|
||||
args?: T
|
||||
): string {
|
||||
const res = bundles.reduce((val, bundle) => {
|
||||
const got = bundle.getMessage(key);
|
||||
if (!got && process.env.NODE_ENV !== "production") {
|
||||
const message = bundle.getMessage(key);
|
||||
if (!message && process.env.NODE_ENV !== "production") {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.warn(`Translation ${key} was not found for ${bundle.locales}`);
|
||||
}
|
||||
return val || got;
|
||||
if (!args) {
|
||||
return val || message;
|
||||
}
|
||||
return val || bundle.format(message, args);
|
||||
}, "");
|
||||
if (res && Array.isArray(res)) {
|
||||
return res.join("");
|
||||
|
||||
@@ -3,3 +3,4 @@ export { default as negotiateLanguages } from "./negotiateLanguages";
|
||||
export { BundledLocales, LoadableLocales, LocalesData } from "./locales";
|
||||
export { default as getMessage } from "./getMessage";
|
||||
export { default as withGetMessage, GetMessage } from "./withGetMessage";
|
||||
export { default as reduceSeconds, UNIT, ScaledUnit } from "./reduceSeconds";
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* UNIT are units that can be used in the
|
||||
* DurationField components.
|
||||
*/
|
||||
export enum UNIT {
|
||||
SECONDS = 1,
|
||||
MINUTES = 60,
|
||||
HOURS = 3600,
|
||||
DAYS = 86400,
|
||||
WEEKS = 604800,
|
||||
}
|
||||
|
||||
export const UNIT_MAP = {
|
||||
[UNIT.SECONDS]: "second",
|
||||
[UNIT.MINUTES]: "minute",
|
||||
[UNIT.HOURS]: "hour",
|
||||
[UNIT.DAYS]: "day",
|
||||
[UNIT.WEEKS]: "week",
|
||||
};
|
||||
|
||||
export const DEFAULT_UNITS = [
|
||||
UNIT.WEEKS,
|
||||
UNIT.DAYS,
|
||||
UNIT.HOURS,
|
||||
UNIT.MINUTES,
|
||||
UNIT.SECONDS,
|
||||
];
|
||||
|
||||
type ValueOf<T> = T[keyof T];
|
||||
|
||||
export interface ScaledUnit {
|
||||
original: number;
|
||||
value: string;
|
||||
unit: ValueOf<typeof UNIT_MAP>;
|
||||
scaled: number;
|
||||
}
|
||||
|
||||
export default function reduceSeconds(
|
||||
value: number,
|
||||
units: UNIT[] = DEFAULT_UNITS
|
||||
): ScaledUnit {
|
||||
// Find the largest match for the smallest number.
|
||||
const unit: keyof typeof UNIT_MAP =
|
||||
units.find(compare => value >= compare) || UNIT.SECONDS;
|
||||
|
||||
// Scale the value to the unit.
|
||||
const scaled = Math.round((value / unit) * 100) / 100;
|
||||
|
||||
return {
|
||||
original: value,
|
||||
value: value.toString(),
|
||||
scaled,
|
||||
unit: UNIT_MAP[unit],
|
||||
};
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { DefaultingInferableComponentEnhancer, hoistStatics } from "recompose";
|
||||
import { CoralContext, withContext } from "../bootstrap";
|
||||
import getMessage from "./getMessage";
|
||||
|
||||
export type GetMessage = (id: string, defaultTo?: string) => string;
|
||||
export type GetMessage = <T>(id: string, defaultTo: string, args?: T) => string;
|
||||
|
||||
interface InjectedProps {
|
||||
getMessage: GetMessage;
|
||||
@@ -35,8 +35,12 @@ const withGetMessage: DefaultingInferableComponentEnhancer<
|
||||
const Workaround = BaseComponent as React.ComponentType<InjectedProps>;
|
||||
|
||||
class WithGetMessage extends React.Component<Props> {
|
||||
private getMessage = (id: string, defaultTo?: string) => {
|
||||
return getMessage(this.props.localeBundles, id, defaultTo);
|
||||
private getMessage = <U extends {}>(
|
||||
id: string,
|
||||
defaultTo: string,
|
||||
args?: U
|
||||
): string => {
|
||||
return getMessage(this.props.localeBundles, id, defaultTo, args);
|
||||
};
|
||||
public render() {
|
||||
const { localeBundles: _, ...rest } = this.props;
|
||||
|
||||
Reference in New Issue
Block a user