@mui/material#InputAdornment TypeScript Examples

The following examples show how to use @mui/material#InputAdornment. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: util.tsx    From sapio-studio with Mozilla Public License 2.0 6 votes vote down vote up
export function PrettyAmountField(props: { amount: number }) {
    let amount = props.amount;
    const max_sats = useSelector(selectMaxSats);
    if (amount > max_sats) {
        amount /= 100_000_000;
        return (
            <TextField
                label="Amount (btc)"
                type="text"
                value={amount}
                variant="outlined"
                InputProps={{
                    readOnly: true,
                    endAdornment: (
                        <InputAdornment position="end">
                            <span
                                style={{ fontSize: 'larger', color: '#f2a900' }}
                            >
                                ₿
                            </span>
                        </InputAdornment>
                    ),
                }}
            />
        );
    } else {
        return (
            <TextField
                label="Amount (sats)"
                type="text"
                value={amount}
                variant="outlined"
                InputProps={{
                    readOnly: true,
                    endAdornment: (
                        <InputAdornment position="end">
                            <span
                                style={{ fontSize: 'larger', color: '#f2a900' }}
                            >
                                §
                            </span>
                        </InputAdornment>
                    ),
                }}
            />
        );
    }
}
Example #2
Source File: PasswordField.tsx    From frontend with MIT License 6 votes vote down vote up
export default function PasswordField({ name = 'password', ...props }: TextFieldProps) {
  const [showPassword, setShowPassword] = useState(false)
  const handleShowPassword = () => setShowPassword((show) => !show)
  return (
    <FormTextField
      name={name}
      {...props}
      type={showPassword ? 'text' : 'password'}
      autoComplete="current-password"
      label="auth:fields.password"
      InputProps={{
        endAdornment: (
          <InputAdornment position="end">
            <IconButton onClick={handleShowPassword} edge="end">
              {showPassword ? <Visibility /> : <VisibilityOff />}
            </IconButton>
          </InputAdornment>
        ),
      }}
    />
  )
}
Example #3
Source File: TextFieldWithTooltip.tsx    From Cromwell with MIT License 6 votes vote down vote up
export default function TextFieldWithTooltip(props: TextFieldProps & {
    tooltipText?: string;
    tooltipLink?: string;
}) {
    const history = useHistory();

    const openLink = () => {
        if (props.tooltipLink) {
            if (props.tooltipLink.startsWith('http')) {
                window.open(props.tooltipLink, '_blank');
            } else {
                history.push(props.tooltipLink);
            }
        }
    }

    return (
        <TextField
            InputProps={{
                endAdornment: (
                    <InputAdornment position="end">
                        <Tooltip title={props.tooltipText}>
                            <IconButton onClick={openLink}>
                                <HelpOutlineOutlined />
                            </IconButton>
                        </Tooltip>
                    </InputAdornment>
                ),
            }}
            variant="standard"
            {...props}
        />
    )
}
Example #4
Source File: Files.tsx    From NekoMaid with MIT License 6 votes vote down vote up
fileNameDialog = (title: string, dirPath: string) => dialog({
  title,
  content: lang.files.dialogContent,
  input: {
    error: true,
    helperText: lang.files.invalidName,
    validator: validFilename,
    InputProps: { startAdornment: <InputAdornment position='start'>{dirPath}/</InputAdornment> }
  }
})
Example #5
Source File: CredentialItem.tsx    From console with GNU Affero General Public License v3.0 6 votes vote down vote up
CredentialItem = ({
  label = "",
  value = "",
  classes = {},
}: {
  label: string;
  value: string;
  classes: any;
}) => {
  return (
    <div className={classes.container}>
      <div className={classes.inputLabel}>{label}:</div>
      <div className={classes.inputWithCopy}>
        <OutlinedInput
          value={value}
          readOnly
          endAdornment={
            <InputAdornment position="end">
              <CopyToClipboard text={value}>
                <BoxIconButton
                  aria-label="copy"
                  tooltip={"Copy"}
                  onClick={() => {}}
                  onMouseDown={() => {}}
                  edge="end"
                >
                  <CopyIcon />
                </BoxIconButton>
              </CopyToClipboard>
            </InputAdornment>
          }
        />
      </div>
    </div>
  );
}
Example #6
Source File: PasswordElement.tsx    From react-hook-form-mui with MIT License 6 votes vote down vote up
export default function PasswordElement({iconColor, ...props}: PasswordElementProps): JSX.Element {
  const [password, setPassword] = useState<boolean>(true)
  return (
    <TextFieldElement
      {...props}
      InputProps={{
        endAdornment: (
          <InputAdornment position={'end'}>
            <IconButton
              onMouseDown={(e: MouseEvent<HTMLButtonElement>) =>
                e.preventDefault()
              }
              onClick={() => setPassword(!password)}
              tabIndex={-1}
              color={iconColor ?? 'default'}
            >
              {password ? <Visibility /> : <VisibilityOff />}
            </IconButton>
          </InputAdornment>
        )
      }}
      type={password ? 'password' : 'text'}
    />
  )
}
Example #7
Source File: PasswordField.tsx    From Cromwell with MIT License 6 votes vote down vote up
PasswordField = (props: TextFieldProps) => {
  const [showPassword, setShowPassword] = useState(false);

  const handleClickShowPassword = () => {
    setShowPassword(!showPassword);
  }

  return (
    <TextField {...props}
      type={showPassword ? 'text' : 'password'}
      variant="standard"
      size="small"
      className={styles.textField}
      InputProps={{
        endAdornment: (
          <InputAdornment position="end">
            <IconButton
              aria-label="toggle password visibility"
              onClick={handleClickShowPassword}
              edge="end"
            >
              {showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
            </IconButton>
          </InputAdornment>
        ),
      }}
    />
  )
}
Example #8
Source File: index.tsx    From yearn-watch-legacy with GNU Affero General Public License v3.0 5 votes vote down vote up
SearchProtocolInput = (props: SearchProtocolInputProps) => {
    const [searchText, setSearchText] = useState('');
    const [isSearching, setIsSearching] = useState(false);
    const classes = useStyles();

    const handleOnChange = (event: ChangeEvent) => {
        event.preventDefault();
        const value = (event.target as HTMLInputElement).value;
        setSearchText(value);
    };
    const doSearch = () => {
        props.onSearch(searchText.trim()).then(() => {
            setIsSearching(false);
            setSearchText('');
        });
    };
    const handleClickSearchIcon = (event: MouseEvent<HTMLElement>) => {
        event.preventDefault();
        setIsSearching(true);
        doSearch();
    };
    const handleClickSearch = (event: FormEvent<HTMLFormElement>) => {
        event.preventDefault();
        setIsSearching(true);
        doSearch();
    };
    const renderSearchingLabel = () => {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let render: any;
        if (isSearching) {
            render = 'Searching items...';
        } else {
            render = ``;
        }
        return render;
    };
    const isSearchDisabled =
        isSearching || searchText === undefined || searchText.trim() === '';

    return (
        <Container maxWidth="lg">
            <form className={classes.root} onSubmit={handleClickSearch}>
                <TextField
                    className={classes.searchInput}
                    id="outlined-basic"
                    variant="outlined"
                    type="text"
                    value={searchText}
                    onChange={handleOnChange}
                    disabled={isSearching}
                    placeholder="Type your terms (protocol name -ex: maker, convex-) and click on the search icon or press enter."
                    InputProps={{
                        endAdornment: (
                            <InputAdornment position="end">
                                <IconButton
                                    aria-label="search"
                                    disabled={isSearchDisabled}
                                    onClick={handleClickSearchIcon}
                                    size="large"
                                >
                                    <SearchIcon />
                                </IconButton>
                            </InputAdornment>
                        ),
                    }}
                />
            </form>
            <Container maxWidth="lg" className={classes.resultText}>
                {renderSearchingLabel()}
            </Container>
        </Container>
    );
}
Example #9
Source File: QuantityField.tsx    From Cromwell with MIT License 5 votes vote down vote up
/** @internal */
export function QuantityField(props: {
  value: number;
  onChange: (value: number, event?: any) => any;
  className?: string;
  style?: React.CSSProperties;
}) {
  const { value, onChange, className, style } = props;
  return (
    <TextField
      variant="outlined"
      size="small"
      className={clsx(styles.QuantityField, className)}
      style={style}
      value={value}
      onChange={(e) => {
        const val = parseInt(e.target.value);
        if (val && !isNaN(val) && val > 0) onChange(val, e)
      }}
      InputProps={{
        startAdornment: (<InputAdornment position="start" style={{ margin: 0 }}>
          <IconButton
            aria-label="Decrease amount"
            className={styles.controlButton}
            style={{ width: '44px' }}
            onClick={() => {
              if (value > 1) {
                onChange(value - 1)
              }
            }}
          ><RemoveIcon /></IconButton>
        </InputAdornment>),
        endAdornment: (<InputAdornment position="end" style={{ margin: 0 }}>
          <IconButton
            aria-label="Increase amount"
            className={styles.controlButton}
            style={{ width: '44px' }}
            onClick={() => {
              onChange(value + 1)
            }}
          ><AddIcon /></IconButton>
        </InputAdornment>),
      }}
    />
  )
}
Example #10
Source File: Select.tsx    From Cromwell with MIT License 5 votes vote down vote up
export function Select(props: {
    options?: ({
        value: string | number | undefined;
        label: string;
    } | string | number | undefined)[];
    selectStyle?: React.CSSProperties;
    selectClassName?: string;
    tooltipText?: string;
    tooltipLink?: string;
} & SelectProps<string | number>) {
    const history = useHistory();

    const openLink = () => {
        if (props.tooltipLink) {
            if (props.tooltipLink.startsWith('http')) {
                window.open(props.tooltipLink, '_blank');
            } else {
                history.push(props.tooltipLink);
            }
        }
    }

    return (
        <FormControl
            fullWidth={props.fullWidth}
            style={props.style}
            className={props.className}
        >
            <InputLabel style={props.variant === 'standard' ? {
                marginLeft: '-15px',
                marginTop: '8px',
            } : undefined}
            >{props.label}</InputLabel>
            <MuiSelect
                {...props}
                className={props.selectClassName}
                style={props.selectStyle}
                MenuProps={{
                    style: { zIndex: 10001 }
                }}
                endAdornment={(
                    (props.tooltipText || props.tooltipLink) && (
                        <InputAdornment position="end" sx={{ mr: 1 }}>
                            <Tooltip title={props.tooltipText}>
                                <IconButton onClick={openLink}>
                                    <HelpOutlineOutlined />
                                </IconButton>
                            </Tooltip>
                        </InputAdornment>
                    )
                )}
            >
                {props.options?.map((option) => {
                    const label = typeof option === 'object' ? option.label : option;
                    const value = typeof option === 'object' ? option.value : option;
                    return (
                        <MenuItem value={value} key={value + ''}>{label}</MenuItem>
                    )
                })}
            </MuiSelect>
        </FormControl>
    )
}
Example #11
Source File: LoginPage.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
Login = ({ classes }: ILoginProps) => {
  const dispatch = useDispatch();

  const [accessKey, setAccessKey] = useState<string>("");
  const [jwt, setJwt] = useState<string>("");
  const [secretKey, setSecretKey] = useState<string>("");
  const [loginStrategy, setLoginStrategy] = useState<ILoginDetails>({
    loginStrategy: loginStrategyType.unknown,
    redirect: "",
  });
  const [loginSending, setLoginSending] = useState<boolean>(false);
  const [loadingFetchConfiguration, setLoadingFetchConfiguration] =
    useState<boolean>(true);

  const [latestMinIOVersion, setLatestMinIOVersion] = useState<string>("");
  const [loadingVersion, setLoadingVersion] = useState<boolean>(true);

  const loginStrategyEndpoints: LoginStrategyRoutes = {
    form: "/api/v1/login",
    "service-account": "/api/v1/login/operator",
  };
  const loginStrategyPayload: LoginStrategyPayload = {
    form: { accessKey, secretKey },
    "service-account": { jwt },
  };

  const fetchConfiguration = () => {
    setLoadingFetchConfiguration(true);
  };

  const formSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setLoginSending(true);
    api
      .invoke(
        "POST",
        loginStrategyEndpoints[loginStrategy.loginStrategy] || "/api/v1/login",
        loginStrategyPayload[loginStrategy.loginStrategy]
      )
      .then(() => {
        // We set the state in redux
        dispatch(userLogged(true));
        if (loginStrategy.loginStrategy === loginStrategyType.form) {
          localStorage.setItem("userLoggedIn", accessKey);
        }
        let targetPath = "/";
        if (
          localStorage.getItem("redirect-path") &&
          localStorage.getItem("redirect-path") !== ""
        ) {
          targetPath = `${localStorage.getItem("redirect-path")}`;
          localStorage.setItem("redirect-path", "");
        }
        history.push(targetPath);
      })
      .catch((err) => {
        setLoginSending(false);
        dispatch(setErrorSnackMessage(err));
      });
  };

  useEffect(() => {
    if (loadingFetchConfiguration) {
      api
        .invoke("GET", "/api/v1/login")
        .then((loginDetails: ILoginDetails) => {
          setLoginStrategy(loginDetails);
          setLoadingFetchConfiguration(false);
        })
        .catch((err: ErrorResponseHandler) => {
          dispatch(setErrorSnackMessage(err));
          setLoadingFetchConfiguration(false);
        });
    }
  }, [loadingFetchConfiguration, dispatch]);

  useEffect(() => {
    if (loadingVersion) {
      api
        .invoke("GET", "/api/v1/check-version")
        .then(
          ({
            current_version,
            latest_version,
          }: {
            current_version: string;
            latest_version: string;
          }) => {
            setLatestMinIOVersion(latest_version);
            setLoadingVersion(false);
          }
        )
        .catch((err: ErrorResponseHandler) => {
          // try the operator version
          api
            .invoke("GET", "/api/v1/check-operator-version")
            .then(
              ({
                current_version,
                latest_version,
              }: {
                current_version: string;
                latest_version: string;
              }) => {
                setLatestMinIOVersion(latest_version);
                setLoadingVersion(false);
              }
            )
            .catch((err: ErrorResponseHandler) => {
              setLoadingVersion(false);
            });
        });
    }
  }, [loadingVersion, setLoadingVersion, setLatestMinIOVersion]);

  let loginComponent = null;

  switch (loginStrategy.loginStrategy) {
    case loginStrategyType.form: {
      loginComponent = (
        <React.Fragment>
          <form className={classes.form} noValidate onSubmit={formSubmit}>
            <Grid container spacing={2}>
              <Grid item xs={12} className={classes.spacerBottom}>
                <LoginField
                  fullWidth
                  id="accessKey"
                  className={classes.inputField}
                  value={accessKey}
                  onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                    setAccessKey(e.target.value)
                  }
                  placeholder={"Username"}
                  name="accessKey"
                  autoComplete="username"
                  disabled={loginSending}
                  variant={"outlined"}
                  InputProps={{
                    startAdornment: (
                      <InputAdornment
                        position="start"
                        className={classes.iconColor}
                      >
                        <UserFilledIcon />
                      </InputAdornment>
                    ),
                  }}
                />
              </Grid>
              <Grid item xs={12}>
                <LoginField
                  fullWidth
                  className={classes.inputField}
                  value={secretKey}
                  onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                    setSecretKey(e.target.value)
                  }
                  name="secretKey"
                  type="password"
                  id="secretKey"
                  autoComplete="current-password"
                  disabled={loginSending}
                  placeholder={"Password"}
                  variant={"outlined"}
                  InputProps={{
                    startAdornment: (
                      <InputAdornment
                        position="start"
                        className={classes.iconColor}
                      >
                        <LockFilledIcon />
                      </InputAdornment>
                    ),
                  }}
                />
              </Grid>
            </Grid>
            <Grid item xs={12} className={classes.submitContainer}>
              <Button
                type="submit"
                variant="contained"
                color="primary"
                id="do-login"
                className={classes.submit}
                disabled={secretKey === "" || accessKey === "" || loginSending}
              >
                Login
              </Button>
            </Grid>
            <Grid item xs={12} className={classes.linearPredef}>
              {loginSending && <LinearProgress />}
            </Grid>
          </form>
        </React.Fragment>
      );
      break;
    }
    case loginStrategyType.redirect:
    case loginStrategyType.redirectServiceAccount: {
      loginComponent = (
        <React.Fragment>
          <Button
            component={"a"}
            href={loginStrategy.redirect}
            type="submit"
            variant="contained"
            color="primary"
            id="sso-login"
            className={classes.submit}
          >
            Login with SSO
          </Button>
        </React.Fragment>
      );
      break;
    }
    case loginStrategyType.serviceAccount: {
      loginComponent = (
        <React.Fragment>
          <form className={classes.form} noValidate onSubmit={formSubmit}>
            <Grid container spacing={2}>
              <Grid item xs={12}>
                <LoginField
                  required
                  className={classes.inputField}
                  fullWidth
                  id="jwt"
                  value={jwt}
                  onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                    setJwt(e.target.value)
                  }
                  name="jwt"
                  autoComplete="off"
                  disabled={loginSending}
                  placeholder={"Enter JWT"}
                  variant={"outlined"}
                  InputProps={{
                    startAdornment: (
                      <InputAdornment position="start">
                        <LockIcon />
                      </InputAdornment>
                    ),
                  }}
                />
              </Grid>
            </Grid>
            <Grid item xs={12} className={classes.submitContainer}>
              <Button
                type="submit"
                variant="contained"
                color="primary"
                id="do-login"
                className={classes.submit}
                disabled={jwt === "" || loginSending}
              >
                Login
              </Button>
            </Grid>
            <Grid item xs={12} className={classes.linearPredef}>
              {loginSending && <LinearProgress />}
            </Grid>
          </form>
        </React.Fragment>
      );
      break;
    }
    default:
      loginComponent = (
        <div style={{ textAlign: "center" }}>
          {loadingFetchConfiguration ? (
            <Loader className={classes.loadingLoginStrategy} />
          ) : (
            <React.Fragment>
              <div>
                <p style={{ color: "#000", textAlign: "center" }}>
                  An error has occurred
                  <br />
                  The backend cannot be reached.
                </p>
              </div>
              <div>
                <Button
                  onClick={() => {
                    fetchConfiguration();
                  }}
                  endIcon={<RefreshIcon />}
                  color={"primary"}
                  variant="outlined"
                  id="retry"
                  className={classes.retryButton}
                >
                  Retry
                </Button>
              </div>
            </React.Fragment>
          )}
        </div>
      );
  }

  const isOperator =
    loginStrategy.loginStrategy === loginStrategyType.serviceAccount ||
    loginStrategy.loginStrategy === loginStrategyType.redirectServiceAccount;

  const consoleText = isOperator ? <OperatorLogo /> : <ConsoleLogo />;

  const hyperLink = isOperator
    ? "https://docs.min.io/minio/k8s/operator-console/operator-console.html?ref=con"
    : "https://docs.min.io/minio/baremetal/console/minio-console.html?ref=con";

  const theme = useTheme();
  return (
    <div className={classes.root}>
      <CssBaseline />
      <MainError />
      <div className={classes.loginPage}>
        <Grid
          container
          style={{
            maxWidth: 400,
            margin: "auto",
          }}
        >
          <Grid
            item
            xs={12}
            style={{
              background:
                "transparent linear-gradient(180deg, #FBFAFA 0%, #E4E4E4 100%) 0% 0% no-repeat padding-box",
              padding: 40,
              color: theme.palette.primary.main,
            }}
            sx={{
              marginTop: {
                md: 16,
                sm: 8,
                xs: 3,
              },
            }}
          >
            <Box className={classes.iconLogo}>{consoleText}</Box>
            <Box
              style={{
                font: "normal normal normal 20px/24px Lato",
              }}
            >
              Multicloud Object Storage
            </Box>
          </Grid>
          <Grid
            item
            xs={12}
            style={{
              backgroundColor: "white",
              padding: 40,
              color: theme.palette.primary.main,
            }}
          >
            {loginComponent}
            <Box
              style={{
                textAlign: "center",
                marginTop: 20,
              }}
            >
              <a
                href={hyperLink}
                target="_blank"
                rel="noreferrer"
                style={{
                  color: theme.colors.link,
                  font: "normal normal normal 12px/15px Lato",
                }}
              >
                Learn more about {isOperator ? "OPERATOR CONSOLE" : "CONSOLE"}
              </a>
              <a
                href={hyperLink}
                target="_blank"
                rel="noreferrer"
                style={{
                  color: theme.colors.link,
                  font: "normal normal normal 12px/15px Lato",
                  textDecoration: "none",
                  fontWeight: "bold",
                  paddingLeft: 4,
                }}
              >
                ➔
              </a>
            </Box>
          </Grid>
          <Grid item xs={12} className={classes.linkHolder}>
            <div className={classes.miniLinks}>
              <a
                href="https://docs.min.io/?ref=con"
                target="_blank"
                rel="noreferrer"
              >
                <DocumentationIcon /> Documentation
              </a>
              <span className={classes.separator}>|</span>
              <a
                href="https://github.com/minio/minio"
                target="_blank"
                rel="noreferrer"
              >
                <GithubIcon /> Github
              </a>
              <span className={classes.separator}>|</span>
              <a
                href="https://subnet.min.io/?ref=con"
                target="_blank"
                rel="noreferrer"
              >
                <SupportMenuIcon /> Support
              </a>
              <span className={classes.separator}>|</span>
              <a
                href="https://min.io/download/?ref=con"
                target="_blank"
                rel="noreferrer"
              >
                <DownloadIcon /> Download
              </a>
            </div>
            <div className={clsx(classes.miniLinks, classes.miniLogo)}>
              <a
                href={"https://github.com/minio/minio/releases"}
                target="_blank"
                rel="noreferrer"
                style={{
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                  marginBottom: 20,
                }}
              >
                <MinIOTierIconXs /> <b>Latest Version:</b>&nbsp;
                {!loadingVersion && latestMinIOVersion !== "" && (
                  <React.Fragment>{latestMinIOVersion}</React.Fragment>
                )}
              </a>
            </div>
          </Grid>
        </Grid>
      </div>
    </div>
  );
}
Example #12
Source File: CustomIdField.tsx    From firecms with MIT License 4 votes vote down vote up
export function CustomIdField<M, UserType>
({ schema, status, onChange, error, entity }: {
    schema: EntitySchema<M>,
    status: EntityStatus,
    onChange: Function,
    error: boolean,
    entity: Entity<M> | undefined
}) {

    const classes = formStyles();

    const disabled = status === "existing" || !schema.customId;
    const idSetAutomatically = status !== "existing" && !schema.customId;

    const hasEnumValues = typeof schema.customId === "object";

    const snackbarContext = useSnackbarController();
    const { copy } = useClipboard({
        onSuccess: (text) => snackbarContext.open({
            type: "success",
            message: `Copied ${text}`
        })
    });

    const appConfig: FireCMSContext<UserType> | undefined = useFireCMSContext();
    const inputProps = {
        className: classes.input,
        endAdornment: entity
? (
            <InputAdornment position="end">

                <IconButton onClick={(e) => copy(entity.id)}
                            aria-label="copy-id"
                            size="large">
                    <Tooltip title={"Copy"}>
                        <svg
                            className={"MuiSvgIcon-root MuiSvgIcon-fontSizeSmall"}
                            fill={"currentColor"}
                            width="20" height="20" viewBox="0 0 24 24">
                            <path
                                d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
                        </svg>
                    </Tooltip>
                </IconButton>

                {appConfig?.entityLinkBuilder &&
                <a href={appConfig.entityLinkBuilder({ entity })}
                   rel="noopener noreferrer"
                   target="_blank">
                    <IconButton onClick={(e) => e.stopPropagation()}
                                aria-label="go-to-datasource" size="large">
                        <Tooltip title={"Open in the console"}>
                            <OpenInNewIcon fontSize={"small"}/>
                        </Tooltip>
                    </IconButton>
                </a>}

            </InputAdornment>
        )
: undefined
    };

    const fieldProps: any = {
        label: idSetAutomatically ? "ID is set automatically" : "ID",
        disabled: disabled,
        name: "id",
        type: null,
        value: entity && status === "existing" ? entity.id : undefined,
        variant: "filled"
    };

    return (
        <FormControl fullWidth
                     error={error}
                     {...fieldProps}
                     key={"custom-id-field"}>

            {hasEnumValues && schema.customId &&
            <>
                <InputLabel id={"id-label"}>{fieldProps.label}</InputLabel>
                <MuiSelect
                    labelId={"id-label"}
                    className={classes.select}
                    error={error}
                    {...fieldProps}
                    onChange={(event: any) => onChange(event.target.value)}>
                    {Object.entries(schema.customId).map(([key, label]) =>
                        <MenuItem
                            key={`custom-id-item-${key}`}
                            value={key}>
                            {`${key} - ${label}`}
                        </MenuItem>)}
                </MuiSelect>
            </>}

            {!hasEnumValues &&
            <MuiTextField {...fieldProps}
                          error={error}
                          InputProps={inputProps}
                          helperText={schema.customId === "optional" ? "Leave this blank to autogenerate an ID" : "ID of the new document"}
                          onChange={(event) => {
                              let value = event.target.value;
                              if (value) value = value.trim();
                              return onChange(value.length ? value : undefined);
                          }}/>}

            <ErrorMessage name={"id"}
                          component="div">
                {(_) => "You need to specify an ID"}
            </ErrorMessage>

        </FormControl>
    );
}
Example #13
Source File: index.tsx    From yearn-watch-legacy with GNU Affero General Public License v3.0 4 votes vote down vote up
SearchInput = (props: SearchInputProps) => {
    const {
        onFilter,
        debounceWait,
        totalItems,
        foundItems,
        totalSubItems,
        foundSubItems,
    } = props;
    const [searchText, setSearchText] = useState('');
    const [isSearching, setIsSearching] = useState(false);
    const [filterVaultsWithWarnings, setFilterVaultsWithWarnings] =
        useState(false);
    const [healthCheckFilter, setHealthCheckFilter] = useState('');

    const debounceFilter = useCallback(
        debounce((newSearchText, flags) => {
            const newSearchTextLowerCase = newSearchText.toLowerCase();
            onFilter(newSearchTextLowerCase, flags, healthCheckFilter);
            setIsSearching(false);
        }, debounceWait),
        [debounceWait, isSearching]
    );

    // Event listener called on every change
    const onChange = useCallback(
        (event: ChangeEvent) => {
            const value = (event.target as HTMLInputElement).value;
            setIsSearching(true);
            setSearchText(value);
            debounceFilter(value, getCurrentFlags(filterVaultsWithWarnings));
        },
        [filterVaultsWithWarnings, searchText, isSearching, healthCheckFilter]
    );

    const onFilterVaultsWithWarnings = useCallback(
        (e: ChangeEvent<HTMLInputElement>) => {
            setFilterVaultsWithWarnings(e.target.checked);
            setIsSearching(true);
            const newSearchTextLowerCase = searchText.toLowerCase();
            onFilter(
                newSearchTextLowerCase,
                getCurrentFlags(e.target.checked),
                healthCheckFilter
            );
            setIsSearching(false);
        },
        [searchText, isSearching, healthCheckFilter]
    );
    const handleClickClearSearch = useCallback(() => {
        setSearchText('');
        setFilterVaultsWithWarnings(false);
        onFilter('', getCurrentFlags(false), '');
    }, [onFilter]);
    const healthCheckFilterChange = useCallback(
        (e: SelectChangeEvent<unknown>) => {
            setHealthCheckFilter((e.target as HTMLInputElement).value);
            setIsSearching(true);
            const newSearchTextLowerCase = searchText.toLowerCase();
            onFilter(
                newSearchTextLowerCase,
                getCurrentFlags(filterVaultsWithWarnings),
                (e.target as HTMLInputElement).value
            );
            setIsSearching(false);
        },
        [searchText, healthCheckFilter, isSearching]
    );

    const renderSearchingLabel = useCallback(() => {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let render: any;
        if (isSearching) {
            render = (
                <div>
                    <ProgressSpinnerBar label="results" />
                </div>
            );
        } else {
            render = (
                <>
                    {healthCheckFilter !== '' && (
                        <WarningLabel
                            warningText={'HealthCheck Filter is ON!'}
                        />
                    )}
                    <ResultsLabel
                        title="Vaults"
                        totalItems={totalItems}
                        foundItems={foundItems}
                        displayFound={true}
                        isSearching={isSearching}
                    />
                    <ResultsLabel
                        title="Strategies"
                        totalItems={totalSubItems}
                        foundItems={foundSubItems}
                        displayFound={true}
                        isSearching={isSearching}
                    />
                </>
            );
        }

        return render;
    }, [isSearching, totalItems, foundItems, totalSubItems, foundSubItems]);

    return (
        <div>
            <StyledForm>
                <Grid container direction="row" alignItems="center" spacing={3}>
                    <Grid item xs={12} sm={6}>
                        <StyledContainer maxWidth="lg">
                            <StyledTextField
                                variant="outlined"
                                onChange={onChange}
                                type="search"
                                value={searchText}
                                placeholder="Search by vault/strategy address/name, strategist address, token name/symbol, share token symbol/name or API version."
                                InputProps={
                                    searchText == ''
                                        ? {
                                              startAdornment: (
                                                  <InputAdornment position="end">
                                                      <Search />
                                                  </InputAdornment>
                                              ),
                                          }
                                        : {
                                              endAdornment: (
                                                  <InputAdornment position="end">
                                                      <IconButton
                                                          aria-label="delete"
                                                          onClick={
                                                              handleClickClearSearch
                                                          }
                                                          size="large"
                                                      >
                                                          <Delete />
                                                      </IconButton>
                                                  </InputAdornment>
                                              ),
                                          }
                                }
                            />
                        </StyledContainer>
                    </Grid>
                    <Grid item xs={12} sm={3}>
                        <StyledContainer maxWidth="lg">
                            <StyledFormControlLabel
                                control={
                                    <Switch
                                        checked={filterVaultsWithWarnings}
                                        onChange={onFilterVaultsWithWarnings}
                                        color="primary"
                                    />
                                }
                                labelPlacement="start"
                                label="Vaults with warnings"
                            />
                        </StyledContainer>
                    </Grid>
                    <Grid item xs={12} sm={3}>
                        <StyledContainer maxWidth="lg">
                            <StyledFormControlLabel
                                control={
                                    <StyledSelect
                                        displayEmpty
                                        variant="standard"
                                        defaultValue=""
                                        value={healthCheckFilter}
                                        onChange={healthCheckFilterChange}
                                    >
                                        <MenuItem value="">All</MenuItem>
                                        <MenuItem value="Enabled">
                                            Enabled
                                        </MenuItem>
                                        <MenuItem value="Disabled">
                                            Disabled
                                        </MenuItem>
                                        <MenuItem value="None">
                                            Not Set
                                        </MenuItem>
                                    </StyledSelect>
                                }
                                labelPlacement="start"
                                label="HealthCheck"
                            />
                        </StyledContainer>
                    </Grid>
                </Grid>
            </StyledForm>
            <StyledContainerResult maxWidth="lg">
                {renderSearchingLabel()}
            </StyledContainerResult>
        </div>
    );
}
Example #14
Source File: Withdraw.tsx    From wrap.scrt.network with MIT License 4 votes vote down vote up
export default function Withdraw({
  token,
  secretjs,
  secretAddress,
  balances,
  onSuccess,
  onFailure,
}: {
  token: Token;
  secretjs: SecretNetworkClient | null;
  secretAddress: string;
  balances: Map<string, string>;
  onSuccess: (txhash: string) => any;
  onFailure: (error: any) => any;
}) {
  const [targetAddress, setTargetAddress] = useState<string>("");
  const [loadingTx, setLoading] = useState<boolean>(false);
  const [selectedChainIndex, setSelectedChainIndex] = useState<number>(0);
  const inputRef = useRef<any>();
  const maxButtonRef = useRef<any>();

  const sourceChain = chains["Secret Network"];
  const targetChain =
    chains[token.withdrawals[selectedChainIndex].target_chain_name];

  const availableBalance =
    balances.get(token.withdrawals[selectedChainIndex].from_denom) || "";

  useEffect(() => {
    (async () => {
      while (!window.keplr || !window.getOfflineSignerOnlyAmino) {
        await sleep(100);
      }

      // Find address on target chain
      const { chain_id: targetChainId } =
        chains[token.withdrawals[selectedChainIndex].target_chain_name];
      if (token.withdrawals[selectedChainIndex].target_chain_name === "Terra") {
        await suggestTerraToKeplr(window.keplr);
      }
      await window.keplr.enable(targetChainId);
      const targetOfflineSigner =
        window.getOfflineSignerOnlyAmino(targetChainId);
      const targetFromAccounts = await targetOfflineSigner.getAccounts();
      setTargetAddress(targetFromAccounts[0].address);
    })();
  }, [selectedChainIndex]);

  return (
    <>
      <div style={{ padding: "1.5em" }}>
        <div
          style={{
            display: "flex",
            placeItems: "center",
            gap: "0.5em",
          }}
        >
          <Typography>
            Withdraw <strong>{token.name}</strong> from{" "}
            <strong>Secret Network</strong> to
          </Typography>
          <If condition={token.withdrawals.length === 1}>
            <Then>
              <Typography sx={{ marginLeft: "-0.2em" }}>
                <strong>
                  {token.withdrawals[selectedChainIndex].target_chain_name}
                </strong>
              </Typography>
            </Then>
            <Else>
              <FormControl>
                <Select
                  value={selectedChainIndex}
                  onChange={(e) =>
                    setSelectedChainIndex(Number(e.target.value))
                  }
                >
                  {token.withdrawals.map((chain, index) => (
                    <MenuItem value={index} key={index}>
                      <div
                        style={{
                          display: "flex",
                          gap: "0.5em",
                          placeItems: "center",
                        }}
                      >
                        <Avatar
                          src={chains[chain.target_chain_name].chain_image}
                          sx={{
                            marginLeft: "0.3em",
                            width: "1em",
                            height: "1em",
                            boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
                          }}
                        />
                        <strong>{chain.target_chain_name}</strong>
                      </div>
                    </MenuItem>
                  ))}
                </Select>
              </FormControl>
            </Else>
          </If>
        </div>
        <br />
        <div
          style={{
            display: "flex",
            placeContent: "space-between",
            placeItems: "center",
            gap: "1em",
          }}
        >
          <Typography sx={{ fontWeight: "bold" }}>From:</Typography>
          <CopyableAddress
            address={secretAddress}
            explorerPrefix={sourceChain.explorer_account}
          />
        </div>
        <div
          style={{
            display: "flex",
            placeContent: "space-between",
            placeItems: "center",
            gap: "1em",
          }}
        >
          <Typography sx={{ fontWeight: "bold" }}>To:</Typography>
          <CopyableAddress
            address={targetAddress}
            explorerPrefix={targetChain.explorer_account}
          />
        </div>
        <br />
        <div
          style={{
            display: "flex",
            placeItems: "center",
            gap: "0.3em",
            marginBottom: "0.8em",
          }}
        >
          <Typography sx={{ fontSize: "0.8em", fontWeight: "bold" }}>
            Available to Withdraw:
          </Typography>
          <Typography
            sx={{
              fontSize: "0.8em",
              opacity: 0.8,
              cursor: "pointer",
            }}
            onClick={() => {
              maxButtonRef.current.click();
            }}
          >
            {(() => {
              if (availableBalance === "") {
                return <CircularProgress size="0.6em" />;
              }

              const prettyBalance = new BigNumber(availableBalance)
                .dividedBy(`1e${token.decimals}`)
                .toFormat();

              if (prettyBalance === "NaN") {
                return "Error";
              }

              return `${prettyBalance} ${token.name}`;
            })()}
          </Typography>
        </div>
        <FormControl sx={{ width: "100%" }} variant="standard">
          <InputLabel htmlFor="Amount to Withdraw">
            Amount to Withdraw
          </InputLabel>
          <Input
            autoFocus
            id="Amount to Withdraw"
            fullWidth
            type="text"
            inputRef={inputRef}
            startAdornment={
              <InputAdornment position="start">
                <Avatar
                  src={token.image}
                  sx={{
                    width: "1em",
                    height: "1em",
                    boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
                  }}
                />
              </InputAdornment>
            }
            endAdornment={
              <InputAdornment position="end">
                <Button
                  ref={maxButtonRef}
                  style={{
                    padding: "0.1em 0.5em",
                    minWidth: 0,
                  }}
                  onClick={() => {
                    if (availableBalance === "") {
                      return;
                    }

                    const prettyBalance = new BigNumber(availableBalance)
                      .dividedBy(`1e${token.decimals}`)
                      .toFormat();

                    if (prettyBalance === "NaN") {
                      return;
                    }

                    inputRef.current.value = prettyBalance;
                  }}
                >
                  MAX
                </Button>
              </InputAdornment>
            }
          />
        </FormControl>
      </div>
      <div
        style={{
          display: "flex",
          placeContent: "center",
          marginBottom: "0.4em",
        }}
      >
        <LoadingButton
          variant="contained"
          sx={{
            padding: "0.5em 0",
            width: "10em",
            fontWeight: "bold",
            fontSize: "1.2em",
          }}
          loading={loadingTx}
          onClick={async () => {
            if (!secretjs) {
              console.error("No secretjs");
              return;
            }

            if (!inputRef?.current?.value) {
              console.error("Empty withdraw");
              return;
            }

            const normalizedAmount = (inputRef.current.value as string).replace(
              /,/g,
              ""
            );

            if (!(Number(normalizedAmount) > 0)) {
              console.error(`${normalizedAmount} not bigger than 0`);
              return;
            }

            setLoading(true);

            const amount = new BigNumber(normalizedAmount)
              .multipliedBy(`1e${token.decimals}`)
              .toFixed(0, BigNumber.ROUND_DOWN);

            const { withdraw_channel_id, withdraw_gas } =
              chains[token.withdrawals[selectedChainIndex].target_chain_name];
            try {
              const tx = await secretjs.tx.broadcast(
                [
                  new MsgTransfer({
                    sender: secretAddress,
                    receiver: targetAddress,
                    sourceChannel: withdraw_channel_id,
                    sourcePort: "transfer",
                    token: {
                      amount,
                      denom: token.withdrawals[selectedChainIndex].from_denom,
                    },
                    timeoutTimestampSec: String(
                      Math.floor(Date.now() / 1000) + 15 * 60
                    ), // 15 minute timeout
                  }),
                ],
                {
                  gasLimit: withdraw_gas,
                  gasPriceInFeeDenom: 0.25,
                  feeDenom: "uscrt",
                }
              );

              if (tx.code === 0) {
                inputRef.current.value = "";
                onSuccess(tx.transactionHash);
              } else {
                onFailure(tx.rawLog);
              }
            } catch (e) {
              onFailure(e);
            } finally {
              setLoading(false);
            }
          }}
        >
          Withdraw
        </LoadingButton>
      </div>
    </>
  );
}
Example #15
Source File: Deposit.tsx    From wrap.scrt.network with MIT License 4 votes vote down vote up
export default function Deposit({
  token,
  secretAddress,
  onSuccess,
  onFailure,
}: {
  token: Token;
  secretAddress: string;
  onSuccess: (txhash: string) => any;
  onFailure: (error: any) => any;
}) {
  const [sourceAddress, setSourceAddress] = useState<string>("");
  const [availableBalance, setAvailableBalance] = useState<string>("");
  const [loadingTx, setLoading] = useState<boolean>(false);
  const [sourceCosmJs, setSourceCosmJs] =
    useState<SigningStargateClient | null>(null);
  const [selectedChainIndex, setSelectedChainIndex] = useState<number>(0);
  const [fetchBalanceInterval, setFetchBalanceInterval] = useState<any>(null);
  const inputRef = useRef<any>();
  const maxButtonRef = useRef<any>();

  const sourceChain =
    chains[token.deposits[selectedChainIndex].source_chain_name];
  const targetChain = chains["Secret Network"];

  const fetchSourceBalance = async (sourceAddress: string) => {
    const url = `${
      chains[token.deposits[selectedChainIndex].source_chain_name].lcd
    }/bank/balances/${sourceAddress}`;
    try {
      const response = await fetch(url);
      const result: {
        height: string;
        result: Array<{ denom: string; amount: string }>;
      } = await response.json();

      const balance =
        result.result.find(
          (c) => c.denom === token.deposits[selectedChainIndex].from_denom
        )?.amount || "0";

      setAvailableBalance(balance);
    } catch (e) {
      console.error(`Error while trying to query ${url}:`, e);
      setAvailableBalance("Error");
    }
  };

  useEffect(() => {
    setAvailableBalance("");

    if (!sourceAddress) {
      return;
    }

    if (fetchBalanceInterval) {
      clearInterval(fetchBalanceInterval);
    }

    fetchSourceBalance(sourceAddress);
    const interval = setInterval(
      () => fetchSourceBalance(sourceAddress),
      10_000
    );
    setFetchBalanceInterval(interval);

    return () => clearInterval(interval);
  }, [sourceAddress]);

  useEffect(() => {
    (async () => {
      while (!window.keplr || !window.getOfflineSignerOnlyAmino) {
        await sleep(100);
      }

      if (["LUNA", "UST"].includes(token.name.toUpperCase())) {
        await suggestTerraToKeplr(window.keplr);
      }
      // Initialize cosmjs on the target chain, because it has sendIbcTokens()
      const { chain_id, rpc, bech32_prefix } =
        chains[token.deposits[selectedChainIndex].source_chain_name];
      await window.keplr.enable(chain_id);
      const sourceOfflineSigner = window.getOfflineSignerOnlyAmino(chain_id);
      const depositFromAccounts = await sourceOfflineSigner.getAccounts();
      setSourceAddress(depositFromAccounts[0].address);
      const cosmjs = await SigningStargateClient.connectWithSigner(
        rpc,
        sourceOfflineSigner,
        { prefix: bech32_prefix, broadcastPollIntervalMs: 10_000 }
      );
      setSourceCosmJs(cosmjs);
    })();
  }, [selectedChainIndex]);

  return (
    <>
      <div style={{ padding: "1.5em" }}>
        <div
          style={{
            display: "flex",
            placeItems: "center",
            gap: token.deposits.length === 1 ? "0.3em" : "0.5em",
          }}
        >
          <Typography>
            Deposit <strong>{token.name}</strong> from
          </Typography>
          <If condition={token.deposits.length === 1}>
            <Then>
              <Typography>
                <strong>
                  {token.deposits[selectedChainIndex].source_chain_name}
                </strong>
              </Typography>
            </Then>
            <Else>
              <FormControl>
                <Select
                  value={selectedChainIndex}
                  onChange={(e) =>
                    setSelectedChainIndex(Number(e.target.value))
                  }
                >
                  {token.deposits.map((chain, index) => (
                    <MenuItem value={index} key={index}>
                      <div
                        style={{
                          display: "flex",
                          gap: "0.5em",
                          placeItems: "center",
                        }}
                      >
                        <Avatar
                          src={chains[chain.source_chain_name].chain_image}
                          sx={{
                            marginLeft: "0.3em",
                            width: "1em",
                            height: "1em",
                            boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
                          }}
                        />
                        <strong>{chain.source_chain_name}</strong>
                      </div>
                    </MenuItem>
                  ))}
                </Select>
              </FormControl>
            </Else>
          </If>
          <Typography>
            to <strong>Secret Network</strong>
          </Typography>
        </div>
        <br />
        <div
          style={{
            display: "flex",
            placeContent: "space-between",
            placeItems: "center",
            gap: "1em",
          }}
        >
          <Typography sx={{ fontWeight: "bold" }}>From:</Typography>
          <CopyableAddress
            address={sourceAddress}
            explorerPrefix={sourceChain.explorer_account}
          />
        </div>
        <div
          style={{
            display: "flex",
            placeContent: "space-between",
            placeItems: "center",
            gap: "1em",
          }}
        >
          <Typography sx={{ fontWeight: "bold" }}>To:</Typography>
          <CopyableAddress
            address={secretAddress}
            explorerPrefix={targetChain.explorer_account}
          />
        </div>
        <br />
        <div
          style={{
            display: "flex",
            placeItems: "center",
            gap: "0.3em",
            marginBottom: "0.8em",
          }}
        >
          <Typography sx={{ fontSize: "0.8em", fontWeight: "bold" }}>
            Available to Deposit:
          </Typography>
          <Typography
            sx={{
              fontSize: "0.8em",
              opacity: 0.8,
              cursor: "pointer",
            }}
            onClick={() => {
              maxButtonRef.current.click();
            }}
          >
            {(() => {
              if (availableBalance === "") {
                return <CircularProgress size="0.6em" />;
              }

              const prettyBalance = new BigNumber(availableBalance)
                .dividedBy(`1e${token.decimals}`)
                .toFormat();

              if (prettyBalance === "NaN") {
                return "Error";
              }

              return `${prettyBalance} ${token.name}`;
            })()}
          </Typography>
        </div>
        <FormControl sx={{ width: "100%" }} variant="standard">
          <InputLabel htmlFor="Amount to Deposit">Amount to Deposit</InputLabel>
          <Input
            autoFocus
            id="Amount to Deposit"
            fullWidth
            type="text"
            inputRef={inputRef}
            startAdornment={
              <InputAdornment position="start">
                <Avatar
                  src={token.image}
                  sx={{
                    width: "1em",
                    height: "1em",
                    boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
                  }}
                />
              </InputAdornment>
            }
            endAdornment={
              <InputAdornment position="end">
                <Button
                  ref={maxButtonRef}
                  style={{
                    padding: "0.1em 0.5em",
                    minWidth: 0,
                  }}
                  onClick={() => {
                    if (availableBalance === "") {
                      return;
                    }

                    const prettyBalance = new BigNumber(availableBalance)
                      .dividedBy(`1e${token.decimals}`)
                      .toFormat();

                    if (prettyBalance === "NaN") {
                      return;
                    }

                    inputRef.current.value = prettyBalance;
                  }}
                >
                  MAX
                </Button>
              </InputAdornment>
            }
          />
        </FormControl>
      </div>
      <div
        style={{
          display: "flex",
          placeContent: "center",
          marginBottom: "0.4em",
        }}
      >
        <LoadingButton
          variant="contained"
          sx={{
            padding: "0.5em 0",
            width: "10em",
            fontWeight: "bold",
            fontSize: "1.2em",
          }}
          loading={loadingTx}
          onClick={async () => {
            if (!sourceCosmJs) {
              console.error("No cosmjs");
              return;
            }

            if (!inputRef?.current?.value) {
              console.error("Empty deposit");
              return;
            }

            const normalizedAmount = (inputRef.current.value as string).replace(
              /,/g,
              ""
            );

            if (!(Number(normalizedAmount) > 0)) {
              console.error(`${normalizedAmount} not bigger than 0`);
              return;
            }

            setLoading(true);

            const amount = new BigNumber(normalizedAmount)
              .multipliedBy(`1e${token.decimals}`)
              .toFixed(0, BigNumber.ROUND_DOWN);

            const { deposit_channel_id, deposit_gas } =
              chains[token.deposits[selectedChainIndex].source_chain_name];
            try {
              const { transactionHash } = await sourceCosmJs.sendIbcTokens(
                sourceAddress,
                secretAddress,
                {
                  amount,
                  denom: token.deposits[selectedChainIndex].from_denom,
                },
                "transfer",
                deposit_channel_id,
                undefined,
                Math.floor(Date.now() / 1000) + 15 * 60, // 15 minute timeout
                gasToFee(deposit_gas)
              );
              inputRef.current.value = "";
              onSuccess(transactionHash);
            } catch (e) {
              onFailure(e);
            } finally {
              setLoading(false);
            }
          }}
        >
          Deposit
        </LoadingButton>
      </div>
    </>
  );
}
Example #16
Source File: ColorPicker.tsx    From Cromwell with MIT License 4 votes vote down vote up
export function ColorPicker(props: {
    label?: string;
    value?: string;
    className?: string;
    style?: React.CSSProperties;
    onChange?: (color: string) => void;
}) {
    const colorRef = useRef<string | null>(null);
    const prevValue = useRef<string | null>(null);
    const inputAnchorRef = useRef<HTMLDivElement | null>(null);
    const [open, setOpen] = useState(false);
    const forceUpdate = useForceUpdate();

    if (props.value !== prevValue.current) {
        prevValue.current = props.value;
        colorRef.current = props.value;
    }

    const handleChange = (color: { hex: string; rgb: { r: number; g: number; b: number; a: number } }) => {
        const colorStr = color.rgb.a === 1 ? color.hex : `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a})`;
        colorRef.current = colorStr;
        forceUpdate();
    }

    const handleClose = () => {
        handleApply();
        setOpen(false);
    }

    const handleApply = () => {
        props.onChange?.(colorRef.current);
    }

    const handleInputChange = (event) => {
        colorRef.current = event.target.value;
        forceUpdate();
        handleApply();
    }

    return (
        <>
            <TextField
                InputProps={{
                    startAdornment: (
                        <InputAdornment position="start">
                            <div style={{ backgroundColor: colorRef.current, width: '20px', height: '20px', borderRadius: '100%' }}></div>
                        </InputAdornment>
                    ),
                }}
                variant="standard"
                className={props.className}
                label={props.label}
                fullWidth
                value={colorRef.current}
                ref={inputAnchorRef}
                onChange={handleInputChange}
                onClick={() => setOpen(true)}
                style={props.style}
            />
            <Popover
                open={open}
                anchorEl={inputAnchorRef.current}
                onClose={handleClose}
                anchorOrigin={{
                    vertical: 'bottom',
                    horizontal: 'left',
                }}
                transformOrigin={{
                    vertical: 'top',
                    horizontal: 'left',
                }}
            >
                <div>
                    <SketchPicker
                        color={colorRef.current ?? '#000'}
                        onChangeComplete={handleChange}
                    />
                </div>
            </Popover>
        </>
    )
}
Example #17
Source File: FirstStep.tsx    From frontend with MIT License 4 votes vote down vote up
export default function FirstStep() {
  const { data: prices } = useSinglePriceList()
  const { t } = useTranslation('one-time-donation')
  const mobile = useMediaQuery('(max-width:568px)')
  const options = [
    { value: 'card', label: t('third-step.card') },
    { value: 'bank', label: t('third-step.bank-payment') },
  ]

  const [paymentField] = useField('payment')
  const [amount] = useField('amount')
  const { campaign } = useContext(StepsContext)
  const bankAccountInfo = {
    owner: t('third-step.owner'),
    bank: t('third-step.bank'),
    iban: ibanNumber,
  }
  return (
    <Root>
      <Typography variant="h4">{t('third-step.title')}</Typography>
      <Box marginTop={theme.spacing(4)}>
        <RadioButtonGroup name="payment" options={options} />
      </Box>
      <Collapse in={paymentField.value === 'bank'} timeout="auto">
        <List component="div" disablePadding>
          <Typography marginTop={theme.spacing(8)} variant="h6">
            {t('third-step.bank-details')}
          </Typography>
          <Divider className={classes.divider} />
          <Grid container justifyContent="center">
            <Grid my={2} item display="flex" justifyContent="space-between" xs={9}>
              <Typography>{bankAccountInfo.owner}</Typography>
              <CopyTextButton
                label={t('third-step.btn-copy')}
                text={bankAccountInfo.owner}
                variant="contained"
                size="small"
                color="info"
              />
            </Grid>
            <Grid my={2} item display="flex" justifyContent="space-between" xs={9}>
              <Typography>{bankAccountInfo.bank}</Typography>
              <CopyTextButton
                label={t('third-step.btn-copy')}
                text={bankAccountInfo.bank}
                variant="contained"
                size="small"
                color="info"
              />
            </Grid>
            <Grid my={2} item display="flex" justifyContent="space-between" xs={9}>
              <Typography>{ibanNumber}</Typography>
              <CopyTextButton
                label={t('third-step.btn-copy')}
                text={bankAccountInfo.iban}
                variant="contained"
                size="small"
                color="info"
              />
            </Grid>
          </Grid>

          <Typography my={2} variant="h6">
            {t('third-step.reason-donation')}
          </Typography>
          <Divider className={classes.divider} />
          <Grid container justifyContent="center">
            <Grid my={3} item display="flex" justifyContent="space-between" xs={9}>
              <Alert severity="warning">
                <Typography fontWeight="bold">{campaign.bankHash}</Typography>
              </Alert>
              <CopyTextButton
                text={campaign.title}
                variant="contained"
                color="info"
                size="small"
                label={t('third-step.btn-copy')}
              />
            </Grid>
          </Grid>

          <Typography>{t('third-step.message-warning')}</Typography>
        </List>
      </Collapse>
      <Collapse in={paymentField.value === 'card'} timeout="auto">
        <Typography variant="h4" sx={{ marginTop: theme.spacing(8) }}>
          {t('first-step.amount')}
        </Typography>
        <Box marginTop={theme.spacing(4)}>
          <RadioButtonGroup
            name="amount"
            options={
              prices
                ?.sort((a, b) => Number(a.unit_amount) - Number(b.unit_amount))
                .map((v) => ({
                  label: money(Number(v.unit_amount)),
                  value: v.id,
                }))
                .concat({ label: 'Other', value: 'other' }) || []
            }
          />
          <Collapse in={amount.value === 'other'} timeout="auto">
            <Grid
              style={
                !mobile
                  ? {
                      float: 'right',
                      marginTop: theme.spacing(-10),
                      width: '49%',
                    }
                  : { marginTop: theme.spacing(2) }
              }>
              <FormTextField
                name="otherAmount"
                type="number"
                label={t('first-step.amount')}
                InputProps={{
                  style: { fontSize: 20, padding: 16 },
                  endAdornment: (
                    <InputAdornment variant="filled" position="end">
                      Лв.
                    </InputAdornment>
                  ),
                }}
              />
            </Grid>
          </Collapse>
        </Box>
      </Collapse>
    </Root>
  )
}
Example #18
Source File: Coupon.tsx    From Cromwell with MIT License 4 votes vote down vote up
CouponPage = () => {
    const { id: couponId } = useParams<{ id: string }>();
    const client = getGraphQLClient();
    const [data, setData] = useState<TCoupon | null>(null);
    const [pickedCategories, setPickedCategories] = useState<TProductCategory[] | null>(null);
    const [pickedProducts, setPickedProducts] = useState<TProduct[] | null>(null);
    const [notFound, setNotFound] = useState(false);
    const [isSaving, setIsSaving] = useState(false);
    const [couponLoading, setCouponLoading] = useState<boolean>(false);
    const history = useHistory();
    const [canValidate, setCanValidate] = useState(false);

    const getCouponData = async (id: number) => {
        let couponData: TCoupon;
        try {
            couponData = await client.getCouponById(id,
                gql`
                    fragment AdminPanelCouponFragment on Coupon {
                        id
                        createDate
                        updateDate
                        pageTitle
                        pageDescription
                        meta {
                            keywords
                        }
                        isEnabled
                        discountType
                        value
                        code
                        description
                        allowFreeShipping
                        minimumSpend
                        maximumSpend
                        categoryIds
                        productIds
                        expiryDate
                        usageLimit
                        customMeta (keys: ${JSON.stringify(getCustomMetaKeysFor(EDBEntity.Coupon))})
                    }`, 'AdminPanelCouponFragment');
            if (couponData) {
                setData(couponData);
            }
        } catch (e) {
            console.error(e)
        }

        if (!couponData) {
            setNotFound(true);
        }
        return couponData;
    }

    const init = async () => {
        setCouponLoading(true);

        let couponData: TCoupon;

        if (couponId && couponId !== 'new') {
            couponData = await getCouponData(parseInt(couponId));
        }

        if (couponId === 'new') {
            setData({} as any);
        }

        setCouponLoading(false);


        if (couponData?.categoryIds?.length) {
            const categories = await Promise.all(couponData.categoryIds.map(async id => {
                try {
                    return await client.getProductCategoryById(Number(id));
                } catch (error) {
                    console.error(error);
                }
            }));
            setPickedCategories(categories ?? []);
        } else setPickedCategories([]);

        if (couponData?.productIds?.length) {
            const products = await Promise.all(couponData.productIds.map(async id => {
                try {
                    return await client.getProductById(Number(id));
                } catch (error) {
                    console.error(error);
                }
            }));
            setPickedProducts(products ?? []);
        } else setPickedProducts([]);
    }

    useEffect(() => {
        init();
    }, []);

    const handleSave = async () => {
        setCanValidate(true);
        if (!data?.code || !data.value || !data.discountType) return;

        setIsSaving(true);

        const inputData: TCouponInput = {
            slug: data.slug,
            pageTitle: data.pageTitle,
            pageDescription: data.pageDescription,
            meta: data.meta && {
                keywords: data.meta.keywords,
            },
            isEnabled: data.isEnabled,
            discountType: data.discountType,
            value: data.value,
            code: data.code,
            description: data.description,
            allowFreeShipping: data.allowFreeShipping,
            minimumSpend: data.minimumSpend ? parseFloat(data.minimumSpend as any) : null,
            maximumSpend: data.maximumSpend ? parseFloat(data.maximumSpend as any) : null,
            categoryIds: data.categoryIds,
            productIds: data.productIds,
            expiryDate: data.expiryDate,
            usageLimit: data.usageLimit,
            customMeta: Object.assign({}, data.customMeta, await getCustomMetaFor(EDBEntity.Coupon)),
        }

        if (couponId === 'new') {
            try {
                const newData = await client?.createCoupon(inputData);
                toast.success('Created coupon!');
                history.replace(`${couponPageInfo.baseRoute}/${newData.id}`)
                await getCouponData(newData.id);
            } catch (e) {
                toast.error('Failed to create coupon');
                console.error(e);
            }
        } else {
            try {
                await client?.updateCoupon(data.id, inputData);
                await getCouponData(data.id);
                toast.success('Saved!');
            } catch (e) {
                toast.error('Failed to save');
                console.error(e)
            }
        }
        setIsSaving(false);
        setCanValidate(false);
    }

    const handleInputChange = (prop: keyof TCoupon, val: any) => {
        if (data) {
            setData((prevData) => {
                const newData = Object.assign({}, prevData);
                (newData[prop] as any) = val;
                return newData;
            });
        }
    }

    const handleGenerateCode = () => {
        handleInputChange('code', getRandStr(8).toUpperCase());
    }

    const handleSearchCategory = async (text: string, params: TPagedParams<TProductCategory>) => {
        return client?.getFilteredProductCategories({
            filterParams: {
                nameSearch: text
            },
            pagedParams: params
        });
    }

    const handleSearchProduct = async (text: string, params: TPagedParams<TProduct>) => {
        return client?.getFilteredProducts({
            filterParams: {
                nameSearch: text
            },
            pagedParams: params
        });
    }

    const refetchMeta = async () => {
        if (!couponId) return;
        const data = await getCouponData(parseInt(couponId));
        return data?.customMeta;
    };

    if (notFound) {
        return (
            <div className={styles.CouponPage}>
                <div className={styles.notFoundPage}>
                    <p className={styles.notFoundText}>Coupon not found</p>
                </div>
            </div>
        )
    }

    let pageFullUrl;
    if (data) {
        pageFullUrl = serviceLocator.getFrontendUrl() + resolvePageRoute('coupon', { slug: data.slug ?? data.id + '' });
    }

    return (
        <div className={styles.CouponPage}>
            <div className={styles.header}>
                <div className={styles.headerLeft}>
                    <IconButton
                        onClick={() => window.history.back()}
                    >
                        <ArrowBackIcon style={{ fontSize: '18px' }} />
                    </IconButton>
                    <p className={commonStyles.pageTitle}>coupon</p>
                </div>
                <div className={styles.headerActions}>
                    {pageFullUrl && (
                        <Tooltip title="Open coupon in the new tab">
                            <IconButton
                                style={{ marginRight: '10px' }}
                                className={styles.openPageBtn}
                                aria-label="open"
                                onClick={() => { window.open(pageFullUrl, '_blank'); }}
                            >
                                <OpenInNewIcon />
                            </IconButton>
                        </Tooltip>
                    )}
                    <Button variant="contained" color="primary"
                        className={styles.saveBtn}
                        size="small"
                        disabled={isSaving}
                        onClick={handleSave}>Save</Button>
                </div>
            </div>
            <div className={styles.fields}>
                {couponLoading && (
                    Array(8).fill(1).map((it, index) => (
                        <Skeleton style={{ marginBottom: '10px' }} key={index} height={"50px"} />
                    ))
                )}
                {!couponLoading && (
                    <Grid container spacing={3}>
                        <Grid item xs={12} sm={6}>
                            <TextField label="Code"
                                value={data?.code || ''}
                                fullWidth
                                variant="standard"
                                className={styles.textField}
                                onChange={(e) => { handleInputChange('code', e.target.value) }}
                                error={canValidate && !data?.code}
                                InputProps={{
                                    endAdornment: (
                                        <InputAdornment position="end">
                                            <Tooltip title="Generate code">
                                                <IconButton
                                                    aria-label="Generate code"
                                                    onClick={handleGenerateCode}
                                                    edge="end"
                                                >
                                                    {<SmartButtonIcon />}
                                                </IconButton>
                                            </Tooltip>
                                        </InputAdornment>
                                    ),
                                }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={6}></Grid>
                        <Grid item xs={12} sm={6}>
                            <Select
                                fullWidth
                                variant="standard"
                                label="Discount type"
                                value={(data?.discountType ?? '')}
                                onChange={(event: SelectChangeEvent<unknown>) => {
                                    handleInputChange('discountType', event.target.value)
                                }}
                                error={canValidate && !data?.discountType}
                                options={[
                                    {
                                        value: 'fixed',
                                        label: 'Fixed'
                                    },
                                    {
                                        value: 'percentage',
                                        label: 'Percentage'
                                    }
                                ]}
                            />
                        </Grid>
                        <Grid item xs={12} sm={6}>
                            <TextField
                                label="Discount value"
                                className={styles.textField}
                                fullWidth
                                variant="standard"
                                value={data?.value || ''}
                                error={canValidate && !data?.value}
                                onChange={(e) => {
                                    const val = Number(e.target.value);
                                    if (!isNaN(val)) handleInputChange('value', val);
                                }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={12}>
                            <TextField
                                label="Description"
                                className={styles.textField}
                                fullWidth
                                multiline
                                variant="standard"
                                value={data?.description || ''}
                                onChange={(e) => { handleInputChange('description', e.target.value) }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={6}>
                            <TextField
                                label="Cart total minimum"
                                className={styles.textField}
                                fullWidth
                                multiline
                                variant="standard"
                                value={data?.minimumSpend || ''}
                                onChange={(e) => { handleInputChange('minimumSpend', e.target.value) }}
                                InputProps={{
                                    inputComponent: NumberFormatCustom as any,
                                }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={6}>
                            <TextField
                                label="Cart total maximum"
                                className={styles.textField}
                                fullWidth
                                multiline
                                variant="standard"
                                value={data?.maximumSpend || ''}
                                onChange={(e) => { handleInputChange('maximumSpend', e.target.value) }}
                                InputProps={{
                                    inputComponent: NumberFormatCustom as any,
                                }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={6}>
                            <LocalizationProvider dateAdapter={AdapterDateFns}>
                                <DatePicker
                                    label="Expiry date"
                                    value={data?.expiryDate}
                                    onChange={(newValue) => {
                                        if (!newValue) {
                                            handleInputChange('expiryDate', null);
                                            return;
                                        }
                                        const date = new Date(newValue);
                                        if (isNaN(date.getTime())) {
                                            handleInputChange('expiryDate', null);
                                            return;
                                        }
                                        handleInputChange('expiryDate', date);
                                    }}
                                    renderInput={(params) => <TextField
                                        variant="standard"
                                        fullWidth
                                        {...params}
                                    />}
                                />
                            </LocalizationProvider>
                        </Grid>
                        <Grid item xs={12} sm={6}>
                            <TextField
                                label="Usage limit"
                                className={styles.textField}
                                fullWidth
                                variant="standard"
                                value={data?.usageLimit || ''}
                                onChange={(e) => {
                                    const val = Number(e.target.value);
                                    if (!isNaN(val)) handleInputChange('usageLimit', val);
                                }}
                            />
                        </Grid>
                        <Grid item xs={12} sm={12}>
                            {pickedCategories ? (
                                <Autocomplete<TProductCategory>
                                    multiple
                                    loader={handleSearchCategory}
                                    onSelect={(data: TProductCategory[]) => {
                                        if (!data?.length) handleInputChange('categoryIds', null);
                                        else handleInputChange('categoryIds', data.map(cat => cat.id));
                                    }}
                                    getOptionLabel={(data) => `${data.name} (id: ${data.id}${data?.parent?.id ? `; parent id: ${data.parent.id}` : ''})`}
                                    getOptionValue={(data) => data.name}
                                    fullWidth
                                    className={styles.textField}
                                    defaultValue={pickedCategories}
                                    label={"Categories"}
                                />
                            ) : <LoadBox size={30} />}
                        </Grid>
                        <Grid item xs={12} sm={12}>
                            {pickedProducts ? (
                                <Autocomplete<TProduct>
                                    multiple
                                    loader={handleSearchProduct}
                                    onSelect={(data: TProduct[]) => {
                                        if (!data?.length) handleInputChange('productIds', null);
                                        else handleInputChange('productIds', data.map(cat => cat.id));
                                    }}
                                    getOptionLabel={(data) => `${data.name} (id: ${data.id})`}
                                    getOptionValue={(data) => data.name}
                                    fullWidth
                                    className={styles.textField}
                                    defaultValue={pickedProducts}
                                    label={"Products"}
                                />
                            ) : <LoadBox size={30} />}
                        </Grid>
                        <Grid item xs={12} >
                            {data && (
                                <RenderCustomFields
                                    entityType={EDBEntity.Coupon}
                                    entityData={data}
                                    refetchMeta={refetchMeta}
                                />
                            )}
                        </Grid>
                    </Grid>
                )}
            </div>
        </div>
    )
}
Example #19
Source File: QueryStateEditor.tsx    From mui-toolpad with MIT License 4 votes vote down vote up
function QueryStateNodeEditor<P>({ node }: QueryStateNodeEditorProps<P>) {
  const dom = useDom();
  const domApi = useDomApi();
  const app = appDom.getApp(dom);
  const { apis = [] } = appDom.getChildNodes(dom, app);

  const handleSelectionChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const apiNodeId = event.target.value ? (event.target.value as NodeId) : null;
      domApi.setNodeNamespacedProp(node, 'attributes', 'api', appDom.createConst(apiNodeId));
    },
    [domApi, node],
  );

  const handleRefetchOnWindowFocusChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      domApi.setNodeNamespacedProp(
        node,
        'attributes',
        'refetchOnWindowFocus',
        appDom.createConst(event.target.checked),
      );
    },
    [domApi, node],
  );

  const handleRefetchOnReconnectChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      domApi.setNodeNamespacedProp(
        node,
        'attributes',
        'refetchOnReconnect',
        appDom.createConst(event.target.checked),
      );
    },
    [domApi, node],
  );

  const handleRefetchIntervalChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const interval = Number(event.target.value);

      if (Number.isNaN(interval) || interval <= 0) {
        domApi.setNodeNamespacedProp(node, 'attributes', 'refetchInterval', undefined);
      } else {
        domApi.setNodeNamespacedProp(
          node,
          'attributes',
          'refetchInterval',
          appDom.createConst(interval * 1000),
        );
      }
    },
    [domApi, node],
  );

  const argTypes = getQueryNodeArgTypes(dom, node);

  return (
    <React.Fragment>
      <Stack spacing={1} py={1}>
        <NodeNameEditor node={node} />
        <TextField
          select
          fullWidth
          value={node.attributes.api.value || ''}
          label="Query"
          onChange={handleSelectionChange}
        >
          <MenuItem value="">---</MenuItem>
          {apis.map(({ id, name }) => (
            <MenuItem key={id} value={id}>
              {name}
            </MenuItem>
          ))}
        </TextField>
        <ParamsEditor node={node} argTypes={argTypes} />
        <FormControlLabel
          control={
            <Checkbox
              checked={node.attributes.refetchOnWindowFocus?.value ?? true}
              onChange={handleRefetchOnWindowFocusChange}
            />
          }
          label="Refetch on window focus"
        />
        <FormControlLabel
          control={
            <Checkbox
              checked={node.attributes.refetchOnReconnect?.value ?? true}
              onChange={handleRefetchOnReconnectChange}
            />
          }
          label="Refetch on network reconnect"
        />
        <TextField
          InputProps={{
            startAdornment: <InputAdornment position="start">s</InputAdornment>,
          }}
          sx={{ maxWidth: 300 }}
          type="number"
          label="Refetch interval"
          value={refetchIntervalInSeconds(node.attributes.refetchInterval?.value) ?? ''}
          onChange={handleRefetchIntervalChange}
        />
        <PreviewQueryStateResult node={node} />
      </Stack>
    </React.Fragment>
  );
}
Example #20
Source File: QueryEditor.tsx    From mui-toolpad with MIT License 4 votes vote down vote up
function QueryNodeEditorDialog<Q, P>({
  open,
  node,
  onClose,
  onRemove,
  onSave,
}: QueryNodeEditorProps<Q, P>) {
  const { appId } = usePageEditorState();

  const [input, setInput] = React.useState(node);
  React.useEffect(() => setInput(node), [node]);

  const connectionId = input.attributes.connectionId.value;
  const dataSourceId = input.attributes.dataSource?.value;
  const dataSource = (dataSourceId && dataSources[dataSourceId]) || null;

  const handleConnectionChange = React.useCallback((newConnectionId) => {
    setInput((existing) =>
      update(existing, {
        attributes: update(existing.attributes, {
          connectionId: appDom.createConst(newConnectionId),
        }),
      }),
    );
  }, []);

  const handleQueryChange = React.useCallback((newQuery: Q) => {
    setInput((existing) =>
      update(existing, {
        attributes: update(existing.attributes, {
          query: appDom.createConst(newQuery),
        }),
      }),
    );
  }, []);

  const handleTransformFnChange = React.useCallback((newValue: string) => {
    setInput((existing) =>
      update(existing, {
        attributes: update(existing.attributes, {
          transform: appDom.createConst(newValue),
        }),
      }),
    );
  }, []);

  const handleTransformEnabledChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setInput((existing) =>
        update(existing, {
          attributes: update(existing.attributes, {
            transformEnabled: appDom.createConst(event.target.checked),
          }),
        }),
      );
    },
    [],
  );

  const handleRefetchOnWindowFocusChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setInput((existing) =>
        update(existing, {
          attributes: update(existing.attributes, {
            refetchOnWindowFocus: appDom.createConst(event.target.checked),
          }),
        }),
      );
    },
    [],
  );

  const handleRefetchOnReconnectChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setInput((existing) =>
        update(existing, {
          attributes: update(existing.attributes, {
            refetchOnReconnect: appDom.createConst(event.target.checked),
          }),
        }),
      );
    },
    [],
  );

  const handleRefetchIntervalChange = React.useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const interval = Number(event.target.value);

      setInput((existing) =>
        update(existing, {
          attributes:
            Number.isNaN(interval) || interval <= 0
              ? omit(existing.attributes, 'refetchInterval')
              : update(existing.attributes, {
                  refetchInterval: appDom.createConst(interval * 1000),
                }),
        }),
      );
    },
    [],
  );

  const [params, setParams] = React.useState<[string, BindableAttrValue<any>][]>(
    Object.entries(input.params || {}),
  );
  React.useEffect(() => setParams(Object.entries(input.params || {})), [input.params]);

  const { pageState } = usePageEditorState();
  const liveParams: [string, LiveBinding][] = React.useMemo(() => {
    return params.map(([name, bindable]) => [name, evaluateBindable(bindable, pageState)]);
  }, [params, pageState]);

  const handleParamsChange = React.useCallback((newParams: [string, BindableAttrValue<any>][]) => {
    setParams(newParams);
    const paramsObj: BindableAttrValues<any> = Object.fromEntries(newParams);
    setInput((existing) =>
      update(existing, {
        params: paramsObj,
      }),
    );
  }, []);

  const handleSave = React.useCallback(() => {
    onSave(input);
  }, [onSave, input]);

  const handleRemove = React.useCallback(() => {
    onRemove(node);
    onClose();
  }, [onRemove, node, onClose]);

  const paramsObject: Record<string, any> = React.useMemo(() => {
    const liveParamValues: [string, any][] = liveParams.map(([name, result]) => [
      name,
      result?.value,
    ]);
    return Object.fromEntries(liveParamValues);
  }, [liveParams]);

  const [previewQuery, setPreviewQuery] = React.useState<appDom.QueryNode<Q, P> | null>(null);
  const [previewParams, setPreviewParams] = React.useState(paramsObject);
  const queryPreview = client.useQuery(
    'execQuery',
    previewQuery ? [appId, previewQuery, previewParams] : null,
    { retry: false },
  );

  const handleUpdatePreview = React.useCallback(() => {
    setPreviewQuery(input);
    setPreviewParams(paramsObject);
  }, [input, paramsObject]);

  const isInputSaved = node === input;

  const handleClose = React.useCallback(() => {
    const ok = isInputSaved
      ? true
      : // eslint-disable-next-line no-alert
        window.confirm(
          'Are you sure you want to close the editor. All unsaved progress will be lost.',
        );

    if (ok) {
      onClose();
    }
  }, [onClose, isInputSaved]);

  if (!dataSourceId || !dataSource) {
    throw new Error(`DataSource "${dataSourceId}" not found`);
  }

  return (
    <Dialog fullWidth maxWidth="lg" open={open} onClose={handleClose} scroll="body">
      <DialogTitle>Edit Query ({node.id})</DialogTitle>
      <DialogContent>
        <Stack spacing={1} py={1} gap={2}>
          <Stack direction="row" gap={2}>
            <NodeNameEditor node={node} />
            <ConnectionSelect
              dataSource={dataSourceId}
              value={input.attributes.connectionId.value || null}
              onChange={handleConnectionChange}
            />
          </Stack>

          <Divider />
          <Typography>Parameters</Typography>
          <ParametersEditor
            value={params}
            onChange={handleParamsChange}
            globalScope={pageState}
            liveValue={liveParams}
          />
          <Divider />
          <Typography>Build query:</Typography>
          <dataSource.QueryEditor
            appId={appId}
            connectionId={connectionId}
            value={input.attributes.query.value}
            onChange={handleQueryChange}
            globalScope={{ query: paramsObject }}
          />
          <Divider />
          <Typography>Options:</Typography>
          <Grid container direction="row" spacing={1}>
            <Grid item xs={4}>
              <Stack direction="column">
                <FormControlLabel
                  control={
                    <Checkbox
                      checked={input.attributes.refetchOnWindowFocus?.value ?? true}
                      onChange={handleRefetchOnWindowFocusChange}
                    />
                  }
                  label="Refetch on window focus"
                />
                <FormControlLabel
                  control={
                    <Checkbox
                      checked={input.attributes.refetchOnReconnect?.value ?? true}
                      onChange={handleRefetchOnReconnectChange}
                    />
                  }
                  label="Refetch on network reconnect"
                />
                <TextField
                  InputProps={{
                    startAdornment: <InputAdornment position="start">s</InputAdornment>,
                  }}
                  sx={{ maxWidth: 300 }}
                  type="number"
                  label="Refetch interval"
                  value={refetchIntervalInSeconds(input.attributes.refetchInterval?.value) ?? ''}
                  onChange={handleRefetchIntervalChange}
                />
              </Stack>
            </Grid>
            <Grid item xs={6}>
              <Stack>
                <FormControlLabel
                  label="Transform API response"
                  control={
                    <Checkbox
                      checked={input.attributes.transformEnabled?.value ?? false}
                      onChange={handleTransformEnabledChange}
                      inputProps={{ 'aria-label': 'controlled' }}
                    />
                  }
                />

                <JsExpressionEditor
                  globalScope={{}}
                  value={input.attributes.transform?.value ?? '(data) => {\n  return data;\n}'}
                  onChange={handleTransformFnChange}
                  disabled={!input.attributes.transformEnabled?.value}
                />
              </Stack>
            </Grid>
          </Grid>
          <Divider />
          <Toolbar disableGutters>
            preview
            <LoadingButton
              sx={{ ml: 2 }}
              disabled={previewParams === paramsObject && previewQuery === input}
              loading={queryPreview.isLoading}
              loadingPosition="start"
              onClick={handleUpdatePreview}
              startIcon={<PlayArrowIcon />}
            >
              Run
            </LoadingButton>
          </Toolbar>
          {queryPreview.error ? <ErrorAlert error={queryPreview.error} /> : null}
          {queryPreview.isSuccess ? <JsonView src={queryPreview.data} /> : null}
        </Stack>
      </DialogContent>
      <DialogActions>
        <Button color="inherit" variant="text" onClick={handleClose}>
          Cancel
        </Button>
        <Button onClick={handleRemove}>Remove</Button>
        <Button disabled={isInputSaved} onClick={handleSave}>
          Save
        </Button>
      </DialogActions>
    </Dialog>
  );
}
Example #21
Source File: Welcome.tsx    From Cromwell with MIT License 4 votes vote down vote up
export default function WelcomePage() {
    const apiClient = getRestApiClient();
    const graphQLClient = getGraphQLClient();
    const history = useHistory();
    const [showPassword, setShowPassword] = useState(false);
    const [submitPressed, setSubmitPressed] = useState(false);
    const [emailInput, setEmailInput] = useState('');
    const [passwordInput, setPasswordInput] = useState('');
    const [nameInput, setNameInput] = useState('');
    const [avatarInput, setAvatarInput] = useState<string | null>(null);
    const [loading, setLoading] = useState(false);

    const handleClickShowPassword = () => {
        setShowPassword(!showPassword);
    }

    const handleSubmitClick = async () => {
        setSubmitPressed(true);

        if (!emailInput || !passwordInput || !nameInput) {
            return;
        }

        setLoading(true);

        try {
            await graphQLClient.createUser({
                fullName: nameInput,
                email: emailInput,
                password: passwordInput,
                avatar: avatarInput,
                role: 'administrator',
            });
        } catch (e) {
            toast.error('Failed to create user with provided credentials');
            console.error(e);
            setLoading(false);
            return;
        }

        try {
            await apiClient.setUpCms({
                url: window.location.origin,
            });

            await apiClient.login({
                email: emailInput,
                password: passwordInput
            });
        } catch (e) {
            console.error(e);
        }

        checkAuth();

        setLoading(false);
    }

    const checkAuth = async () => {
        const userInfo = await apiClient.getUserInfo({ disableLog: true });
        if (userInfo) {
            setStoreItem('userInfo', userInfo);
            history?.push?.(`/`);
        }
    }

    return (
        <div className={styles.WelcomePage}>
            <div className={styles.wrapper}>
                <img src="/admin/static/logo_small_black.svg" width="100px" className={styles.logo} />
                <h1 className={styles.title}>Welcome to Cromwell CMS!</h1>
                <h3 className={styles.subtitle}>Let&apos;s create your account</h3>
                <div className={styles.inputForm}>
                    <div className={styles.userMainInfo}>
                        <ImagePicker
                            toolTip="Pick avatar"
                            onChange={setAvatarInput}
                            value={avatarInput}
                            className={styles.avatar}
                            hideSrc
                        />
                        <CssTextField
                            label="Name"
                            value={nameInput}
                            onChange={e => setNameInput(e.target.value)}
                            fullWidth
                            variant="standard"
                            error={nameInput === '' && submitPressed}
                            helperText={nameInput === '' && submitPressed ? "This field is required" : undefined}
                            id="name-input"
                        />
                    </div>

                    <CssTextField
                        label="E-mail"
                        value={emailInput}
                        className={styles.textField}
                        onChange={e => setEmailInput(e.target.value)}
                        fullWidth
                        variant="standard"
                        error={emailInput === '' && submitPressed}
                        helperText={emailInput === '' && submitPressed ? "This field is required" : undefined}
                        id="email-input"
                    />
                    <CssTextField
                        label="Password"
                        type={showPassword ? 'text' : 'password'}
                        value={passwordInput}
                        onChange={e => setPasswordInput(e.target.value)}
                        className={styles.textField}
                        fullWidth
                        variant="standard"
                        error={passwordInput === '' && submitPressed}
                        helperText={passwordInput === '' && submitPressed ? "This field is required" : undefined}
                        id="password-input"
                        InputProps={{
                            endAdornment: (
                                <InputAdornment position="end">
                                    <IconButton
                                        aria-label="toggle password visibility"
                                        onClick={handleClickShowPassword}
                                        edge="end"
                                    >
                                        {showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
                                    </IconButton>
                                </InputAdornment>
                            ),
                        }}
                    />
                    <Button
                        onClick={handleSubmitClick}
                        className={styles.createBtn}
                        disabled={loading}
                        color="primary"
                        variant="contained"
                    >Create</Button>
                </div>
            </div>
            <LoadingStatus isActive={loading} />
        </div>
    );
}
Example #22
Source File: VolumesSummary.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
TenantVolumes = ({ classes, history, match }: ITenantVolumesProps) => {
  const dispatch = useDispatch();

  const loadingTenant = useSelector(
    (state: AppState) => state.tenants.loadingTenant
  );

  const [records, setRecords] = useState<IStoragePVCs[]>([]);
  const [filter, setFilter] = useState("");
  const [loading, setLoading] = useState<boolean>(true);
  const [selectedPVC, setSelectedPVC] = useState<any>(null);
  const [deleteOpen, setDeleteOpen] = useState<boolean>(false);

  const tenantName = match.params["tenantName"];
  const tenantNamespace = match.params["tenantNamespace"];

  useEffect(() => {
    if (loading) {
      api
        .invoke(
          "GET",
          `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/pvcs`
        )
        .then((res: IStoragePVCs) => {
          let volumes = get(res, "pvcs", []);
          setRecords(volumes ? volumes : []);
          setLoading(false);
        })
        .catch((err: ErrorResponseHandler) => {
          setLoading(false);
          dispatch(setErrorSnackMessage(err));
        });
    }
  }, [loading, dispatch, tenantName, tenantNamespace]);

  const confirmDeletePVC = (pvcItem: IStoragePVCs) => {
    const delPvc = {
      ...pvcItem,
      tenant: tenantName,
      namespace: tenantNamespace,
    };
    setSelectedPVC(delPvc);
    setDeleteOpen(true);
  };

  const filteredRecords: IStoragePVCs[] = records.filter((elementItem) =>
    elementItem.name.toLowerCase().includes(filter.toLowerCase())
  );

  const PVCViewAction = (PVC: IPodListElement) => {
    history.push(
      `/namespaces/${tenantNamespace}/tenants/${tenantName}/pvcs/${PVC.name}`
    );
    return;
  };

  const closeDeleteModalAndRefresh = (reloadData: boolean) => {
    setDeleteOpen(false);
    setLoading(true);
  };

  useEffect(() => {
    if (loadingTenant) {
      setLoading(true);
    }
  }, [loadingTenant]);

  return (
    <Fragment>
      {deleteOpen && (
        <DeletePVC
          deleteOpen={deleteOpen}
          selectedPVC={selectedPVC}
          closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
        />
      )}
      <Grid container spacing={1}>
        <h1 className={classes.sectionTitle}>Volumes</h1>
        <Grid item xs={12}>
          <TextField
            placeholder="Search Volumes (PVCs)"
            className={classes.searchField}
            id="search-resource"
            label=""
            InputProps={{
              disableUnderline: true,
              startAdornment: (
                <InputAdornment position="start">
                  <SearchIcon />
                </InputAdornment>
              ),
            }}
            onChange={(e) => {
              setFilter(e.target.value);
            }}
            variant="standard"
          />
        </Grid>
        <Grid item xs={12} className={classes.tableBlock}>
          <TableWrapper
            itemActions={[
              { type: "view", onClick: PVCViewAction },
              { type: "delete", onClick: confirmDeletePVC },
            ]}
            columns={[
              {
                label: "Name",
                elementKey: "name",
              },
              {
                label: "Status",
                elementKey: "status",
                width: 120,
              },
              {
                label: "Capacity",
                elementKey: "capacity",
                width: 120,
              },
              {
                label: "Storage Class",
                elementKey: "storageClass",
              },
            ]}
            isLoading={loading}
            records={filteredRecords}
            entityName="PVCs"
            idField="name"
            customPaperHeight={classes.tableWrapper}
          />
        </Grid>
      </Grid>
    </Fragment>
  );
}
Example #23
Source File: PodsSummary.tsx    From console with GNU Affero General Public License v3.0 4 votes vote down vote up
PodsSummary = ({ classes, match, history }: IPodsSummary) => {
  const dispatch = useDispatch();

  const loadingTenant = useSelector(
    (state: AppState) => state.tenants.loadingTenant
  );

  const [pods, setPods] = useState<IPodListElement[]>([]);
  const [loadingPods, setLoadingPods] = useState<boolean>(true);
  const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
  const [selectedPod, setSelectedPod] = useState<any>(null);
  const [filter, setFilter] = useState("");
  const tenantName = match.params["tenantName"];
  const tenantNamespace = match.params["tenantNamespace"];

  const podViewAction = (pod: IPodListElement) => {
    history.push(
      `/namespaces/${tenantNamespace}/tenants/${tenantName}/pods/${pod.name}`
    );
    return;
  };

  const closeDeleteModalAndRefresh = (reloadData: boolean) => {
    setDeleteOpen(false);
    setLoadingPods(true);
  };

  const confirmDeletePod = (pod: IPodListElement) => {
    pod.tenant = tenantName;
    pod.namespace = tenantNamespace;
    setSelectedPod(pod);
    setDeleteOpen(true);
  };

  const filteredRecords: IPodListElement[] = pods.filter((elementItem) =>
    elementItem.name.toLowerCase().includes(filter.toLowerCase())
  );

  const podTableActions = [
    { type: "view", onClick: podViewAction },
    { type: "delete", onClick: confirmDeletePod },
  ];

  useEffect(() => {
    if (loadingTenant) {
      setLoadingPods(true);
    }
  }, [loadingTenant]);

  useEffect(() => {
    if (loadingPods) {
      api
        .invoke(
          "GET",
          `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/pods`
        )
        .then((result: IPodListElement[]) => {
          for (let i = 0; i < result.length; i++) {
            let currentTime = (Date.now() / 1000) | 0;
            result[i].time = niceDays(
              (currentTime - parseInt(result[i].timeCreated)).toString()
            );
          }
          setPods(result);
          setLoadingPods(false);
        })
        .catch((err: ErrorResponseHandler) => {
          dispatch(
            setErrorSnackMessage({
              errorMessage: "Error loading pods",
              detailedError: err.detailedError,
            })
          );
        });
    }
  }, [loadingPods, tenantName, tenantNamespace, dispatch]);

  return (
    <Fragment>
      {deleteOpen && (
        <DeletePod
          deleteOpen={deleteOpen}
          selectedPod={selectedPod}
          closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
        />
      )}
      <h1 className={classes.sectionTitle}>Pods</h1>
      <Grid item xs={12} className={classes.actionsTray}>
        <TextField
          placeholder="Search Pods"
          className={classes.searchField}
          id="search-resource"
          label=""
          InputProps={{
            disableUnderline: true,
            startAdornment: (
              <InputAdornment position="start">
                <SearchIcon />
              </InputAdornment>
            ),
          }}
          onChange={(e) => {
            setFilter(e.target.value);
          }}
          variant="standard"
        />
      </Grid>
      <Grid item xs={12} className={classes.tableBlock}>
        <TableWrapper
          columns={[
            { label: "Name", elementKey: "name", width: 200 },
            { label: "Status", elementKey: "status" },
            { label: "Age", elementKey: "time" },
            { label: "Pod IP", elementKey: "podIP" },
            {
              label: "Restarts",
              elementKey: "restarts",
              renderFunction: (input) => {
                return input !== null ? input : 0;
              },
            },
            { label: "Node", elementKey: "node" },
          ]}
          isLoading={loadingPods}
          records={filteredRecords}
          itemActions={podTableActions}
          entityName="Pods"
          idField="name"
        />
      </Grid>
    </Fragment>
  );
}
Example #24
Source File: LoginPage.tsx    From Cromwell with MIT License 4 votes vote down vote up
LoginPage = () => {
    const apiClient = getRestApiClient();
    const [showPassword, setShowPassword] = useState(false);
    const [loading, setLoading] = useState(false);
    const [canValidate, setCanValidate] = useState(false);
    const [emailInput, setEmailInput] = useState('');
    const [codeInput, setCodeInput] = useState('');
    const [passwordInput, setPasswordInput] = useState('');
    const history = useHistory();
    const [formType, setFormType] = useState<TFromType>('sign-in');

    const handleClickShowPassword = () => {
        setShowPassword(!showPassword);
    }

    const handleLoginClick = async (event: TEvent) => {
        event?.preventDefault?.();
        setCanValidate(true);
        if (!emailInput || !passwordInput) return;

        setLoading(true);
        try {
            await apiClient.login({
                email: emailInput,
                password: passwordInput
            }, { disableLog: true });
            checkAuth(true);
        } catch (e) {
            if (e.statusCode === 0) {
                toast.error('Could not connect to the Server');
            } else {
                toast.error('Incorrect email or password');
            }
            console.error(e);
        }
        setLoading(false);
    }

    const checkAuth = async (showError?: boolean) => {
        const userInfo = await apiClient.getUserInfo({ disableLog: true });
        if (userInfo?.id) {
            if (!userInfo.role || !userInfo.email) {
                if (showError) toast.error('Incorrect user account');
                return;
            }
            setStoreItem('userInfo', userInfo);
            loginSuccess(userInfo);
        } else {
            if (showError) toast.error('Incorrect email or password');
        }
    }

    useEffect(() => {
        checkAuth();
    }, []);

    const loginSuccess = (userInfo: TUser) => {
        if (userInfo.role === 'administrator' || userInfo.role === 'guest') {
            history?.push?.(`/`);
        } else if (userInfo.role === 'author') {
            history?.push?.(`/post-list`);
        } else {
            toast.error('Access forbidden');
        }
    }

    const handleSubmit = (event: TEvent) => {
        setCanValidate(true);
        event?.preventDefault?.();
        if (formType === 'sign-in') handleLoginClick(event);
        if (formType === 'forgot-pass') handleForgotPass();
        if (formType === 'reset-pass') handleResetPass();
    }

    const handleGoToForgotPass = () => {
        setFormType('forgot-pass');
    }

    const handleForgotPass = async () => {
        if (!emailInput || emailInput == '') return;
        setLoading(true);

        try {
            const success = await apiClient?.forgotPassword({ email: emailInput }, { disableLog: true });
            if (success) {
                toast.success('We sent you an e-mail');
                setFormType('reset-pass');
            } else {
                throw new Error('!success');
            }
        } catch (e) {
            console.error(e);
            let info = e?.message;
            try {
                info = JSON.parse(e.message)
            } catch (e) { }

            if (info?.statusCode === 429) {
            } else {
                toast.error?.('Incorrect e-mail or user was not found');
            }
        }
        setLoading(false);
    }

    const handleResetPass = async () => {
        if (!emailInput || !codeInput || !passwordInput) return;

        setLoading(true);
        try {
            const success = await apiClient?.resetPassword({
                email: emailInput,
                code: codeInput,
                newPassword: passwordInput,
            });
            if (success) {
                toast.success('Password has been changed.');
                setFormType('sign-in');
            } else {
                throw new Error('!success');
            }
        } catch (e) {
            console.error(e);
            let info = e?.message;
            try {
                info = JSON.parse(e.message)
            } catch (e) { }

            if (info?.statusCode === 429) {

            } else if (info?.statusCode === 417) {
                toast.error?.('Exceeded reset password attempts');
                setCodeInput('')
                setFormType('sign-in');
            } else {
                toast.error?.('Incorrect e-mail or user was not found');
            }
        }
        setLoading(false);
    }

    return (
        <div className={styles.LoginPage}>
            <form className={styles.loginForm} onSubmit={handleSubmit}>
                <img src="/admin/static/logo_small_black.svg" width="100px" className={styles.logo} />
                {formType === 'sign-in' && (
                    <>
                        <TextField
                            label="E-mail"
                            value={emailInput}
                            className={styles.textField}
                            onChange={e => setEmailInput(e.target.value)}
                            fullWidth
                            variant="standard"
                            id="email-input"
                            error={canValidate && !emailInput}
                        />
                        <TextField
                            label="Password"
                            type={showPassword ? 'text' : 'password'}
                            value={passwordInput}
                            onChange={e => setPasswordInput(e.target.value)}
                            className={styles.textField}
                            fullWidth
                            id="password-input"
                            variant="standard"
                            error={canValidate && !passwordInput}
                            InputProps={{
                                endAdornment: (
                                    <InputAdornment position="end">
                                        <IconButton
                                            aria-label="toggle password visibility"
                                            onClick={handleClickShowPassword}
                                            edge="end"
                                        >
                                            {showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
                                        </IconButton>
                                    </InputAdornment>
                                ),
                            }}
                        />
                        <Button
                            type="submit"
                            onClick={handleLoginClick}
                            className={styles.loginBtn}
                            disabled={loading}
                            variant="outlined"
                            color="inherit">Login</Button>
                        <p onClick={handleGoToForgotPass} className={styles.forgotPassText}>Forgot password?</p>
                    </>
                )}
                {formType === 'forgot-pass' && (
                    <>
                        <TextField
                            label="E-mail"
                            value={emailInput}
                            className={styles.textField}
                            onChange={e => setEmailInput(e.target.value)}
                            fullWidth
                            variant="standard"
                            id="email-input"
                            error={canValidate && !passwordInput}
                        />
                        <Button
                            type="submit"
                            onClick={handleForgotPass}
                            className={styles.loginBtn}
                            disabled={loading}
                            variant="outlined"
                            color="inherit">Reset password</Button>
                        <p onClick={() => setFormType('sign-in')} className={styles.forgotPassText}>back</p>
                    </>
                )}
                {formType === 'reset-pass' && (
                    <>
                        <p className={styles.resetPassInstructions}>We sent you an e-mail with reset code. Copy the code below and create a new password</p>
                        <TextField
                            label="Code"
                            value={codeInput}
                            className={styles.textField}
                            onChange={e => setCodeInput(e.target.value)}
                            fullWidth
                            variant="standard"
                            id="code-input"
                            error={canValidate && !codeInput}
                        />
                        <TextField
                            label="New password"
                            type={showPassword ? 'text' : 'password'}
                            value={passwordInput}
                            onChange={e => setPasswordInput(e.target.value)}
                            className={styles.textField}
                            fullWidth
                            id="password-input"
                            error={canValidate && !passwordInput}
                            variant="standard"
                            InputProps={{
                                endAdornment: (
                                    <InputAdornment position="end">
                                        <IconButton
                                            aria-label="toggle password visibility"
                                            onClick={handleClickShowPassword}
                                            edge="end"
                                        >
                                            {showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
                                        </IconButton>
                                    </InputAdornment>
                                ),
                            }}
                        />
                        <Button
                            type="submit"
                            onClick={handleResetPass}
                            className={styles.loginBtn}
                            disabled={loading}
                            variant="outlined"
                            color="inherit">Change password</Button>
                    </>
                )}
            </form>
        </div>
    )
}
Example #25
Source File: User.tsx    From Cromwell with MIT License 4 votes vote down vote up
export default function UserPage() {
    const { id: userId } = useParams<{ id: string }>();
    const client = getGraphQLClient();
    const [notFound, setNotFound] = useState(false);
    const [passwordInput, setPasswordInput] = useState('');
    const history = useHistory();
    const [userData, setUserData] = useState<TUser | undefined | null>(null);
    const [showPassword, setShowPassword] = useState(false);
    const isNew = userId === 'new';
    const [canValidate, setCanValidate] = useState(false);

    // Support old and new address format
    const { addressString, addressJson } = parseAddress(userData?.address);

    const handleClickShowPassword = () => {
        setShowPassword(!showPassword);
    }

    const getUser = async (id: number) => {
        let data: TUser | undefined;
        try {
            data = await client?.getUserById(id,
                gql`
                fragment AdminPanelUserFragment on User {
                    id
                    slug
                    createDate
                    updateDate
                    isEnabled
                    pageTitle
                    pageDescription
                    meta {
                        keywords
                    }
                    fullName
                    email
                    avatar
                    bio
                    phone
                    address
                    role
                    customMeta (keys: ${JSON.stringify(getCustomMetaKeysFor(EDBEntity.User))})
                }`, 'AdminPanelUserFragment');
        } catch (e) { console.error(e) }

        return data;
    }


    const init = async () => {
        if (userId && !isNew) {
            const data = await getUser(parseInt(userId));
            if (data?.id) {
                setUserData(data);
            } else setNotFound(true);

        } else if (isNew) {
            setUserData({} as any);
        }
    }

    useEffect(() => {
        init();
    }, []);

    const refetchMeta = async () => {
        if (!userId) return;
        const data = await getUser(parseInt(userId));
        return data?.customMeta;
    };


    const handleInputChange = (prop: keyof TUser, val: any) => {
        if (userData) {
            setUserData((prevData) => {
                const newData = Object.assign({}, prevData);
                (newData[prop] as any) = val;
                return newData;
            });
        }
    }

    const getInput = async (): Promise<TUpdateUser> => ({
        slug: userData.slug,
        pageTitle: userData.pageTitle,
        pageDescription: userData.pageDescription,
        fullName: userData.fullName,
        email: userData.email,
        avatar: userData.avatar,
        bio: userData.bio,
        phone: userData.phone,
        address: userData.address,
        role: userData.role,
        customMeta: Object.assign({}, userData.customMeta, await getCustomMetaFor(EDBEntity.User)),
    });

    const handleSave = async () => {
        setCanValidate(true);
        const inputData = await getInput();

        if (!inputData.email || !inputData.fullName || !inputData.role) return;

        if (isNew) {
            if (!passwordInput) return;
            try {
                const createInput: TCreateUser = {
                    ...inputData,
                    password: passwordInput
                }
                const newData = await client?.createUser(createInput);
                toast.success('Created user');
                history.replace(`${userPageInfo.baseRoute}/${newData.id}`);
                setUserData(newData);
            } catch (e) {
                toast.error('Failed to create user');
                console.error(e);
            }

        } else if (userData?.id) {
            try {
                await client?.updateUser(userData.id, inputData);
                const newData = await getUser(parseInt(userId));
                setUserData(newData);
                toast.success('Saved!');

                const currentUser: TUser | undefined = getStoreItem('userInfo');
                if (currentUser?.id && currentUser.id === newData.id) {
                    setStoreItem('userInfo', userData);
                }

            } catch (e) {
                toast.error('Failed to save');
                console.error(e);
            }
        }
        setCanValidate(false);
    }

    if (notFound) {
        return (
            <div className={styles.UserPage}>
                <div className={styles.notFoundPage}>
                    <p className={styles.notFoundText}>User not found</p>
                </div>
            </div>
        )
    }

    return (
        <div className={styles.UserPage}>
            <div className={styles.header}>
                <div className={styles.headerLeft}>
                    <IconButton
                        onClick={() => window.history.back()}
                    >
                        <ArrowBackIcon style={{ fontSize: '18px' }} />
                    </IconButton>
                    <p className={commonStyles.pageTitle}>account</p>
                </div>
                <div className={styles.headerActions}>
                    <Button variant="contained" color="primary"
                        className={styles.saveBtn}
                        size="small"
                        onClick={handleSave}>
                        Save</Button>
                </div>
            </div>
            <div className={styles.fields}>
                <Grid container spacing={3}>
                    <Grid item xs={12} sm={6} style={{ display: 'flex', alignItems: 'flex-end' }}>
                        <TextField
                            label="Name"
                            value={userData?.fullName || ''}
                            fullWidth
                            variant="standard"
                            className={styles.field}
                            onChange={(e) => { handleInputChange('fullName', e.target.value) }}
                            error={canValidate && !userData?.fullName}
                        />
                    </Grid>
                    <Grid item xs={12} sm={6}>
                        <ImagePicker
                            label="Avatar"
                            onChange={(val) => { handleInputChange('avatar', val) }}
                            value={userData?.avatar ?? null}
                            className={styles.imageField}
                            classes={{ image: styles.image }}
                            backgroundSize='cover'
                            width="50px"
                            height="50px"
                            showRemove
                        />
                    </Grid>
                    <Grid item xs={12} sm={6}>
                        <TextField
                            label="E-mail"
                            value={userData?.email || ''}
                            fullWidth
                            variant="standard"
                            className={styles.field}
                            onChange={(e) => { handleInputChange('email', e.target.value) }}
                            error={canValidate && !userData?.email}
                        />
                    </Grid>
                    {isNew && (
                        <Grid item xs={12} sm={6}>
                            <TextField
                                label="Password"
                                value={passwordInput || ''}
                                type={showPassword ? 'text' : 'password'}
                                fullWidth
                                variant="standard"
                                className={styles.field}
                                onChange={(e) => { setPasswordInput(e.target.value) }}
                                error={canValidate && !passwordInput}
                                InputProps={{
                                    endAdornment: (
                                        <InputAdornment position="end">
                                            <IconButton
                                                aria-label="toggle password visibility"
                                                onClick={handleClickShowPassword}
                                                edge="end"
                                            >
                                                {showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
                                            </IconButton>
                                        </InputAdornment>
                                    ),
                                }}
                            />
                        </Grid>
                    )}
                    <Grid item xs={12} sm={6} display="flex" alignItems="flex-end">
                        <Select
                            fullWidth
                            variant="standard"
                            label="Role"
                            value={(userData?.role ?? '') as TUserRole}
                            onChange={(event: SelectChangeEvent<unknown>) => {
                                handleInputChange('role', event.target.value)
                            }}
                            error={canValidate && !userData?.role}
                            options={userRoles.map(role => ({ label: role, value: role }))}
                        />
                    </Grid>
                    <Grid item xs={12} sm={12}>
                        <TextField
                            label="Bio"
                            value={userData?.bio || ''}
                            fullWidth
                            variant="standard"
                            multiline
                            className={styles.field}
                            onChange={(e) => { handleInputChange('bio', e.target.value) }}
                        />
                    </Grid>
                    {!addressJson && (
                        <Grid item xs={12} sm={12}>
                            <TextField label="Address"
                                value={addressString || ''}
                                fullWidth
                                variant="standard"
                                className={styles.field}
                                onChange={(e) => { handleInputChange('address', e.target.value) }}
                            />
                        </Grid>
                    )}
                    {addressJson && (
                        Object.entries<any>(addressJson).map(([fieldKey, value]) => {
                            return (
                                <Grid item xs={12} sm={6} key={fieldKey}>
                                    <TextField label={fieldKey}
                                        value={value || ''}
                                        fullWidth
                                        variant="standard"
                                        className={styles.field}
                                        onChange={(e) => {
                                            const newVal = e.target.value;
                                            handleInputChange('address', JSON.stringify({
                                                ...addressJson,
                                                [fieldKey]: newVal,
                                            }))
                                        }}
                                    />
                                </Grid>
                            )
                        }))}
                    <Grid item xs={12} sm={6}>
                        <TextField
                            label="Phone"
                            value={userData?.phone || ''}
                            fullWidth
                            variant="standard"
                            className={styles.field}
                            onChange={(e) => { handleInputChange('phone', e.target.value) }}
                        />
                    </Grid>
                    <Grid item xs={12} sm={12}>
                        {userData && (
                            <RenderCustomFields
                                entityType={EDBEntity.User}
                                entityData={userData}
                                refetchMeta={refetchMeta}
                            />
                        )}
                    </Grid>
                </Grid>
            </div>
        </div>
    );
}
Example #26
Source File: AccountList.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function AccountList({ group }) {
    const [speedDialOpen, setSpeedDialOpen] = useState(false);
    const toggleSpeedDial = () => setSpeedDialOpen((currValue) => !currValue);

    const [showPersonalAccountCreationModal, setShowPersonalAccountCreationModal] = useState(false);
    const [showClearingAccountCreationModal, setShowClearingAccountCreationModal] = useState(false);

    const [activeTab, setActiveTab] = useState("personal");
    const [searchValuePersonal, setSearchValuePersonal] = useState("");
    const [searchValueClearing, setSearchValueClearing] = useState("");

    const [showPersonalAccountEditModal, setShowPersonalAccountEditModal] = useState(false);
    const [showClearingAccountEditModal, setShowClearingAccountEditModal] = useState(false);
    const [clearingAccountToCopy, setClearingAccountToCopy] = useState(undefined);
    const [accountToEdit, setAccountToEdit] = useState(null);
    const [clearingAccountToEdit, setClearingAccountToEdit] = useState(null);
    const setAccounts = useSetRecoilState(groupAccounts(group.id));
    const personalAccounts = useRecoilValue(personalAccountsSeenByUser(group.id));
    const clearingAccounts = useRecoilValue(clearingAccountsSeenByUser(group.id));
    const allAccounts = useRecoilValue(accountsSeenByUser(group.id));
    const [accountToDelete, setAccountToDelete] = useState(null);
    const userPermissions = useRecoilValue(currUserPermissions(group.id));
    const currentUser = useRecoilValue(userData);
    const memberIDToUsername = useRecoilValue(groupMemberIDsToUsername(group.id));

    const [filteredPersonalAccounts, setFilteredPersonalAccounts] = useState([]);
    const [filteredClearingAccounts, setFilteredClearingAccounts] = useState([]);
    useEffect(() => {
        if (searchValuePersonal != null && searchValuePersonal !== "") {
            setFilteredPersonalAccounts(
                personalAccounts.filter((t) => {
                    return (
                        t.name.toLowerCase().includes(searchValuePersonal.toLowerCase()) ||
                        t.description.toLowerCase().includes(searchValuePersonal.toLowerCase())
                    );
                })
            );
        } else {
            return setFilteredPersonalAccounts(personalAccounts);
        }
    }, [personalAccounts, searchValuePersonal, setFilteredPersonalAccounts]);

    useEffect(() => {
        if (searchValueClearing != null && searchValueClearing !== "") {
            setFilteredClearingAccounts(
                clearingAccounts.filter((t) => {
                    return (
                        t.name.toLowerCase().includes(searchValueClearing.toLowerCase()) ||
                        t.description.toLowerCase().includes(searchValueClearing.toLowerCase())
                    );
                })
            );
        } else {
            return setFilteredClearingAccounts(clearingAccounts);
        }
    }, [clearingAccounts, searchValueClearing, setFilteredClearingAccounts]);

    useTitle(`${group.name} - Accounts`);

    const openAccountEdit = (account) => {
        setAccountToEdit(account);
        setShowPersonalAccountEditModal(true);
    };

    const closeAccountEdit = (evt, reason) => {
        if (reason !== "backdropClick") {
            setShowPersonalAccountEditModal(false);
            setAccountToEdit(null);
        }
    };

    const openClearingAccountEdit = (account) => {
        setClearingAccountToEdit(account);
        setShowClearingAccountEditModal(true);
    };

    const closeClearingAccountEdit = (evt, reason) => {
        if (reason !== "backdropClick") {
            setShowClearingAccountEditModal(false);
            setClearingAccountToEdit(null);
        }
    };

    const confirmDeleteAccount = () => {
        if (accountToDelete !== null) {
            deleteAccount({ accountID: accountToDelete })
                .then((account) => {
                    updateAccount(account, setAccounts);
                    setAccountToDelete(null);
                })
                .catch((err) => {
                    toast.error(err);
                });
        }
    };

    const openCreateDialog = () => {
        setClearingAccountToCopy(undefined);
        setShowClearingAccountCreationModal(true);
    };

    const copyClearingAccount = (account) => {
        setClearingAccountToCopy(account);
        setShowClearingAccountCreationModal(true);
    };

    return (
        <>
            <MobilePaper>
                <TabContext value={activeTab}>
                    <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
                        <TabList onChange={(e, newValue) => setActiveTab(newValue)} centered>
                            <Tab
                                value="personal"
                                label={
                                    <TextBadge badgeContent={personalAccounts.length} color="primary">
                                        <span>Personal Accounts</span>
                                    </TextBadge>
                                }
                            />
                            <Tab
                                label={
                                    <TextBadge badgeContent={clearingAccounts.length} color="primary">
                                        <span>Clearing Accounts</span>
                                    </TextBadge>
                                }
                                value="clearing"
                            />
                        </TabList>
                    </Box>
                    <TabPanel value="personal">
                        <List>
                            {personalAccounts.length === 0 ? (
                                <Alert severity="info">No Accounts</Alert>
                            ) : (
                                <>
                                    <ListItem>
                                        <Input
                                            value={searchValuePersonal}
                                            onChange={(e) => setSearchValuePersonal(e.target.value)}
                                            placeholder="Search…"
                                            inputProps={{
                                                "aria-label": "search",
                                            }}
                                            endAdornment={
                                                <InputAdornment position="end">
                                                    <IconButton
                                                        aria-label="clear search input"
                                                        onClick={(e) => setSearchValuePersonal("")}
                                                        edge="end"
                                                    >
                                                        <Clear />
                                                    </IconButton>
                                                </InputAdornment>
                                            }
                                        />
                                    </ListItem>
                                    <Divider />
                                    {filteredPersonalAccounts.map((account) => (
                                        <ListItem sx={{ padding: 0 }} key={account.id}>
                                            <ListItemLink to={`/groups/${group.id}/accounts/${account.id}`}>
                                                <ListItemText
                                                    primary={
                                                        <div>
                                                            <span>{account.name}</span>
                                                            {account.owning_user_id === currentUser.id ? (
                                                                <span>
                                                                    , owned by{" "}
                                                                    <Chip
                                                                        size="small"
                                                                        component="span"
                                                                        color="primary"
                                                                        label="you"
                                                                    />
                                                                </span>
                                                            ) : (
                                                                account.owning_user_id !== null && (
                                                                    <span>
                                                                        , owned by{" "}
                                                                        <Chip
                                                                            size="small"
                                                                            component="span"
                                                                            color="secondary"
                                                                            label={
                                                                                memberIDToUsername[
                                                                                    account.owning_user_id
                                                                                ]
                                                                            }
                                                                        />
                                                                    </span>
                                                                )
                                                            )}
                                                        </div>
                                                    }
                                                    secondary={account.description}
                                                />
                                            </ListItemLink>
                                            {userPermissions.can_write && (
                                                <ListItemSecondaryAction>
                                                    <IconButton
                                                        color="primary"
                                                        onClick={() => openAccountEdit(account)}
                                                    >
                                                        <Edit />
                                                    </IconButton>
                                                    <IconButton
                                                        color="error"
                                                        onClick={() => setAccountToDelete(account.id)}
                                                    >
                                                        <Delete />
                                                    </IconButton>
                                                </ListItemSecondaryAction>
                                            )}
                                        </ListItem>
                                    ))}
                                </>
                            )}
                        </List>
                        {userPermissions.can_write && (
                            <>
                                <Grid container justifyContent="center">
                                    <Tooltip title="Create Personal Account">
                                        <IconButton
                                            color="primary"
                                            onClick={() => setShowPersonalAccountCreationModal(true)}
                                        >
                                            <Add />
                                        </IconButton>
                                    </Tooltip>
                                </Grid>
                                <CreateAccountModal
                                    show={showPersonalAccountCreationModal}
                                    onClose={(evt, reason) => {
                                        if (reason !== "backdropClick") {
                                            setShowPersonalAccountCreationModal(false);
                                        }
                                    }}
                                    group={group}
                                />
                                <EditAccountModal
                                    show={showPersonalAccountEditModal}
                                    onClose={closeAccountEdit}
                                    account={accountToEdit}
                                    group={group}
                                />
                            </>
                        )}
                    </TabPanel>
                    <TabPanel value="clearing">
                        <List>
                            {clearingAccounts.length === 0 ? (
                                <Alert severity="info">No Accounts</Alert>
                            ) : (
                                <>
                                    <ListItem>
                                        <Input
                                            value={searchValueClearing}
                                            onChange={(e) => setSearchValueClearing(e.target.value)}
                                            placeholder="Search…"
                                            inputProps={{
                                                "aria-label": "search",
                                            }}
                                            endAdornment={
                                                <InputAdornment position="end">
                                                    <IconButton
                                                        aria-label="clear search input"
                                                        onClick={(e) => setSearchValueClearing("")}
                                                        edge="end"
                                                    >
                                                        <Clear />
                                                    </IconButton>
                                                </InputAdornment>
                                            }
                                        />
                                    </ListItem>
                                    <Divider />
                                    {filteredClearingAccounts.map((account) => (
                                        <ListItem sx={{ padding: 0 }} key={account.id}>
                                            <ListItemLink to={`/groups/${group.id}/accounts/${account.id}`}>
                                                <ListItemText primary={account.name} secondary={account.description} />
                                            </ListItemLink>
                                            {userPermissions.can_write && (
                                                <ListItemSecondaryAction>
                                                    <IconButton
                                                        color="primary"
                                                        onClick={() => openClearingAccountEdit(account)}
                                                    >
                                                        <Edit />
                                                    </IconButton>
                                                    <IconButton
                                                        color="primary"
                                                        onClick={() => copyClearingAccount(account)}
                                                    >
                                                        <ContentCopy />
                                                    </IconButton>
                                                    <IconButton
                                                        color="error"
                                                        onClick={() => setAccountToDelete(account.id)}
                                                    >
                                                        <Delete />
                                                    </IconButton>
                                                </ListItemSecondaryAction>
                                            )}
                                        </ListItem>
                                    ))}
                                </>
                            )}
                        </List>
                        {userPermissions.can_write && (
                            <>
                                <Grid container justifyContent="center">
                                    <Tooltip title="Create Clearing Account">
                                        <IconButton color="primary" onClick={openCreateDialog}>
                                            <Add />
                                        </IconButton>
                                    </Tooltip>
                                </Grid>
                                <CreateClearingAccountModal
                                    show={showClearingAccountCreationModal}
                                    onClose={(evt, reason) => {
                                        if (reason !== "backdropClick") {
                                            setShowClearingAccountCreationModal(false);
                                        }
                                    }}
                                    initialValues={clearingAccountToCopy}
                                    group={group}
                                />
                                <EditClearingAccountModal
                                    show={showClearingAccountEditModal}
                                    onClose={closeClearingAccountEdit}
                                    account={clearingAccountToEdit}
                                    group={group}
                                />
                            </>
                        )}
                    </TabPanel>
                </TabContext>
            </MobilePaper>
            {userPermissions.can_write && (
                <>
                    <SpeedDial
                        ariaLabel="Create Account"
                        sx={{ position: "fixed", bottom: 20, right: 20 }}
                        icon={<SpeedDialIcon />}
                        // onClose={() => setSpeedDialOpen(false)}
                        // onOpen={() => setSpeedDialOpen(true)}
                        onClick={toggleSpeedDial}
                        open={speedDialOpen}
                    >
                        <SpeedDialAction
                            icon={<PersonalAccountIcon />}
                            tooltipTitle="Personal"
                            tooltipOpen
                            onClick={() => setShowPersonalAccountCreationModal(true)}
                        />
                        <SpeedDialAction
                            icon={<ClearingAccountIcon />}
                            tooltipTitle="Clearing"
                            tooltipOpen
                            onClick={openCreateDialog}
                        />
                    </SpeedDial>

                    <Dialog maxWidth="xs" aria-labelledby="confirmation-dialog-title" open={accountToDelete !== null}>
                        <DialogTitle id="confirmation-dialog-title">Confirm delete account</DialogTitle>
                        <DialogContent dividers>
                            Are you sure you want to delete the account "
                            {allAccounts.find((acc) => acc.id === accountToDelete)?.name}"
                        </DialogContent>
                        <DialogActions>
                            <Button autoFocus onClick={() => setAccountToDelete(null)} color="primary">
                                Cancel
                            </Button>
                            <Button onClick={confirmDeleteAccount} color="error">
                                Ok
                            </Button>
                        </DialogActions>
                    </Dialog>
                </>
            )}
        </>
    );
}
Example #27
Source File: PurchaseDebitorShares.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function PurchaseDebitorShares({ group, transaction, showPositions = false }) {
    const classes = useStyles();
    const isSmallScreen = useMediaQuery((theme: Theme) => theme.breakpoints.down("sm"));

    const accounts = useRecoilValue(accountsSeenByUser(group.id));

    const [searchValue, setSearchValue] = useState("");
    const [filteredAccounts, setFilteredAccounts] = useState([]);

    const [showAdvanced, setShowAdvanced] = useState(false);

    const transactionHasPositions =
        transaction.positions != null && transaction.positions.find((item) => !item.deleted) !== undefined;
    const setLocalTransactionDetails = useSetRecoilState(pendingTransactionDetailChanges(transaction.id));

    useEffect(() => {
        for (const share of Object.values(transaction.debitor_shares)) {
            if (share !== 1) {
                setShowAdvanced(true);
                break;
            }
        }
    }, [transaction]);

    useEffect(() => {
        if (searchValue != null && searchValue !== "") {
            setFilteredAccounts(
                accounts.filter((acc) => {
                    return acc.name.toLowerCase().includes(searchValue.toLowerCase());
                })
            );
        } else {
            setFilteredAccounts(accounts);
        }
    }, [searchValue, accounts]);

    const debitorShareValueForAccount = (accountID) => {
        return transaction.debitor_shares && transaction.debitor_shares.hasOwnProperty(accountID)
            ? transaction.debitor_shares[accountID]
            : 0;
    };

    const debitorValueForAccount = (accountID) => {
        if (!transaction.account_balances.hasOwnProperty(accountID)) {
            return 0.0;
        }
        return transaction.account_balances[accountID].common_debitors;
    };

    const positionValueForAccount = (accountID) => {
        if (!transaction.account_balances.hasOwnProperty(accountID)) {
            return 0.0;
        }
        return transaction.account_balances[accountID].positions;
    };

    const updateDebShare = (accountID, value) => {
        if (value === 0) {
            setLocalTransactionDetails((currState) => {
                let newDebitorShares;
                if (currState.debitor_shares === undefined) {
                    newDebitorShares = {
                        ...transaction.debitor_shares,
                    };
                } else {
                    newDebitorShares = {
                        ...currState.debitor_shares,
                    };
                }
                delete newDebitorShares[accountID];
                return {
                    ...currState,
                    debitor_shares: newDebitorShares,
                };
            });
        } else {
            setLocalTransactionDetails((currState) => {
                let newDebitorShares;
                if (currState.debitor_shares === undefined) {
                    newDebitorShares = {
                        ...transaction.debitor_shares,
                        [accountID]: value,
                    };
                } else {
                    newDebitorShares = {
                        ...currState.debitor_shares,
                        [accountID]: value,
                    };
                }
                return {
                    ...currState,
                    debitor_shares: newDebitorShares,
                };
            });
        }
    };

    return (
        <div>
            <Box className={classes.listItem}>
                <Grid container direction="row" justifyContent="space-between">
                    <Typography variant="subtitle1" className={classes.checkboxLabel}>
                        <Box sx={{ display: "flex", alignItems: "flex-end" }}>For whom</Box>
                    </Typography>
                    {transaction.is_wip && (
                        <FormControlLabel
                            control={<Checkbox name={`show-advanced`} />}
                            checked={showAdvanced}
                            onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
                                setShowAdvanced(event.target.checked)
                            }
                            label="Advanced"
                        />
                    )}
                </Grid>
            </Box>
            <Divider variant="middle" className={classes.divider} />
            <TableContainer sx={{ maxHeight: { md: 400 } }}>
                <Table size="small" stickyHeader>
                    <TableHead>
                        <TableRow>
                            <TableCell>
                                {isSmallScreen ? (
                                    "Account"
                                ) : (
                                    <TextField
                                        placeholder="Search ..."
                                        margin="none"
                                        size="small"
                                        value={searchValue}
                                        onChange={(e) => setSearchValue(e.target.value)}
                                        variant="standard"
                                        InputProps={{
                                            startAdornment: (
                                                <InputAdornment position="start">
                                                    <SearchIcon />
                                                </InputAdornment>
                                            ),
                                            endAdornment: (
                                                <InputAdornment position="end">
                                                    <IconButton
                                                        aria-label="clear search input"
                                                        onClick={(e) => setSearchValue("")}
                                                        edge="end"
                                                    >
                                                        <Clear />
                                                    </IconButton>
                                                </InputAdornment>
                                            ),
                                        }}
                                    />
                                )}
                            </TableCell>
                            <TableCell width="100px">Shares</TableCell>
                            {showPositions || transactionHasPositions ? (
                                <>
                                    <TableCell width="100px" align="right">
                                        Positions
                                    </TableCell>
                                    <TableCell width="3px" align="center">
                                        +
                                    </TableCell>
                                    <TableCell width="100px" align="right">
                                        Shared + Rest
                                    </TableCell>
                                    <TableCell width="3px" align="center">
                                        =
                                    </TableCell>
                                    <TableCell width="100px" align="right">
                                        Total
                                    </TableCell>
                                </>
                            ) : (
                                <TableCell width="100px" align="right">
                                    Shared
                                </TableCell>
                            )}
                        </TableRow>
                    </TableHead>
                    <TableBody>
                        {filteredAccounts.map((account) => (
                            <AccountTableRow
                                key={account.id}
                                transaction={transaction}
                                account={account}
                                debitorValueForAccount={debitorValueForAccount}
                                debitorShareValueForAccount={debitorShareValueForAccount}
                                positionValueForAccount={positionValueForAccount}
                                showAdvanced={showAdvanced}
                                showPositions={showPositions}
                                updateDebShare={updateDebShare}
                            />
                        ))}
                    </TableBody>
                </Table>
            </TableContainer>
        </div>
    );
}
Example #28
Source File: TransactionList.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function TransactionList({ group }) {
    const [speedDialOpen, setSpeedDialOpen] = useState(false);
    const toggleSpeedDial = () => setSpeedDialOpen((currValue) => !currValue);

    const [showTransferCreateDialog, setShowTransferCreateDialog] = useState(false);
    const [showPurchaseCreateDialog, setShowPurchaseCreateDialog] = useState(false);
    const transactions = useRecoilValue(transactionsSeenByUser(group.id));
    const currentUser = useRecoilValue(userData);
    const userPermissions = useRecoilValue(currUserPermissions(group.id));
    const userAccounts = useRecoilValue(accountsOwnedByUser({ groupID: group.id, userID: currentUser.id }));
    const groupAccountMap = useRecoilValue(accountIDsToName(group.id));

    const theme: Theme = useTheme();
    const isSmallScreen = useMediaQuery(theme.breakpoints.down("md"));

    const [filteredTransactions, setFilteredTransactions] = useState([]);

    const [searchValue, setSearchValue] = useState("");

    const [sortMode, setSortMode] = useState("last_changed"); // last_changed, description, value, billed_at

    useEffect(() => {
        let filtered = transactions;
        if (searchValue != null && searchValue !== "") {
            filtered = transactions.filter((t) => t.filter(searchValue, groupAccountMap));
        }
        filtered = [...filtered].sort(getTransactionSortFunc(sortMode));

        setFilteredTransactions(filtered);
    }, [searchValue, setFilteredTransactions, sortMode, transactions, userAccounts]);

    useTitle(`${group.name} - Transactions`);

    const openPurchaseCreateDialog = () => {
        setShowPurchaseCreateDialog(true);
    };

    const openTransferCreateDialog = () => {
        setShowTransferCreateDialog(true);
    };

    return (
        <>
            <MobilePaper>
                <Box
                    sx={{
                        display: "flex",
                        flexDirection: { xs: "column", sm: "column", md: "row", lg: "row" },
                        alignItems: { md: "flex-end" },
                        pl: "16px",
                        justifyContent: "space-between",
                    }}
                >
                    <Box sx={{ display: "flex-item" }}>
                        <Box sx={{ minWidth: "56px", pt: "16px" }}>
                            <SearchIcon sx={{ color: "action.active" }} />
                        </Box>
                        <Input
                            value={searchValue}
                            onChange={(e) => setSearchValue(e.target.value)}
                            placeholder="Search…"
                            inputProps={{
                                "aria-label": "search",
                            }}
                            sx={{ pt: "16px" }}
                            endAdornment={
                                <InputAdornment position="end">
                                    <IconButton
                                        aria-label="clear search input"
                                        onClick={(e) => setSearchValue("")}
                                        edge="end"
                                    >
                                        <Clear />
                                    </IconButton>
                                </InputAdornment>
                            }
                        />
                        <FormControl variant="standard" sx={{ minWidth: 120, ml: 3 }}>
                            <InputLabel id="select-sort-by-label">Sort by</InputLabel>
                            <Select
                                labelId="select-sort-by-label"
                                id="select-sort-by"
                                label="Sort by"
                                onChange={(evt) => setSortMode(evt.target.value)}
                                value={sortMode}
                            >
                                <MenuItem value="last_changed">Last changed</MenuItem>
                                <MenuItem value="description">Description</MenuItem>
                                <MenuItem value="value">Value</MenuItem>
                                <MenuItem value="billed_at">Date</MenuItem>
                            </Select>
                        </FormControl>
                    </Box>
                    {!isSmallScreen && (
                        <Box sx={{ display: "flex-item" }}>
                            <div style={{ padding: "8px" }}>
                                <Add color="primary" />
                            </div>
                            <Tooltip title="Create Purchase">
                                <IconButton color="primary" onClick={openPurchaseCreateDialog}>
                                    <PurchaseIcon />
                                </IconButton>
                            </Tooltip>
                            <Tooltip title="Create Transfer">
                                <IconButton color="primary" onClick={openTransferCreateDialog}>
                                    <TransferIcon />
                                </IconButton>
                            </Tooltip>
                        </Box>
                    )}
                </Box>
                <Divider sx={{ mt: 1 }} />
                <List>
                    {transactions.length === 0 ? (
                        <Alert severity="info">No Transactions</Alert>
                    ) : (
                        filteredTransactions.map((transaction) => (
                            <TransactionListEntry key={transaction.id} group={group} transaction={transaction} />
                        ))
                    )}
                </List>
                <TransferCreateModal
                    group={group}
                    show={showTransferCreateDialog}
                    onClose={(evt, reason) => {
                        if (reason !== "backdropClick") {
                            setShowTransferCreateDialog(false);
                        }
                    }}
                />
                <PurchaseCreateModal
                    group={group}
                    show={showPurchaseCreateDialog}
                    onClose={(evt, reason) => {
                        if (reason !== "backdropClick") {
                            setShowPurchaseCreateDialog(false);
                        }
                    }}
                />
            </MobilePaper>
            {userPermissions.can_write && (
                <SpeedDial
                    ariaLabel="Create Account"
                    sx={{ position: "fixed", bottom: 20, right: 20 }}
                    icon={<SpeedDialIcon />}
                    // onClose={() => setSpeedDialOpen(false)}
                    // onOpen={() => setSpeedDialOpen(true)}
                    onClick={toggleSpeedDial}
                    open={speedDialOpen}
                >
                    <SpeedDialAction
                        icon={<PurchaseIcon />}
                        tooltipTitle="Purchase"
                        tooltipOpen
                        onClick={openPurchaseCreateDialog}
                    />
                    <SpeedDialAction
                        icon={<TransferIcon />}
                        tooltipTitle="Transfer"
                        tooltipOpen
                        onClick={openTransferCreateDialog}
                    />
                </SpeedDial>
            )}
        </>
    );
}
Example #29
Source File: ClearingSharesFormElement.tsx    From abrechnung with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function ClearingSharesFormElement({ group, clearingShares, setClearingShares, accountID = undefined }) {
    const accounts = useRecoilValue(accountsSeenByUser(group.id));
    const [showAdvanced, setShowAdvanced] = useState(false);
    const [searchValue, setSearchValue] = useState("");
    const [filteredAccounts, setFilteredAccounts] = useState([]);

    useEffect(() => {
        if (searchValue != null && searchValue !== "") {
            setFilteredAccounts(
                accounts.filter((acc) => {
                    return acc.name.toLowerCase().includes(searchValue.toLowerCase());
                })
            );
        } else {
            setFilteredAccounts(accounts);
        }
    }, [searchValue, accounts]);

    return (
        <>
            <Grid container direction="row" justifyContent="space-between">
                <Typography variant="subtitle1">Allocation to</Typography>
                <FormControlLabel
                    control={<Checkbox name={`show-advanced`} />}
                    checked={showAdvanced}
                    onChange={(event: React.ChangeEvent<HTMLInputElement>) => setShowAdvanced(event.target.checked)}
                    label="Advanced"
                />
            </Grid>
            <TableContainer sx={{ maxHeight: 400 }}>
                <Table size="small" stickyHeader>
                    <TableHead>
                        <TableRow>
                            <TableCell>
                                <TextField
                                    placeholder="Search ..."
                                    margin="none"
                                    size="small"
                                    value={searchValue}
                                    onChange={(e) => setSearchValue(e.target.value)}
                                    variant="standard"
                                    InputProps={{
                                        startAdornment: (
                                            <InputAdornment position="start">
                                                <SearchIcon />
                                            </InputAdornment>
                                        ),
                                    }}
                                />
                            </TableCell>
                            <TableCell width="100px">Shares</TableCell>
                        </TableRow>
                    </TableHead>
                    <TableBody>
                        {filteredAccounts.map(
                            (account) =>
                                (accountID === undefined || account.id !== accountID) && (
                                    <TableRow hover key={account.id}>
                                        <TableCell>
                                            <Grid container direction="row" alignItems="center">
                                                <Grid item>
                                                    {account.type === "personal" ? <Person /> : <CompareArrows />}
                                                </Grid>
                                                <Grid item sx={{ ml: 1 }}>
                                                    <Typography variant="body2" component="span">
                                                        {account.name}
                                                    </Typography>
                                                </Grid>
                                            </Grid>
                                        </TableCell>
                                        <TableCell width="100px">
                                            {showAdvanced ? (
                                                <ShareInput
                                                    onChange={(value) =>
                                                        setClearingShares({
                                                            ...(clearingShares !== undefined ? clearingShares : {}),
                                                            [account.id]: value,
                                                        })
                                                    }
                                                    value={
                                                        clearingShares && clearingShares.hasOwnProperty(account.id)
                                                            ? clearingShares[account.id]
                                                            : 0.0
                                                    }
                                                />
                                            ) : (
                                                <Checkbox
                                                    name={`${account.name}-checked`}
                                                    checked={
                                                        clearingShares &&
                                                        clearingShares.hasOwnProperty(account.id) &&
                                                        clearingShares[account.id] !== 0
                                                    }
                                                    onChange={(event) =>
                                                        setClearingShares({
                                                            ...(clearingShares !== undefined ? clearingShares : {}),
                                                            [account.id]: event.target.checked ? 1.0 : 0.0,
                                                        })
                                                    }
                                                />
                                            )}
                                        </TableCell>
                                    </TableRow>
                                )
                        )}
                    </TableBody>
                </Table>
            </TableContainer>
        </>
    );
}