reactstrap#Input TypeScript Examples

The following examples show how to use reactstrap#Input. 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: Modals.tsx    From health-cards-tests with MIT License 6 votes vote down vote up
ConfigEditOption: React.FC<{
    title: string;
    default: string;
    value: string;
    onChange: (string) => void;
}> = (props) => {
    return <>
        <InputGroup>
            <InputGroupAddon addonType='prepend' className='config-prepend'>
                <InputGroupText>{props.title}</InputGroupText>
            </InputGroupAddon>
            <Input type="text" value={props.value} onChange={e => props.onChange(e.target.value)}>
            </Input>
            <InputGroupAddon addonType="prepend">
                <Button onClick={e => props.onChange(props.default)}>↻</Button>
            </InputGroupAddon>
        </InputGroup>
        <br />
    </>;
}
Example #2
Source File: TextInputMonospace.tsx    From nextclade with MIT License 6 votes vote down vote up
TextInputMonospace = styled(Input)`
  font-family: ${(props) => props.theme.font.monospace};
  font-size: 0.75rem;
  border-radius: 3px;
  box-shadow: inset 2px 1px 3px #2222;
  resize: none;
  word-break: break-all;
  word-wrap: break-word;
  text-wrap: unrestricted;
`
Example #3
Source File: text_search_bar.tsx    From website with Apache License 2.0 6 votes vote down vote up
/**
 * Component for the search box in the rich search interface. The value of the
 * is not controlled to prevent constantly updating the hash URL.
 */
export function TextSearchBar({
  onSearch,
  initialValue,
  placeholder,
}: TextSearchBarPropType): JSX.Element {
  const [invalid, setInvalid] = useState(false);
  const [value, setValue] = useState(initialValue);
  const callback = () => (value ? onSearch(value) : setInvalid(true));
  return (
    <div className="input-query">
      <InputGroup>
        <Input
          invalid={invalid}
          placeholder={placeholder}
          value={value}
          onChange={(e) => {
            setValue(e.target.value);
            setInvalid(false);
          }}
          onKeyPress={(e) => e.key === "Enter" && callback()}
          className="pac-target-input"
        />
        <Button onClick={callback} className="rich-search-button">
          <span className="material-icons search rich-search-icon">search</span>
        </Button>
      </InputGroup>
    </div>
  );
}
Example #4
Source File: NavBar.tsx    From opensaas with MIT License 6 votes vote down vote up
NavBar = (props: NavBarProps) => {
  const { className } = props;

  return (
    <div className={classNames('nav-bar', className)}>
      <div className='logo d-none d-md-flex'>
        <a className='d-flex flex-row align-items-center justify-content-start' href='/'>
          <Image src='images/logo.png' />
        </a>
      </div>
      <Icon type='search' className='search-icon' />
      <Input className='search-field' type='text' placeholder='Search...' />

      <div className='account-wrapper' style={{ display: 'flex' }}>
        <Notifications />
        <AccountDropdown />
      </div>
    </div>
  );
}
Example #5
Source File: CheckBox.tsx    From opensaas with MIT License 6 votes vote down vote up
CheckBox: React.FC<CheckBoxProps> = (props) => {
  const { label, name, valid = false, placeholder, signature, className, values } = props;

  return (
    <div className={className}>
      <div>
        <Label>{label}</Label>
      </div>

      {values.map((value: string, i: number) => (
        <div className='custom-control custom-control-inline custom-checkbox' key={i}>
          <Input
            className={classNames('form-input', 'custom-control-input', {
              'border-green-500 is-valid': valid,
              'border-red-500 is-invalid': !valid,
            })}
            placeholder={placeholder}
            type='checkbox'
            name={name}
            value={value}
            id={name + i + value}
          />
          <Label className='form-check-label custom-control-label' htmlFor={name + i + value}>
            {value}
          </Label>
        </div>
      ))}
      <div
        className={classNames('text-xs', {
          'text-green-500': valid,
          'text-red-500': !valid,
        })}>
        {signature}
      </div>
    </div>
  );
}
Example #6
Source File: SiopApproval.tsx    From health-cards-tests with MIT License 5 votes vote down vote up
SiopRequestReceiver: React.FC<SiopRequestReceiverProps> = (props) => {

    const [currentCode, setCurrentCode] = useState("")

    const runningQrScanner = useRef(null)

    let qrScanner; // scope bound to callback
    const videoCallback = useCallback((videoElement) => {
        if (!videoElement) {
            if (qrScanner) {
                qrScanner.destroy()
            }
            return;
        }
        qrScanner = new QrScanner(videoElement, result => {
            if (result.length)
                props.onReady(result)
        });
        runningQrScanner.current = qrScanner 
        qrScanner.start();
    }, [])

    useEffect(() => {
        if (props.redirectMode === "window-open") {
            const onMessage = ({ data, source }) => {
                props.onReady(data)
            }
            const registered = window.addEventListener("message", onMessage)
            const opened = window.open(props.startUrl)
            return () => {
                window.removeEventListener("message", onMessage)
            }
        }
    }, [])

    return props.redirectMode === "qr" ? <>
        <Modal isOpen={true}>
            <ModalHeader>Connect to {props.label}</ModalHeader>
            <ModalBody>
                <div>Scan a QR Code</div>
                <video ref={videoCallback} style={{ maxWidth: "100%", maxHeight: "30vh" }} />
            </ModalBody>
            <ModalFooter  >
                <InputGroup>
                    <Input placeholder="Or paste a URL directly..." type="text" autoFocus={true} value={currentCode} onChange={e => setCurrentCode(e.target.value)}>
                    </Input>
                </InputGroup>

                {props.onCancel ?
                    <Button color="error" onClick={e => props.onCancel()}>
                        Cancel
                </Button> : ""
                }

                <Button color="success" onClick={e => props.onReady(currentCode)}>
                    Connect
                </Button>

            </ModalFooter>
        </Modal>
    </> : <>
            <span>Waiting for redirect...</span>
        </>
}
Example #7
Source File: source_selector.tsx    From website with Apache License 2.0 5 votes vote down vote up
/**
 * Gets the element for a single source option
 */
function getSourceOptionJsx(
  svInfo: SourceSelectorSvInfo,
  metahash: string,
  modalSelections: Record<string, string>,
  setModalSelections: (selections: Record<string, string>) => void
): JSX.Element {
  const metadata = svInfo.metadataMap[metahash] || {};
  let sourceTitle = getSourceTitle(metadata);
  if (svInfo.displayNames && metahash in svInfo.displayNames) {
    sourceTitle = svInfo.displayNames[metahash];
  }
  return (
    <FormGroup radio="true" row key={svInfo.dcid + metahash}>
      <Label radio="true" className={`${SELECTOR_PREFIX}-option`}>
        <div className={`${SELECTOR_PREFIX}-option-title`}>
          <Input
            type="radio"
            name={svInfo.dcid}
            defaultChecked={svInfo.metahash === metahash}
            onClick={() => {
              setModalSelections({
                ...modalSelections,
                [svInfo.dcid]: metahash,
              });
            }}
          />
          <div>{sourceTitle}</div>
        </div>
        <div className={`${SELECTOR_PREFIX}-option-details`}>
          {metadata.importName && <div>importName: {metadata.importName}</div>}
          {metadata.measurementMethod && (
            <div>measurementMethod: {metadata.measurementMethod}</div>
          )}
          {metadata.observationPeriod && (
            <div>observationPeriod: {metadata.observationPeriod}</div>
          )}
          {metadata.scalingFactor && (
            <div>scalingFactor: {metadata.scalingFactor}</div>
          )}
          {metadata.unit && <div>unit: {metadata.unit}</div>}
        </div>
      </Label>
    </FormGroup>
  );
}
Example #8
Source File: Radio.tsx    From opensaas with MIT License 5 votes vote down vote up
Radio: React.FC<RadioProps> = (props) => {
  const { label, name, valid = false, placeholder, signature, className, values } = props;

  return (
    <div className={className}>
      <div>
        <Label>{label}</Label>
      </div>

      {values.map((value: string, i: number) => (
        <div className='custom-control custom-control-inline custom-radio' key={i}>
          <Input
            className={classNames('form-check-input', 'custom-control-input', {
              'border-green-500': valid,
              'border-red-500': !valid,
            })}
            placeholder={placeholder}
            type='radio'
            name={name}
            value={value}
            id={name + i + value}
          />
          <Label
            className={classNames('form-check-label', 'custom-control-label', {
              'text-green-500': valid,
              'text-red-500': !valid,
            })}
            htmlFor={name + i + value}>
            {value}
          </Label>
        </div>
      ))}
      <div
        className={classNames('text-xs', {
          'text-green-500': valid,
          'text-red-500': !valid,
        })}>
        {signature}
      </div>
    </div>
  );
}
Example #9
Source File: chart.tsx    From website with Apache License 2.0 5 votes vote down vote up
render(): JSX.Element {
    const statVars = Object.keys(this.props.statVarInfos);
    // TODO(shifucun): investigate on stats var title, now this is updated
    // several times.
    this.plotParams = computePlotParams(
      this.props.placeNames,
      statVars,
      this.props.statVarInfos
    );
    // Stats var chip color is independent of places, so pick one place to
    // provide a key for style look up.
    const placeName = Object.values(this.props.placeNames)[0];
    const deltaCheckboxId = `delta-cb-${this.props.mprop}`;
    const sourceSelectorSvInfoList = this.getSourceSelectorSvInfo(statVars);
    return (
      <div className="chart-container">
        <div className="card">
          <div className="statVarChipRegion">
            {statVars.map((statVar) => {
              let color: string;
              if (statVars.length > 1) {
                color = this.plotParams.lines[placeName + statVar].color;
              }
              return (
                <Chip
                  key={statVar}
                  id={statVar}
                  title={this.props.statVarInfos[statVar].title}
                  color={color}
                  removeChip={this.props.removeStatVar}
                  onTextClick={() => window.open(`/tools/statvar#${statVar}`)}
                />
              );
            })}
          </div>
          <div ref={this.svgContainer} className="chart-svg"></div>
        </div>
        <ToolChartFooter
          chartId={this.props.mprop}
          sources={
            this.state.statData ? this.state.statData.sources : new Set()
          }
          mMethods={
            this.state.statData
              ? this.state.statData.measurementMethods
              : new Set()
          }
          sourceSelectorSvInfoList={sourceSelectorSvInfoList}
          onSvMetahashUpdated={(svMetahashMap) => setMetahash(svMetahashMap)}
          hideIsRatio={false}
          isPerCapita={this.props.pc}
          onIsPerCapitaUpdated={(isPerCapita: boolean) =>
            setChartOption(this.props.mprop, "pc", isPerCapita)
          }
        >
          <span className="chart-option">
            <FormGroup check>
              <Label check>
                <Input
                  id={deltaCheckboxId}
                  className="is-delta-input"
                  type="checkbox"
                  checked={this.props.delta}
                  onChange={() =>
                    setChartOption(this.props.mprop, "delta", !this.props.delta)
                  }
                />
                Delta
              </Label>
            </FormGroup>
          </span>
        </ToolChartFooter>
      </div>
    );
  }
Example #10
Source File: tool_chart_footer.tsx    From website with Apache License 2.0 5 votes vote down vote up
export function ToolChartFooter(props: ToolChartFooterPropType): JSX.Element {
  const mMethods = !_.isEmpty(props.mMethods)
    ? Array.from(props.mMethods).join(", ")
    : "";
  const ratioCheckboxId = props.chartId + "-ratio";
  const [chartOptionsOpened, setChartOptionsOpened] = useState(true);

  return (
    <>
      <div
        className={`${SELECTOR_PREFIX}-container ${
          chartOptionsOpened ? "no-bottom-border" : ""
        }`}
      >
        <div className={`${SELECTOR_PREFIX}-metadata-section`}>
          {!_.isEmpty(props.sources) && (
            <div className={`${SELECTOR_PREFIX}-metadata`}>
              <span>Data from {getSourcesJsx(props.sources)}</span>
            </div>
          )}
          {!_.isEmpty(mMethods) && (
            <div className={`${SELECTOR_PREFIX}-metadata`}>
              <span>{`Measurement method${
                props.mMethods.size > 1 ? "s" : ""
              }: ${mMethods}`}</span>
            </div>
          )}
        </div>
        <div
          onClick={() => setChartOptionsOpened(!chartOptionsOpened)}
          className={`${SELECTOR_PREFIX}-options-button`}
        >
          <span>Chart Options</span>
          {chartOptionsOpened ? UP_ARROW_HTML : DOWN_ARROW_HTML}
        </div>
      </div>
      {chartOptionsOpened && (
        <div className={`${SELECTOR_PREFIX}-options-section`}>
          {!props.hideIsRatio && (
            <span className="chart-option">
              <FormGroup check>
                <Label check>
                  <Input
                    id={ratioCheckboxId}
                    type="checkbox"
                    checked={props.isPerCapita}
                    onChange={() =>
                      props.onIsPerCapitaUpdated(!props.isPerCapita)
                    }
                  />
                  Per Capita
                </Label>
              </FormGroup>
            </span>
          )}
          {props.children}
          <SourceSelector
            svInfoList={props.sourceSelectorSvInfoList}
            onSvMetahashUpdated={props.onSvMetahashUpdated}
          />
        </div>
      )}
      <div className="feedback-link">
        <a href={FEEDBACK_LINK}>Feedback</a>
      </div>
    </>
  );
}
Example #11
Source File: plot_options.tsx    From website with Apache License 2.0 4 votes vote down vote up
// TODO: Add a new API that given a statvar, a parent place, and a child type,
// returns the available dates for the statvar. Then, fill the datapicker with
// the dates.
function PlotOptions(): JSX.Element {
  const { place, x, y, display } = useContext(Context);
  const [lowerBound, setLowerBound] = useState(
    place.value.lowerBound.toString()
  );
  const [upperBound, setUpperBound] = useState(
    place.value.upperBound.toString()
  );
  const [xDenomInput, setXDenomInput] = useState(x.value.denom);
  const [yDenomInput, setYDenomInput] = useState(y.value.denom);
  const yAxisLabel =
    display.chartType === ScatterChartType.SCATTER
      ? "Y-axis"
      : y.value.statVarInfo.title || y.value.statVarDcid;
  const xAxisLabel =
    display.chartType === ScatterChartType.SCATTER
      ? "X-axis"
      : x.value.statVarInfo.title || x.value.statVarDcid;
  const axisLabelStyle = {};
  if (
    yAxisLabel.length > MIN_WIDTH_LABEL_LENGTH ||
    xAxisLabel.length > MIN_WIDTH_LABEL_LENGTH
  ) {
    axisLabelStyle["width"] =
      Math.min(
        MAX_WIDTH_LABEL_LENGTH,
        Math.max(xAxisLabel.length, yAxisLabel.length)
      ) /
        2 +
      "rem";
  }

  return (
    <Card id="plot-options">
      <Container fluid={true}>
        <div className="plot-options-row">
          <div className="plot-options-label" style={axisLabelStyle}>
            {yAxisLabel}:
          </div>
          <div className="plot-options-input-section">
            <div className="plot-options-input">
              <FormGroup check>
                <Label check>
                  <Input
                    id="per-capita-y"
                    type="checkbox"
                    checked={y.value.perCapita}
                    onChange={(e) => y.setPerCapita(e.target.checked)}
                  />
                  Per Capita
                </Label>
              </FormGroup>
            </div>
            <div className="plot-options-input">
              <FormGroup check>
                <Input
                  id="log-y"
                  type="checkbox"
                  checked={y.value.log}
                  onChange={(e) => checkLog(y, e)}
                />
                <Label check>Log scale</Label>
              </FormGroup>
            </div>
          </div>
        </div>
        <div className="plot-options-row">
          <div className="plot-options-label" style={axisLabelStyle}>
            {xAxisLabel}:
          </div>
          <div className="plot-options-input-section">
            <div className="plot-options-input">
              <FormGroup check>
                <Label check>
                  <Input
                    id="per-capita-x"
                    type="checkbox"
                    checked={x.value.perCapita}
                    onChange={(e) => x.setPerCapita(e.target.checked)}
                  />
                  Per Capita
                </Label>
              </FormGroup>
            </div>
            <div className="plot-options-input">
              <FormGroup check>
                <Input
                  id="log-x"
                  type="checkbox"
                  checked={x.value.log}
                  onChange={(e) => checkLog(x, e)}
                />
                <Label check>Log scale</Label>
              </FormGroup>
            </div>
          </div>
        </div>
        {display.chartType === ScatterChartType.SCATTER && (
          <>
            <div className="plot-options-row">
              <div className="plot-options-label">Display:</div>
              <div className="plot-options-input-section">
                <div className="plot-options-input">
                  <Button
                    id="swap-axes"
                    size="sm"
                    color="light"
                    onClick={() => swapAxes(x, y)}
                    className="plot-options-swap-button"
                  >
                    Swap X and Y axes
                  </Button>
                </div>
                <div className="plot-options-input">
                  <FormGroup check>
                    <Label check>
                      <Input
                        id="quadrants"
                        type="checkbox"
                        checked={display.showQuadrants}
                        onChange={(e) => checkQuadrants(display, e)}
                      />
                      Show quadrants
                    </Label>
                  </FormGroup>
                </div>
                <div className="plot-options-input">
                  <FormGroup check>
                    <Label check>
                      <Input
                        id="quadrants"
                        type="checkbox"
                        checked={display.showLabels}
                        onChange={(e) => checkLabels(display, e)}
                      />
                      Show labels
                    </Label>
                  </FormGroup>
                </div>
                <div className="plot-options-input">
                  <FormGroup check>
                    <Label check>
                      <Input
                        id="density"
                        type="checkbox"
                        checked={display.showDensity}
                        onChange={(e) => checkDensity(display, e)}
                      />
                      Show density
                    </Label>
                  </FormGroup>
                </div>
              </div>
            </div>
            <div className="plot-options-row">
              <div className="plot-options-label">Filter by population:</div>
              <div className="plot-options-input-section pop-filter">
                <div className="plot-options-input">
                  <FormGroup check>
                    <Input
                      className="pop-filter-input"
                      type="number"
                      onChange={(e) =>
                        selectLowerBound(place, e, setLowerBound)
                      }
                      value={lowerBound}
                      onBlur={() =>
                        setLowerBound(place.value.lowerBound.toString())
                      }
                    />
                  </FormGroup>
                </div>
                <span>to</span>
                <div className="plot-options-input">
                  <FormGroup check>
                    <Input
                      className="pop-filter-input"
                      type="number"
                      onChange={(e) =>
                        selectUpperBound(place, e, setUpperBound)
                      }
                      value={upperBound}
                      onBlur={() =>
                        setUpperBound(place.value.upperBound.toString())
                      }
                    />
                  </FormGroup>
                </div>
              </div>
            </div>
          </>
        )}
      </Container>
    </Card>
  );
}
Example #12
Source File: DatasetSelector.tsx    From nextclade with MIT License 4 votes vote down vote up
export function DatasetSelector({ searchTerm, setSearchTerm }: DatasetSelectorProps) {
  const { t } = useTranslationSafe()
  const [error, setError] = useState<string | undefined>()
  const { datasets, defaultDatasetName } = useRecoilValue(datasetsAtom)
  const [datasetCurrentName, setDatasetCurrent] = useRecoilState(datasetCurrentNameAtom)
  const [datasetHighlighted, setDatasetHighlighted] = useState<string | undefined>(
    datasetCurrentName ?? defaultDatasetName,
  )

  const onSearchTermChange = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      const { value } = event.target
      setSearchTerm(value)
    },
    [setSearchTerm],
  )

  const onNextClicked = useCallback(() => {
    if (datasetHighlighted) {
      setDatasetCurrent(datasetHighlighted)
      setError(undefined)
    } else {
      setError(t('Please select a pathogen first'))
    }
  }, [datasetHighlighted, setDatasetCurrent, t])

  const isBusy = datasets.length === 0

  return (
    <DatasetSelectorContainer fluid>
      <Row noGutters>
        <Col sm={6} className="d-flex">
          <DatasetSelectorTitle>{t('Select a pathogen')}</DatasetSelectorTitle>
        </Col>

        <Col sm={6}>
          <Input
            type="text"
            title="Search pathogens"
            placeholder="Search pathogens"
            autoComplete="off"
            autoCorrect="off"
            autoCapitalize="off"
            spellCheck="false"
            data-gramm="false"
            value={searchTerm}
            onChange={onSearchTermChange}
          />
        </Col>
      </Row>

      <Row noGutters className="mt-2">
        <DatasetSelectorListContainer>
          {!isBusy && (
            <DatasetSelectorList
              datasets={datasets}
              datasetHighlighted={datasetHighlighted}
              searchTerm={searchTerm}
              onDatasetHighlighted={setDatasetHighlighted}
            />
          )}

          {isBusy && (
            <SpinnerWrapper>
              <SpinnerWrapperInternal>
                <Spinner color="#aaa" width={20} height={20} />
              </SpinnerWrapperInternal>
            </SpinnerWrapper>
          )}
        </DatasetSelectorListContainer>
      </Row>

      <Row noGutters>
        <Col className="py-1">
          <LinkExternal href="https://github.com/nextstrain/nextclade_data/blob/master/CHANGELOG.md">
            <small>{t('Recent dataset updates')}</small>
          </LinkExternal>
        </Col>
      </Row>

      <Row noGutters className="mt-2">
        <Col className="d-flex">
          {error && <p className="m-0 p-0 flex-1 text-danger">{error}</p>}
          <Button
            className={classNames('ml-auto', !datasetHighlighted && 'disabled')}
            type="button"
            color={datasetHighlighted ? 'primary' : 'secondary'}
            onClick={onNextClicked}
          >
            {t('Next')}
          </Button>
        </Col>
      </Row>
    </DatasetSelectorContainer>
  )
}
Example #13
Source File: statvar.tsx    From website with Apache License 2.0 4 votes vote down vote up
export function StatVarChooser(props: StatVarChooserProps): JSX.Element {
  const { x, y, place } = useContext(Context);

  // Temporary variable for storing an extra statvar.
  const [thirdStatVar, setThirdStatVar] = useState(emptyStatVar);
  // Records which two of the three statvars are wanted if a third statvar is selected.
  const [modalSelected, setModalSelected] = useState(defaultModalSelected);
  const [modalOpen, setModalOpen] = useState(false);
  const [samplePlaces, setSamplePlaces] = useState(
    getSamplePlaces(
      place.value.enclosingPlace.dcid,
      place.value.enclosedPlaceType,
      place.value.enclosedPlaces
    )
  );
  useEffect(() => {
    const samplePlaces = getSamplePlaces(
      place.value.enclosingPlace.dcid,
      place.value.enclosedPlaceType,
      place.value.enclosedPlaces
    );
    setSamplePlaces(samplePlaces);
  }, [place.value.enclosedPlaces]);
  const closeModal = () => {
    setThirdStatVar(emptyStatVar);
    setModalOpen(false);
  };

  useEffect(() => {
    const statVarsToGetInfo = [];
    if (!_.isEmpty(x.value.statVarDcid) && _.isNull(x.value.statVarInfo)) {
      statVarsToGetInfo.push(x.value.statVarDcid);
    }
    if (!_.isEmpty(y.value.statVarDcid) && _.isNull(y.value.statVarInfo)) {
      statVarsToGetInfo.push(y.value.statVarDcid);
    }
    if (_.isEmpty(statVarsToGetInfo)) {
      return;
    }
    getStatVarInfo(statVarsToGetInfo)
      .then((info) => {
        statVarsToGetInfo.forEach((sv) => {
          const svInfo = sv in info ? info[sv] : {};
          if (sv === x.value.statVarDcid) {
            x.setStatVarInfo(svInfo);
          } else {
            y.setStatVarInfo(svInfo);
          }
        });
      })
      .catch(() => {
        if (statVarsToGetInfo.indexOf(x.value.statVarDcid) > -1) {
          x.setStatVarInfo({});
        }
        if (statVarsToGetInfo.indexOf(y.value.statVarDcid) > -1) {
          y.setStatVarInfo({});
        }
      });
  }, [x.value, y.value]);

  let yTitle = y.value.statVarDcid;
  if (y.value.statVarInfo && y.value.statVarInfo.title) {
    yTitle = y.value.statVarInfo.title;
  }
  let xTitle = x.value.statVarDcid;
  if (x.value.statVarInfo && x.value.statVarInfo.title) {
    xTitle = x.value.statVarInfo.title;
  }

  const selectedSvs = {};
  if (!_.isEmpty(x.value.statVarDcid)) {
    selectedSvs[x.value.statVarDcid] = x.value.statVarInfo;
  }
  if (!_.isEmpty(y.value.statVarDcid)) {
    selectedSvs[y.value.statVarDcid] = y.value.statVarInfo;
  }
  if (!_.isEmpty(thirdStatVar.dcid)) {
    selectedSvs[thirdStatVar.dcid] = thirdStatVar.info;
  }

  return (
    <>
      <StatVarWidget
        openSvHierarchyModal={props.openSvHierarchyModal}
        openSvHierarchyModalCallback={props.openSvHierarchyModalCallback}
        collapsible={true}
        svHierarchyType={StatVarHierarchyType.SCATTER}
        samplePlaces={samplePlaces}
        deselectSVs={(svList: string[]) =>
          svList.forEach((sv) => {
            removeStatVar(x, y, sv);
          })
        }
        selectedSVs={selectedSvs}
        selectSV={(sv) => addStatVar(x, y, sv, setThirdStatVar, setModalOpen)}
      />
      {/* Modal for selecting 2 stat vars when a third is selected */}
      <Modal isOpen={modalOpen} backdrop="static" id="statvar-modal">
        <ModalHeader toggle={closeModal}>
          Only Two Statistical Variables Supported
        </ModalHeader>
        <ModalBody>
          <Container>
            <div>
              You selected:{" "}
              <b>{thirdStatVar.info.title || thirdStatVar.dcid}</b>
            </div>
            <div className="radio-selection-label">
              Please choose 1 more statistical variable to keep:
            </div>
            <div className="radio-selection-section">
              <FormGroup radio="true" row>
                <Label radio="true">
                  <Input
                    id="x-radio-button"
                    type="radio"
                    name="statvar"
                    defaultChecked={modalSelected.x}
                    onClick={() => setModalSelected({ x: true, y: false })}
                  />
                  {xTitle}
                </Label>
              </FormGroup>
              <FormGroup radio="true" row>
                <Label radio="true">
                  <Input
                    id="y-radio-button"
                    type="radio"
                    name="statvar"
                    defaultChecked={modalSelected.y}
                    onClick={() => setModalSelected({ x: false, y: true })}
                  />
                  {yTitle}
                </Label>
              </FormGroup>
            </div>
          </Container>
        </ModalBody>
        <ModalFooter>
          <Button
            color="primary"
            onClick={() =>
              confirmStatVars(
                x,
                y,
                thirdStatVar,
                setThirdStatVar,
                modalSelected,
                setModalSelected,
                setModalOpen
              )
            }
          >
            Confirm
          </Button>
        </ModalFooter>
      </Modal>
    </>
  );
}
Example #14
Source File: FeedbackForm.tsx    From TutorBase with MIT License 4 votes vote down vote up
export default function FeedbackForm({apptTutorId}: IProps) {
    const [formOpen, setFormOpen] = useState(false);
    const clientData = useSelector(selectClientData);
    const [feedbackMessage, setFeedbackMessage] = useState("");
    const [rating, setRating] = useState(0);

    const submitFeedback = async () => {
        let clientId = clientData.clientId;
        let tutorId = apptTutorId;

        setFormOpen(false)

        await api.SubmitFeedback({
            clientId: clientId,
            tutorId: tutorId,
            message: feedbackMessage,
            rating: rating
        });

        // TODO: Show some Toast UI confirming that the rating was submitted
    }

    return (
        <Container>
            <Button onClick={(e) => {
                setFormOpen(true);
                e.stopPropagation();
            }}
                    style={{
                        minWidth: '60px',
                        lineHeight: '1',
                        position: "relative",
                    }}>
                <div style={{
                    fontSize: "clamp(8px, 1vw, 12px)"
                }}>
                    Rate
                    <FontAwesomeIcon icon={faStar} style={{marginLeft: '5px'}}/>
                </div>
            </Button>

            <Modal
                isOpen={formOpen}
                toggle={() => setFormOpen(!formOpen)}
            >
                <ModalHeader toggle={() => setFormOpen(!formOpen)}>
                    Please give feedback on your session below.
                </ModalHeader>
                <StyledBody>
                    <Label for="exampleText">
                        What did you think of your session?
                    </Label>
                    <Input
                        id="exampleText"
                        name="text"
                        type="textarea"
                        value={feedbackMessage}
                        onChange={(element) => setFeedbackMessage(element.target.value)}
                    />

                    <Label style={{marginTop: '1em'}}>
                        How would you rate your session?
                    </Label>
                    <div style={{lineHeight: '0.75'}}>
                        <ReactStars size={40} value={rating} onChange={new_rating => setRating(new_rating)}/>
                    </div>
                </StyledBody>
                <ModalFooter>
                    <Button
                        color="primary"
                        onClick={() => submitFeedback()}
                    >
                        Submit
                    </Button>
                    {' '}
                    <Button onClick={() => setFormOpen(false)}>
                        Cancel
                    </Button>
                </ModalFooter>
            </Modal>
        </Container>
    )
}
Example #15
Source File: chart.tsx    From website with Apache License 2.0 4 votes vote down vote up
export function Chart(props: ChartProps): JSX.Element {
  const statVar = props.statVar.value;
  const [errorMessage, setErrorMessage] = useState("");
  const mainSvInfo: StatVarInfo =
    statVar.dcid in statVar.info ? statVar.info[statVar.dcid] : {};
  const title = getTitle(
    Array.from(props.dates),
    mainSvInfo.title || statVar.dcid,
    statVar.perCapita
  );
  const placeDcid = props.placeInfo.enclosingPlace.dcid;
  const statVarDcid = statVar.dcid;
  const [mapPoints, setMapPoints] = useState(null);
  const [mapPointsFetched, setMapPointsFetched] = useState(false);
  const [zoomTransformation, setZoomTransformation] = useState(
    DEFAULT_ZOOM_TRANSFORMATION
  );
  const chartContainerRef = useRef<HTMLDivElement>();

  // load mapPoints in the background.
  useEffect(() => {
    props.mapPointsPromise
      .then((mapPoints) => {
        setMapPoints(mapPoints);
        setMapPointsFetched(true);
      })
      .catch(() => setMapPointsFetched(true));
  }, []);

  function replot() {
    draw(
      props,
      setErrorMessage,
      mapPoints,
      zoomTransformation,
      setZoomTransformation,
      props.display.value.color,
      props.display.value.domain
    );
  }

  // Replot when data changes.
  useEffect(() => {
    if (props.display.value.showMapPoints && !mapPointsFetched) {
      loadSpinner(SECTION_CONTAINER_ID);
      return;
    } else {
      removeSpinner(SECTION_CONTAINER_ID);
    }
    replot();
  }, [props, mapPointsFetched]);

  // Replot when chart width changes on sv widget toggle.
  useEffect(() => {
    const debouncedHandler = _.debounce(() => {
      if (!props.display.value.showMapPoints || mapPointsFetched) {
        replot();
      }
    }, DEBOUNCE_INTERVAL_MS);
    const resizeObserver = new ResizeObserver(debouncedHandler);
    if (chartContainerRef.current) {
      resizeObserver.observe(chartContainerRef.current);
    }
    return () => {
      resizeObserver.unobserve(chartContainerRef.current);
      debouncedHandler.cancel();
    };
  }, [props, chartContainerRef]);

  return (
    <div className="chart-section-container">
      <Card className="chart-section-card">
        <Container id={SECTION_CONTAINER_ID} fluid={true}>
          <div className="chart-section">
            <div className="map-title">
              <h3>
                {title}
                {props.dates.size > 1 && (
                  <span
                    onMouseOver={onDateRangeMouseOver}
                    onMouseOut={onDateRangeMouseOut}
                    id={DATE_RANGE_INFO_ID}
                  >
                    <i className="material-icons-outlined">info</i>
                  </span>
                )}
              </h3>
              <div id={DATE_RANGE_INFO_TEXT_ID}>
                The date range represents the dates of the data shown in this
                map.
              </div>
            </div>
            {errorMessage ? (
              <div className="error-message">{errorMessage}</div>
            ) : (
              <div className="map-section-container">
                <div id={CHART_CONTAINER_ID} ref={chartContainerRef}>
                  <div id={MAP_CONTAINER_ID}></div>
                  <div id={LEGEND_CONTAINER_ID}></div>
                </div>
                <div className="zoom-button-section">
                  <div id={ZOOM_IN_BUTTON_ID} className="zoom-button">
                    <i className="material-icons">add</i>
                  </div>
                  <div id={ZOOM_OUT_BUTTON_ID} className="zoom-button">
                    <i className="material-icons">remove</i>
                  </div>
                </div>
              </div>
            )}
            {props.display.value.showTimeSlider &&
              props.sampleDates &&
              props.sampleDates.length > 1 && (
                <TimeSlider
                  currentDate={_.max(Array.from(props.dates))}
                  dates={props.sampleDates}
                  metahash={props.metahash}
                  onPlay={props.onPlay}
                  startEnabled={props.dates.size === 1}
                  updateDate={props.updateDate}
                />
              )}
            <div className="map-links">
              {mainSvInfo.ranked && (
                <a className="explore-timeline-link" href={props.rankingLink}>
                  <span className="explore-timeline-text">
                    Explore rankings
                  </span>
                  <i className="material-icons">keyboard_arrow_right</i>
                </a>
              )}
              {!mainSvInfo.ranked &&
                (props.placeInfo.selectedPlace.dcid in props.mapDataValues ||
                  props.placeInfo.selectedPlace.dcid in
                    props.breadcrumbDataValues) && (
                  <a
                    className="explore-timeline-link"
                    href={`/tools/timeline#place=${placeDcid}&statsVar=${statVarDcid}`}
                  >
                    <span className="explore-timeline-text">
                      Explore timeline
                    </span>
                    <i className="material-icons">keyboard_arrow_right</i>
                  </a>
                )}
            </div>
          </div>
        </Container>
      </Card>
      <ToolChartFooter
        chartId="map"
        sources={props.sources}
        mMethods={null}
        sourceSelectorSvInfoList={[props.sourceSelectorSvInfo]}
        onSvMetahashUpdated={(svMetahashMap) =>
          props.statVar.setMetahash(svMetahashMap[props.statVar.value.dcid])
        }
        hideIsRatio={false}
        isPerCapita={props.statVar.value.perCapita}
        onIsPerCapitaUpdated={(isPerCapita: boolean) =>
          props.statVar.setPerCapita(isPerCapita)
        }
      >
        {props.placeInfo.mapPointPlaceType && (
          <div className="chart-option">
            <FormGroup check>
              <Label check>
                <Input
                  id="show-installations"
                  type="checkbox"
                  checked={props.display.value.showMapPoints}
                  onChange={(e) =>
                    props.display.setShowMapPoints(e.target.checked)
                  }
                />
                Show Installations
              </Label>
            </FormGroup>
          </div>
        )}
      </ToolChartFooter>
      <div id="map-chart-screen" className="screen">
        <div id="spinner"></div>
      </div>
    </div>
  );
}
Example #16
Source File: stat_var_chooser.tsx    From website with Apache License 2.0 4 votes vote down vote up
export function StatVarChooser(props: StatVarChooserProps): JSX.Element {
  const [samplePlaces, setSamplePlaces] = useState([]);
  // extraStatVar holds a stat var that is selected after the max number of
  // selected stat vars has been reached. This stat var will either be removed
  // or used to replace another selected stat var.
  const [extraStatVar, setExtraStatVar] = useState(EMPTY_SV_AND_INFO);
  const [modalSelection, setModalSelection] = useState("");
  const [modalOpen, setModalOpen] = useState(false);
  const modalSvOrder = useRef(Object.keys(props.statVars));

  useEffect(() => {
    modalSvOrder.current = Object.keys(props.statVars);
  }, [props.statVars]);

  useEffect(() => {
    if (!props.placeDcid || !props.enclosedPlaceType) {
      setSamplePlaces([]);
      return;
    }
    getEnclosedPlacesPromise(props.placeDcid, props.enclosedPlaceType)
      .then((enclosedPlaces) => {
        const samplePlaces = getSamplePlaces(
          props.placeDcid,
          props.enclosedPlaceType,
          enclosedPlaces
        );
        setSamplePlaces(samplePlaces);
      })
      .catch(() => {
        setSamplePlaces([]);
      });
  }, [props.placeDcid, props.enclosedPlaceType]);

  const selectedSVs = { ...props.statVars };
  // although we don't propagate the extra stat var selection to the rest of the
  // tool, we do need to pass it to the widget because the StatVarHierarchy has
  // it showing as selected.
  if (!_.isEmpty(extraStatVar.dcid)) {
    selectedSVs[extraStatVar.dcid] = extraStatVar.info;
  }

  return (
    <>
      <StatVarWidget
        openSvHierarchyModal={props.openSvHierarchyModal}
        openSvHierarchyModalCallback={props.openSvHierarchyModalCallback}
        collapsible={false}
        svHierarchyType={StatVarHierarchyType.DOWNLOAD}
        samplePlaces={samplePlaces}
        deselectSVs={(svList: string[]) =>
          svList.forEach((sv) => {
            props.onStatVarRemoved(sv);
          })
        }
        selectedSVs={selectedSVs}
        selectSV={(sv) => selectSV(sv)}
      />
      {/* Modal for selecting stat var to replace when too many are selected */}
      <Modal isOpen={modalOpen} backdrop="static" id="statvar-modal">
        <ModalHeader toggle={closeModal}>
          Only 5 Statistical Variables Supported
        </ModalHeader>
        <ModalBody>
          <Container>
            <div>
              You selected:{" "}
              <b>{extraStatVar.info.title || extraStatVar.dcid}</b>
            </div>
            <div className="radio-selection-label">
              Please choose a statistical variable to replace:
            </div>
            <div className="radio-selection-section">
              {modalSvOrder.current.map((sv, idx) => {
                return (
                  <FormGroup key={sv} radio="true" row>
                    <Label radio="true">
                      <Input
                        type="radio"
                        name="statvar"
                        defaultChecked={
                          (_.isEmpty(modalSelection) && idx === 0) ||
                          modalSelection === sv
                        }
                        onClick={() => setModalSelection(sv)}
                      />
                      {sv in props.statVars
                        ? props.statVars[sv].title || sv
                        : sv}
                    </Label>
                  </FormGroup>
                );
              })}
            </div>
          </Container>
        </ModalBody>
        <ModalFooter>
          <Button color="primary" onClick={() => confirmStatVars()}>
            Confirm
          </Button>
        </ModalFooter>
      </Modal>
    </>
  );

  /**
   * Close the modal
   */
  function closeModal(): void {
    setExtraStatVar(EMPTY_SV_AND_INFO);
    setModalSelection("");
    setModalOpen(false);
  }

  /**
   * Confirms the variable to replace in the modal.
   */
  function confirmStatVars(): void {
    const svToRemove = _.isEmpty(modalSelection)
      ? modalSvOrder.current[0]
      : modalSelection;
    props.onStatVarRemoved(svToRemove);
    props.onStatVarSelected(extraStatVar.dcid, extraStatVar.info);
    closeModal();
  }

  /**
   * Select a variable.
   */
  function selectSV(sv: string): void {
    getStatVarInfo([sv])
      .then((svInfo) => {
        const selectedSVInfo = svInfo[sv] || {};
        if (Object.keys(props.statVars).length >= MAX_SV) {
          setExtraStatVar({ dcid: sv, info: selectedSVInfo });
          setModalOpen(true);
        } else {
          props.onStatVarSelected(sv, selectedSVInfo);
        }
      })
      .catch(() => {
        if (Object.keys(props.statVars).length >= MAX_SV) {
          setExtraStatVar({ dcid: sv, info: {} });
          setModalOpen(true);
        } else {
          props.onStatVarSelected(sv, {});
        }
      });
  }
}
Example #17
Source File: page.tsx    From website with Apache License 2.0 4 votes vote down vote up
export function Page(): JSX.Element {
  const [selectedOptions, setSelectedOptions] = useState<DownloadOptions>(null);
  const [previewDisabled, setPreviewDisabled] = useState(false);
  const [showPreview, setShowPreview] = useState(false);
  const [validationErrors, setValidationErrors] = useState<ValidationErrors>({
    incompleteSelectionMessage: "",
    maxDate: false,
    minDate: false,
  });
  const [isSvModalOpen, updateSvModalOpen] = useState(false);
  const toggleSvModalCallback = () => updateSvModalOpen(!isSvModalOpen);

  useEffect(() => {
    if (showPreview) {
      setPreviewDisabled(true);
    }
  }, [selectedOptions]);

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

  if (!selectedOptions) {
    return <></>;
  }

  const getDataButtonText = showPreview ? "Update" : "Preview";
  const showInfo =
    _.isEmpty(validationErrors.incompleteSelectionMessage) && !showPreview;
  return (
    // TODO: Try to move the options into a separate component.
    <>
      <StatVarChooser
        statVars={selectedOptions.selectedStatVars}
        placeDcid={selectedOptions.selectedPlace.dcid}
        enclosedPlaceType={selectedOptions.enclosedPlaceType}
        onStatVarSelected={selectSV}
        onStatVarRemoved={removeSV}
        openSvHierarchyModal={isSvModalOpen}
        openSvHierarchyModalCallback={toggleSvModalCallback}
      />
      <div id="plot-container">
        <h1 className="mb-4">Download Tool</h1>
        <div className="download-options-container">
          <PlaceSelector
            selectedPlace={selectedOptions.selectedPlace}
            enclosedPlaceType={selectedOptions.enclosedPlaceType}
            onPlaceSelected={(place) =>
              setSelectedOptions((prev) => {
                return { ...prev, selectedPlace: place, enclosedPlaceType: "" };
              })
            }
            onEnclosedPlaceTypeSelected={(enclosedPlaceType) =>
              setSelectedOptions((prev) => {
                return { ...prev, enclosedPlaceType };
              })
            }
          >
            <div className="download-option-section">
              <div className="download-option-label">Date</div>
              <div className="download-date-options">
                <FormGroup radio="true">
                  <Label radio="true">
                    <Input
                      id="latest-date"
                      type="radio"
                      name="date"
                      defaultChecked={!selectedOptions.dateRange}
                      onClick={() =>
                        setSelectedOptions((prev) => {
                          return { ...prev, dateRange: false };
                        })
                      }
                    />
                    Latest Date
                  </Label>
                </FormGroup>
                <FormGroup radio="true" className="download-date-range-section">
                  <Label radio="true" className="download-date-range-container">
                    <Input
                      id="date-range"
                      type="radio"
                      name="date"
                      defaultChecked={selectedOptions.dateRange}
                      onClick={() =>
                        setSelectedOptions((prev) => {
                          return { ...prev, dateRange: true };
                        })
                      }
                    />
                    Date Range:
                  </Label>
                  <div className="download-date-range-input-section">
                    <div className="download-date-range-input-container">
                      <div>
                        <FormGroup>
                          <Input
                            className={`download-date-range-input${
                              validationErrors.minDate ? "-error" : ""
                            }`}
                            type="text"
                            onChange={(e) => {
                              const date = e.target.value;
                              setSelectedOptions((prev) => {
                                return { ...prev, minDate: date };
                              });
                            }}
                            disabled={!selectedOptions.dateRange}
                            value={selectedOptions.minDate}
                            onBlur={(e) => validateDate(e.target.value, true)}
                          />
                        </FormGroup>
                      </div>
                      <span>to</span>
                      <div>
                        <FormGroup>
                          <Input
                            className={`download-date-range-input${
                              validationErrors.maxDate ? "-error" : ""
                            }`}
                            type="text"
                            onChange={(e) => {
                              const date = e.target.value;
                              setSelectedOptions((prev) => {
                                return { ...prev, maxDate: date };
                              });
                            }}
                            disabled={!selectedOptions.dateRange}
                            value={selectedOptions.maxDate}
                            onBlur={(e) => validateDate(e.target.value, false)}
                          />
                        </FormGroup>
                      </div>
                    </div>
                    <div
                      className={`download-date-range-hint${
                        validationErrors.minDate || validationErrors.maxDate
                          ? "-error"
                          : ""
                      }`}
                    >
                      YYYY or YYYY-MM or YYYY-MM-DD
                    </div>
                  </div>
                </FormGroup>
              </div>
            </div>
            <div className="download-option-section">
              <div className="download-option-label">Variables</div>
              {_.isEmpty(selectedOptions.selectedStatVars) ? (
                "None selected"
              ) : (
                <div className="download-sv-chips">
                  {Object.keys(selectedOptions.selectedStatVars).map((sv) => {
                    return (
                      <Chip
                        key={sv}
                        id={sv}
                        title={selectedOptions.selectedStatVars[sv].title || sv}
                        removeChip={removeSV}
                      />
                    );
                  })}
                </div>
              )}
            </div>
            <Row className="d-lg-none">
              <Col>
                <Button color="primary" onClick={toggleSvModalCallback}>
                  Select variable
                </Button>
              </Col>
            </Row>
            <Button onClick={onGetDataButtonClicked} color="primary">
              {getDataButtonText}
            </Button>
          </PlaceSelector>
        </div>
        {!_.isEmpty(validationErrors.incompleteSelectionMessage) && (
          <div>{validationErrors.incompleteSelectionMessage}</div>
        )}
        {showPreview && (
          <div>{previewDisabled ? "disabled preview" : "preview"}</div>
        )}
        {showInfo && <div>info text</div>}
      </div>
    </>
  );

  function selectSV(sv: string, svInfo: StatVarInfo): void {
    setSelectedOptions((prev) => {
      const updatedSV = _.cloneDeep(prev.selectedStatVars);
      updatedSV[sv] = svInfo;
      return { ...prev, selectedStatVars: updatedSV };
    });
  }

  function removeSV(sv: string): void {
    setSelectedOptions((prev) => {
      const updatedSV = _.cloneDeep(prev.selectedStatVars);
      if (sv in updatedSV) {
        delete updatedSV[sv];
      }
      return { ...prev, selectedStatVars: updatedSV };
    });
  }

  function getOptionsFromURL(): void {
    const options = {
      dateRange: false,
      enclosedPlaceType: "",
      maxDate: "",
      minDate: "",
      selectedPlace: { dcid: "", name: "", types: null },
      selectedStatVars: {},
    };
    if (!window.location.hash) {
      setSelectedOptions(options);
    }
    const urlParams = new URLSearchParams(window.location.hash.split("#")[1]);
    const place = urlParams.get(URL_PARAM_KEYS.PLACE);
    const placePromise = place
      ? getNamedTypedPlace(place)
      : Promise.resolve({ dcid: "", name: "", types: null });
    const statVarsParam = urlParams.get(URL_PARAM_KEYS.STAT_VARS);
    const statVarsList = statVarsParam ? statVarsParam.split(SEPARATOR) : [];
    const svInfoPromise = !_.isEmpty(statVarsList)
      ? getStatVarInfo(statVarsList)
      : Promise.resolve({});
    options.enclosedPlaceType = urlParams.get(URL_PARAM_KEYS.PLACE_TYPE) || "";
    options.dateRange =
      urlParams.get(URL_PARAM_KEYS.DATE_RANGE) === PARAM_VALUE_TRUE;
    options.minDate = urlParams.get(URL_PARAM_KEYS.MIN_DATE) || "";
    options.maxDate = urlParams.get(URL_PARAM_KEYS.MAX_DATE) || "";
    Promise.all([placePromise, svInfoPromise])
      .then(([place, svInfo]) => {
        setSelectedOptions({
          ...options,
          selectedPlace: place,
          selectedStatVars: svInfo,
        });
      })
      .catch(() => {
        const emptySvInfo = {};
        statVarsList.forEach((sv) => (emptySvInfo[sv] = {}));
        setSelectedOptions({
          ...options,
          selectedPlace: { dcid: place, name: place, types: [] },
          selectedStatVars: emptySvInfo,
        });
      });
  }

  function updateURL(): void {
    const urlParams = new URLSearchParams(window.location.hash.split("#")[1]);
    const urlParamVals = {
      [URL_PARAM_KEYS.PLACE_TYPE]: selectedOptions.enclosedPlaceType,
      [URL_PARAM_KEYS.PLACE]: selectedOptions.selectedPlace
        ? selectedOptions.selectedPlace.dcid
        : "",
      [URL_PARAM_KEYS.STAT_VARS]: Object.keys(
        selectedOptions.selectedStatVars
      ).join(SEPARATOR),
      [URL_PARAM_KEYS.DATE_RANGE]: selectedOptions.dateRange
        ? PARAM_VALUE_TRUE
        : "",
      [URL_PARAM_KEYS.MIN_DATE]: selectedOptions.minDate,
      [URL_PARAM_KEYS.MAX_DATE]: selectedOptions.maxDate,
    };
    for (const key of Object.keys(urlParamVals)) {
      const val = urlParamVals[key];
      if (_.isEmpty(val)) {
        urlParams.delete(key);
      } else {
        urlParams.set(key, val);
      }
    }
    window.location.hash = urlParams.toString();
  }

  function validateDate(date: string, isMinDate: boolean): void {
    const dateError = !_.isEmpty(date) && !isValidDate(date);
    setValidationErrors((prev) => {
      return {
        ...prev,
        maxDate: !isMinDate ? dateError : prev.maxDate,
        minDate: isMinDate ? dateError : prev.minDate,
      };
    });
  }

  function onGetDataButtonClicked(): void {
    let incompleteSelectionMessage = "";
    if (selectedOptions.dateRange) {
      if (
        (!_.isEmpty(selectedOptions.minDate) &&
          !isValidDate(selectedOptions.minDate)) ||
        (!_.isEmpty(selectedOptions.maxDate) &&
          !isValidDate(selectedOptions.maxDate))
      ) {
        incompleteSelectionMessage = "Invalid dates entered.";
      }
    }
    if (
      _.isEmpty(selectedOptions.selectedStatVars) ||
      _.isEmpty(selectedOptions.selectedPlace) ||
      _.isEmpty(selectedOptions.enclosedPlaceType)
    ) {
      incompleteSelectionMessage =
        "Please select a place, place type, and at least one variable.";
    }
    setValidationErrors((prev) => {
      return { ...prev, incompleteSelectionMessage };
    });
    if (!_.isEmpty(incompleteSelectionMessage)) {
      return;
    }
    setShowPreview(true);
    setPreviewDisabled(false);
    updateURL();
  }
}
Example #18
Source File: SignupPage.tsx    From TutorBase with MIT License 4 votes vote down vote up
export function SignUpPage() {
    const [signUpData, setSignUpData] = useState({
        first_name: "",
        last_name: "",
        email: "",
        password: "",
        phone_number: "",
        visible: false,
        passwordValid: false,
        emailValid: false,
        firstNameValid: false,
        lastNameValid: false,
        phoneValid: false,
        loginValid: true,
        emailTaken: false,
    });

    const history = useHistory();

    const HandleChange = (event: any) => {
        let name = event.target.name;
        let value = event.target.value;

        if (name === "first_name")
            IsFirstNameValid(value)
        else if (name === "last_name")
            IsLastNameValid(value)
        else if (name === "email")
            IsEmailValid(value)
    };

    const IsFirstNameValid = (value: any) => {
        let firstValid = false;
        if (value.length > 0)
            firstValid = true

        setSignUpData({
            ...signUpData,
            first_name: value,
            firstNameValid: firstValid,
        })
    }

    const IsLastNameValid = (value: any) => {
        let lastValid = false;
        if (value.length > 0)
            lastValid = true

        setSignUpData({
            ...signUpData,
            last_name: value,
            lastNameValid: lastValid,
        })
    }

    const IsEmailValid = (value: string) => {
        setSignUpData((signUpData: any) => ({
            ...signUpData,
            email: value,
            emailValid: isEmail.validate(value),
            emailTaken: false,
        }));
    }

    const IsPhoneNumberValid = (value: any) => {
        let phoneValid = false;
        if (value.length === 0 || (value.length === 10 && value.match(/^[0-9]+$/) != null))
            phoneValid = true

        setSignUpData({
            ...signUpData,
            phone_number: value,
            phoneValid: phoneValid,
        })
    }

    const CreateUser = async () => {
        let body = {
            "email": signUpData.email,
            "first_name": signUpData.first_name,
            "last_name": signUpData.last_name,
            "phone": signUpData.phone_number,
        }

        const request = await fetch(ApiBaseAddress + "api/users", {
            method: "post",
            body: JSON.stringify(body),
            headers: {
                "Content-Type": "application/json",
            },
            credentials: 'include',
        });

        if (request.ok) {
            history.push("home");
        } else {
            setSignUpData((signUpData) => ({
                ...signUpData,
                emailTaken: true,
                emailValid: false,
            }));
        }
    }
        
    const SubmitEvent = (event: any) => {
        event.preventDefault();
        if (signUpData.firstNameValid && signUpData.lastNameValid) {
            CreateUser();
        } else {
            setSignUpData({
                ...signUpData,
                loginValid: false
            });
        }
    };

    return (
        <div className="flexBox">

            <Container className="signupContainer" fluid="xs" style={{padding: "3%", margin: "10em"}}>
                <Row>
                    <Col xs="1"/>
                    <Col xs="11">
                        <Form onSubmit={event => SubmitEvent(event)}>
                            <Label className="signupText">Sign Up</Label>
                            <FormGroup row>
                                <Container>
                                    <Row>
                                        <Col xs="auto">
                                            <Input
                                                type="text"
                                                className="form-control"
                                                name="first_name"
                                                placeholder="First Name"
                                                value={signUpData.first_name}
                                                onChange={event => HandleChange(event)}
                                                autoComplete="off"
                                            />
                                        </Col>
                                        <Col xs="auto">
                                            <div>
                                                {signUpData.firstNameValid ?
                                                    <MdCheck size="30px" color="green"></MdCheck> :
                                                    <VscError size="30px" color="red"></VscError>}
                                            </div>
                                        </Col>
                                    </Row>
                                </Container>
                            </FormGroup>
                            <FormGroup row>
                                <Container>
                                    <Row>
                                        <Col xs="auto">
                                            <Input
                                                type="text"
                                                className="form-control"
                                                name="last_name"
                                                placeholder="Last Name"
                                                value={signUpData.last_name}
                                                onChange={event => HandleChange(event)}
                                                autoComplete="off"
                                            />
                                        </Col>
                                        <Col xs="auto">
                                            <div>
                                                {signUpData.lastNameValid ?
                                                    <MdCheck size="30px" color="green"></MdCheck> :
                                                    <VscError size="30px" color="red"></VscError>}
                                            </div>
                                        </Col>
                                    </Row>

                                </Container>
                            </FormGroup>
                            <FormGroup row>
                                <Container>
                                    <Row>
                                        <Col xs="auto">
                                            <Input
                                                type="email"
                                                className="form-control"
                                                name="email"
                                                placeholder="Email"
                                                value={signUpData.email}
                                                onChange={event => HandleChange(event)}
                                                autoComplete="off"
                                            />
                                        </Col>
                                        <Col xs="auto">
                                            <div>
                                                {signUpData.emailValid ?
                                                    <MdCheck size="30px" color="green"></MdCheck> :
                                                    <VscError size="30px" color="red"></VscError>}
                                            </div>
                                        </Col>
                                    </Row>
                                </Container>
                            </FormGroup>
                            <FormGroup row>
                                <Container fluid>
                                    <Row>
                                        <Col xs="auto">
                                            <Input
                                                type="number"
                                                className="form-control"
                                                name="phone"
                                                placeholder="Cell Number (optional)"
                                                value={signUpData.phone_number}
                                                onChange={event => HandleChange(event)}
                                                autoComplete="off"
                                            />
                                        </Col>
                                        <Col xs="auto">
                                            <div>
                                                {signUpData.phoneValid ? (
                                                    <MdCheck size="30px" color="green"></MdCheck>
                                                ) : (
                                                    <VscError size="30px" color="red"></VscError>
                                                )}
                                            </div>
                                        </Col>
                                    </Row>
                                </Container>
                            </FormGroup>
                            <div>
                                {signUpData.loginValid ? '' : 'Invalid Fields'}
                            </div>
                            <div>
                                {signUpData.emailTaken && 'Email already taken'}
                            </div>
                            <Button color="danger" type="submit">
                                Create Account
                            </Button>
                            <div>Already have an account? Click <Link to='/login'>here</Link></div>
                        </Form>

                    </Col>
                </Row>
            </Container>
        </div>
    );
}
Example #19
Source File: TutorPanelSignup.tsx    From TutorBase with MIT License 4 votes vote down vote up
Panel = (props: IProps) => {
    let dispatch = useDispatch();
    let id = useSelector(selectClientData).clientId;
    const [modalOpen, setModalOpen] = useState(false);
    let params : string = useLocation().pathname;
    const [selectedSubjects, setSelectedSubjects] = useState(new Set<string>());
    const [RIN, setRIN] = useState("");
    const [validRIN, setValidRIN] = useState(false);
    const [cohort, setCohort] = useState("");
    const [comments, setComments] = useState("");
    const [footerMessage, setFooterMessage] = useState("");
    const [rate, setRate] = useState(0);
    let subjects = [];
    let selectedSubjectsOutput = [];
    const [subjectsList, setSubjectsList] = useState(new Array<Subject>());
    function checkRIN(value: string) {
        if (value.length !== 9) {
            setValidRIN(false);
        }
        else {
            setValidRIN(true); 
        }
        setRIN(value);

    }
    function submit() {
        if (!validRIN
            || cohort === ""
            || cohort === "Select"
            || selectedSubjects.size === 0) {
                setFooterMessage("Please complete required fields.");
                return;
        }
        let subs: Array<String> = Array.from(selectedSubjects.keys());
        api.TutorSignup(id, RIN, subs, comments, rate).then(res =>{
            res ?
            setFooterMessage("Application submitted.")
            : setFooterMessage("Error submitting. Please try again.");
            }).catch(err => {
                setFooterMessage("Error submitting. Please try again.")
            });
    }
    
    useEffect(() => {
        // Get all avaliable subjects from API
        const getSubjects = async () => {
            return (await api.GetSubjects()).data;
        }

        getSubjects().then(value => {
                setSubjectsList(value);
            }
        )
    }, []);
    for (let i = 0; i < subjectsList.length; i++) {
        let name: string = subjectsList[i].id;
        let color = SubjectToColor(name);
        subjects.push(
            (<Button
                style={{background: color}}
                onClick={() => setSelectedSubjects(SelectedSubjectsHandler(selectedSubjects, name))}
            >
                {name}
            </Button>
            )
            );
    }
    let selectedSubs:Array<string> = Array.from(selectedSubjects.keys());
    for (let i = 0; i < selectedSubs.length; i++) {
        let name: string = selectedSubs[i];
        let color = SubjectToColor(name);
        selectedSubjectsOutput.push(
            (
               
                    <Badge
                    style={{
                        backgroundColor: color,
                        cursor:'default',
                        color: "black",
                        minWidth: '6em',
                        display: "flex",
                        flexDirection:'row',
                        alignItems: 'center',
                        marginRight: '0.5em'
                    }}
                    pill
                >
                    <div style={{
                        display: "flex",
                        flex: '50%',
                    }}>
                        {name + ' '}
                    </div> 
                    <Button 
                    close 
                    style={{
                        display: "flex",
                        flex: '50%',
                        alignItems: 'center'
                    }}
                    onClick={() => setSelectedSubjects(SelectedSubjectsHandler(selectedSubjects, name))} /> 
                    
                </Badge>
            )
        );
    }
    return (
        <div id="panel-wrapper">
            <Navbar className={classNames("navbar-expand-lg", "navbar-light", "bg-light", "border-bottom", "shadow")}>
                <Button className="btn-red" id="menu-toggle" onClick={() => {
                    dispatch(actions.toggleSidebar());
                }}>☰</Button>
            </Navbar>

            <Container fluid className="background" style={{marginBottom:'10em'}}>
                <hr></hr>
                <Row xs="2" className="parent">

                </Row>
                <div style={{display:'flex', flexDirection:'column', flexWrap:'wrap', alignContent:'center'}}>
                    {props.isLoading ? (
                        <div style={{display:'flex', flexDirection:'row', flex:'1 1 0px', flexWrap:'wrap', justifyContent:'center', marginTop:'10em'}}>
                            <Spinner style={{color:'#E66064'}}></Spinner>
                        </div>) 
                    : (
                    <div>
                        <div style={{display:'flex', flexDirection:'row', flex:'1 1 0px', flexWrap:'wrap', justifyContent:'center', marginTop:'10em'}}>
                            <h5>You are not currently signed up as a tutor. This dashboard is for tutors only. You can apply to be a TutorBase tutor below!
                            </h5></div>
                            
                            <div style={{display:'flex', flexDirection:'row', flex:'1 1 0px', flexWrap:'wrap', justifyContent:'center', marginTop:'1em'}}>
                            <Button 
                                className="btn-red" 
                                style={{height:'4em', width:'10em', borderRadius:'20em'}}
                                onClick={() => setModalOpen(true)}
                            >
                                Sign up as tutor
                            </Button>
                            <Modal
                                centered={true}
                                scrollable={true}
                                isOpen={modalOpen}
                            >
                                <ModalHeader toggle={() => setModalOpen(!modalOpen)}>
                                    Tutor Application Form
                                </ModalHeader>
                                <ModalBody>
                                <h5>RIN</h5>
                                <Input 
                                    defaultValue={RIN}
                                    onChange={(e) => checkRIN(e.target.value)}
                                    valid={validRIN}
                                    invalid={!validRIN}
                                    />
                                <p />
                                <h5>Cohort</h5>
                                <Input 
                                type="select"
                                    onChange={(e) => setCohort(e.target.value)}
                                    initialValue="Select"
                                    invalid={cohort === "" || cohort === "Select"}>
                                        <option>
                                        Select
                                    </option>
                                    <option>
                                        Freshman
                                    </option>
                                    <option>
                                        Sophomore
                                    </option>
                                    <option>
                                        Junior
                                    </option>
                                    <option>
                                        Senior
                                    </option>
                                    <option>
                                        Graduate
                                    </option>
                                    </Input>
                                <p />
                                <h5>Select Subjects to tutor</h5>
                                <ButtonGroup>
                                    {subjects}
                                    
                                </ButtonGroup>
                                <p>
                                    Selected:
                                    <Card
                                    outline={selectedSubjects.size === 0}
                                    color= {selectedSubjects.size === 0 ? "danger" : ""}>
                                <CardBody 
                                    style={{
                                        display: "flex",
                                        background: "lightgray",
                                        minHeight: "4em",
                                        flexWrap: 'wrap'
                                    }}>
                                {selectedSubjectsOutput}


                            </CardBody></Card>
                            </p>
                            <p>
                                <h5>Hourly Rate ($) (optional)</h5>
                                <Input
                                type="number"
                                    onChange={(e) => setRate(+(e.target.value))} />
                                </p>
                                <h5>Comments (optional)</h5>
                                <Input 
                                    type="textarea"
                                    onChange={(e) => setComments(e.target.value)} />

                                </ModalBody>
                                <ModalFooter>
                                <p style={{color: footerMessage === "Application submitted." ? 'green' : 'red'}}>
                                    {footerMessage}
                                </p>

                                <Button
                                    color="primary"
                                    onClick={() => submit()}
                                >
                                    Submit
                                </Button>
                                {' '}
                                <Button onClick={() => setModalOpen(false)}>
                                    Cancel
                                </Button>
                                </ModalFooter>
                            </Modal>
                        </div>
                    </div>
                    )}
                </div>
            </Container>
        </div>
    );
}
Example #20
Source File: ClientSettings.tsx    From TutorBase with MIT License 4 votes vote down vote up
ClientSettings = () => {
    let clientData = useSelector(selectClientData);
    let dispatch = useDispatch();

    let [nameModalOpen, setNameModalOpen] = useState<boolean>(false);
    let [imgModalOpen, setImgModalOpen] = useState<boolean>(false);
    
    let [tempName, setTempName] = useState<Name>({
        first_name: "", 
        last_name: ""
    });
    let [clientName, setClientName] = useState<Name>({
        first_name: "",
        last_name: ""
    }); 
    let [croppedImg, setCroppedImg] = useState<string>("");
    let [clientImg, setClientImg] = useState<string>("");

    const saveNameChange = async () => {
        let name: Name = {first_name: tempName.first_name, last_name: tempName.last_name};
        await api.SetClientName(name, clientData.clientId);
        setClientName(name);
        dispatch(clientDataActions.setFirstName(tempName.first_name));
        dispatch(clientDataActions.setLastName(tempName.last_name));
        setNameModalOpen(false);
    }

    const handleImageSave = async (img: string) => {
        await api.SetClientProfileImage(img, clientData.clientId);
        setClientImg(img);
        dispatch(clientDataActions.setProfileImage(img));
    }

    const saveImgChange = async () => {
        if(croppedImg.toString() !== "") {
            CompressAndSaveImg(croppedImg, clientData.first_name + clientData.last_name + "-photo", handleImageSave);
        } else {
            handleImageSave(croppedImg);
        }

        setImgModalOpen(false);
    }

    const cancelNameChange = () => {
        setNameModalOpen(false); 
        setTempName(clientName);
    }

    const cancelImgChange = () => {
        setCroppedImg("");
        setImgModalOpen(false);
    }

    useEffect(() => {
        const getUser = async () => {
            return (await api.GetUserById(clientData.clientId)).data;
        }
        getUser().then(value => {
            setTempName({first_name: value[0].first_name, last_name: value[0].last_name});
            setClientName({first_name: value[0].first_name, last_name: value[0].last_name});
            setClientImg(value[0].profile_img);
            dispatch(clientDataActions.setFirstName(value[0].first_name));
            dispatch(clientDataActions.setLastName(value[0].last_name));
            dispatch(clientDataActions.setProfileImage(value[0].profile_img));
        });
        
    }, [clientData.clientId, dispatch]);

    return (
        <Container className="settings" fluid>
            <Row className="title" style={{ marginTop: '25px'}}>
            <div className="profile-text">Settings</div>
            </Row>

            <hr></hr>

             <Row>
                <ListGroup>

                    <ListGroupItem className="img-item">
                        <img src={clientImg === ""  ? defaultUser : clientImg} width="200px"/>
                        <a href="#" className="modal-link" onClick={() => setImgModalOpen(true)}>
                            <span className="heading-item"><FontAwesomeIcon icon={faEdit} className="font-adj"/></span>
                        </a>
                        <Modal isOpen={imgModalOpen} fade={false} toggle={() => {setImgModalOpen(!imgModalOpen)}} className="img-modal">
                            <ModalHeader toggle={() => {cancelImgChange()}}>Edit Profile Photo</ModalHeader>
                            <ModalBody>
                                Change your profile photo here.
                                <hr/>
                                <Avatar
                                    width={250}
                                    height={250}
                                    cropColor="#E66064"
                                    closeIconColor="#E66064"
                                    onCrop={(img) => setCroppedImg(img)}
                                    onClose={() => {setCroppedImg("")}}
                                    onBeforeFileLoad={() => {}}
                                    src={clientImg === "" ? defaultUser : clientImg}
                                />
                            </ModalBody>
                            <ModalFooter>
                                <Button className="btn-red" onClick={() => {saveImgChange()}}>Save</Button>
                                <Button color="secondary" onClick={() => {cancelImgChange()}}>Cancel</Button>
                            </ModalFooter>
                        </Modal>
                    </ListGroupItem>

                    <ListGroupItem className="name-item">
                        <span className="heading-item">{clientName.first_name} {clientName.last_name}</span>
                        <a href="#" className="modal-link" onClick={() => {setNameModalOpen(true)}}>
                            <span className="heading-item"><FontAwesomeIcon icon={faEdit} className="font-adj"/></span>
                        </a>
                        <Modal isOpen={nameModalOpen} fade={false} toggle={() => {setNameModalOpen(!nameModalOpen)}} className="name-modal">
                            <ModalHeader toggle={() => {cancelNameChange()}}>Edit Name</ModalHeader>
                            <ModalBody>
                                Change your name here.
                                <hr/>
                                <InputGroup>
                                    First Name:<Input id="first-name" value={tempName.first_name} onChange={(value) => setTempName({first_name: value.target.value, last_name: tempName.last_name})} />
                                </InputGroup>
                                <InputGroup>
                                    Last Name:<Input id="last-name" value={tempName.last_name} onChange={(value) => setTempName({first_name: tempName.first_name, last_name: value.target.value})} />
                                </InputGroup>
                            </ModalBody>
                            <ModalFooter>
                                <Button className="btn-red" onClick={() => {saveNameChange()}}>Save</Button>
                                <Button color="secondary" onClick={() => {cancelNameChange()}}>Cancel</Button>
                            </ModalFooter>
                        </Modal>
                    </ListGroupItem>

                 </ListGroup>
             </Row>
        </Container>
    );
}
Example #21
Source File: MeetingCard.tsx    From TutorBase with MIT License 4 votes vote down vote up
export function MeetingCard(props: IProps) {
    let { appt } = props;
    let cardType = appt.confirmed ? "upcoming-card" : "pending-card";
    let cardStatus = appt.confirmed ? "Upcoming" : "Pending";
    let [modalOpen, setModalOpen] = useState(false);
    let [cardExpanded, toggleCardExpansion] = useState<boolean>(false);
    let [meetingLink, setMeetingLink] = useState(appt.link !== null ? appt.link! : "");
    let [loading, setLoading] = useState(false);
    let [check, setCheck] = useState(false);
    let [err, setErr] = useState(false);
    let [clientData, setClientData] = useState<User>({
        _id: "",
        profile_img: "",
        phone: "",
        email: "",
        first_name: "",
        last_name: "",
    });
    function setMeetingLinkChange(link: React.FormEvent<HTMLInputElement>) {
        setMeetingLink(link.currentTarget.value);
    }
    async function updateMeetingLink() {
        setLoading(true);
        try {
            let res = await api.SetMeetingLink(appt.appt_id, meetingLink);
            setLoading(false);
            if (res.status === 200) {
                setCheck(true);
            }
        }
        catch {
            setLoading(false);
            setErr(true);
        }
        //setModalOpen(!modalOpen);
    }
    let [tutorFirstName, setTutorFirstName] = useState("");
    let [tutorLastName, setTutorLastName] = useState("");

    const confirmAppt = async () => {
        await api.ConfirmAppointment(appt.appt_id);
    }

    useEffect(() => {
        const getTutor = async () => {
            let tutor = (await api.GetTutorById(appt.tutor_id));
            setTutorFirstName(tutor.data[0]?.first_name ?? "Unknown");
            setTutorLastName(tutor.data[0]?.last_name ?? "Unknown");
        }

        getTutor();
    }, []);

    // Time checks for different card types
    if (!IsFutureDate(appt.start_time) && appt.confirmed){
        cardType = "completed-card";
        cardStatus = "Completed";
    }

    if (!IsFutureDate(appt.start_time) && !appt.confirmed){
        cardType = "denied-card";
        cardStatus = "Denied";
    }

    if (IsFutureDate(appt.start_time) && props.includePrevious) {
        return <></>
    }

    if (!IsFutureDate(appt.start_time) && !props.includePrevious) {
        return <></>
    }

    // Card creation
    let name = CapitalizeFirstLetter(tutorFirstName + " " + tutorLastName);
    let location = CapitalizeFirstLetter(appt.location);
    let date_time = BreakDownTime(appt.start_time);

    // Card tag setup
    let cardTag = <div className={"card-status"}>{cardStatus}</div>;
    if (cardStatus === "Pending" && props.isTutor) {
        cardTag = (
            <>
                <div className={"card-icon"}>
                    <Button color="success" onClick={() => confirmAppt()}>
                        <FontAwesomeIcon icon={faCheck} />
                    </Button>
                </div>

                <div className={"card-status"}>
                    {cardStatus}
                </div>
            </>
        );
    }

    // Card details
    let upperCardContent = (
        <>
            <div className={"card-name"}>{name}</div>
            <div className={"card-location"}>{location}</div>
            <div className={"card-time"}>{date_time[0] + " at " + date_time[1]}</div>
        </>
    );

    // Card structure
    let card = (
        <CompressedCard
            onClick={() => { toggleCardExpansion(!cardExpanded) }}
            className={cardType}
        >
            <div className={"card-container-start"}>
                {upperCardContent}
            </div>

            <div className={"card-container-end"}>
                {cardStatus === "Completed"
                    ? <FeedbackForm apptTutorId={appt.tutor_id} />
                    : <></>}
                {cardTag}
                <Button
                    color="none"
                    onClick={(e) => {
                        toggleCardExpansion(!cardExpanded)
                    }} >
                    <FontAwesomeIcon
                        icon={faArrowDown}
                    />
                </Button>
            </div>
        </CompressedCard>
    );

    if(cardExpanded) {
        card = (
            <ExpandedCard
                onClick={() => { toggleCardExpansion(!cardExpanded) }}
                className={cardType}
            >
                <div className={"card-container-start-expanded"}>{upperCardContent}</div>
                <div className={"card-container-end-expanded"}>
                    {cardStatus === "Completed" ? <FeedbackForm apptTutorId={appt.tutor_id} /> : <></>}
                    {cardTag}
                    <Button
                    color="none"
                    onClick={(e) => {
                    toggleCardExpansion(!cardExpanded)
                    }} >
                    <FontAwesomeIcon
                    icon={faArrowUp}
                    />
                    </Button>
                </div>

                <hr style={{width: '100%', backgroundColor: 'black', margin: '0 0.5em'}}/>

                <div className={"card-container-item"}>
                    {!appt.notes || appt.notes === "" ? ""
                    : "Client Notes: "
                    }
                </div>
                <div className={"break"}></div>
                <div className={"client-notes"}>{appt.notes}</div>
                <div className={"break"}></div>


                { props.isTutor ?
                <div>
                <div className={"client-notes"}>
                <Button
                    color="danger"
                    onClick={(e) => {
                        setModalOpen(!modalOpen);
                        e.stopPropagation();
                    }}
                    >
                    Add Zoom/Webex meeting link
                    </Button>
                                <Modal isOpen={modalOpen}>
                    <ModalHeader toggle={function noRefCheck(){}}>
                    Add Tutoring Meeting Link
                    </ModalHeader>
                    <ModalBody>
                    Link:
                    <Input onChange={setMeetingLinkChange} value={meetingLink}>
                    </Input>
                    </ModalBody>
                    <ModalFooter>
                    <Button
                        color={check ? "success": err ? "danger" : "primary"}

                        onClick={updateMeetingLink}
                    >
                    {loading ? (<Spinner />)
                        : check ? <FontAwesomeIcon icon={faCheck}/>
                        : err ? <div>Error<FontAwesomeIcon icon={faTimes}/></div>
                        : "Save"}
                    </Button>
                    {' '}
                    <Button onClick={() => setModalOpen(!modalOpen)}>
                        Cancel
                    </Button>
                    </ModalFooter>
                </Modal>
                </div>
                </div>
                : <div>{meetingLink === "" ? "" :
                (<div>
                    
                    <div className={"card-container-item "}>
                        Meeting Link:
                    </div>
                <div className={"client-notes"}><a href={meetingLink} target="new">{meetingLink}</a></div>
                </div>)
                }
                </div>
                }

            </ExpandedCard>
        );
    }

    return <>{card}</>;
}