@headlessui/react#Listbox TypeScript Examples

The following examples show how to use @headlessui/react#Listbox. 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: SelectHeader.tsx    From yet-another-generic-startpage with MIT License 6 votes vote down vote up
HeaderButton = styled(Listbox.Button)<Pick<SelectHeaderProps, "open">>`
  ${({ theme: { space, color }, open }) => css`
    height: calc(${space.medium} * 2);
    padding: 0 ${space.small};
    width: 100%;

    display: inline-flex;
    align-items: center;

    cursor: pointer;
    color: inherit;
    background-color: transparent;

    outline: none;
    border: none;
    border: ${color.fg.base} solid ${space.smallest};

    > svg {
      height: 1.2rem;
    }

    :hover {
      background-color: ${color.bg.highlight};
    }
    :focus-visible {
      background: ${color.bg.highlight};
      border-color: ${color.primary.base};
    }
    ${open &&
    css`
      border-color: ${color.primary.base};
    `}
  `}
`
Example #2
Source File: Select.tsx    From yet-another-generic-startpage with MIT License 6 votes vote down vote up
Select = ({
  value,
  placeholder,
  onChange,
  options,
  label,
}: SelectProps) => {
  const currentOption = getOptionByValue(options, value)

  return (
    <Wrapper>
      <Label label={label}>
        <Listbox value={currentOption?.value} onChange={onChange}>
          {({ open }) => (
            <>
              <SelectHeader
                label={currentOption?.label || placeholder}
                open={open}
              />
              <SelectOptions options={options} />
            </>
          )}
        </Listbox>
      </Label>
    </Wrapper>
  )
}
Example #3
Source File: SelectOptions.tsx    From yet-another-generic-startpage with MIT License 6 votes vote down vote up
StyledOptions = styled(Listbox.Options)`
  ${({ theme: { color, space } }) => css`
    position: absolute;
    width: 100%;
    padding: 0;
    margin: 0;
    color: ${color.fg.surface};
    background-color: ${color.bg.surface};
    outline: none;
    z-index: 10;
    box-shadow: 0 0 ${space.small} ${color.bg.shade};
  `}
`
Example #4
Source File: index.tsx    From interbtc-ui with Apache License 2.0 6 votes vote down vote up
SelectLabel = ({ className, ...rest }: SelectLabelProps): JSX.Element => (
  <Listbox.Label
    className={clsx(
      { 'text-interlayTextSecondaryInLightMode': process.env.REACT_APP_RELAY_CHAIN_NAME === POLKADOT },
      { 'dark:text-kintsugiTextSecondaryInDarkMode': process.env.REACT_APP_RELAY_CHAIN_NAME === KUSAMA },
      className
    )}
    {...rest}
  />
)
Example #5
Source File: index.tsx    From interbtc-ui with Apache License 2.0 6 votes vote down vote up
SelectOption = ({ value, className, ...rest }: SelectOptionProps): JSX.Element => (
  <Listbox.Option
    className={({ active }) =>
      clsx(
        active ? clsx('text-white', 'bg-interlayDenim') : 'text-textPrimary',
        'cursor-default',
        'select-none',
        'relative',
        'py-2',
        'pl-3',
        'pr-9',
        className
      )
    }
    value={value}
    {...rest}
  />
)
Example #6
Source File: select.tsx    From marina with MIT License 5 votes vote down vote up
Select: React.FC<Props> = ({ list, selected, onSelect, disabled, onClick }) => {
  return (
    <Listbox value={selected} onChange={onSelect} disabled={disabled}>
      {({ open }) => (
        <>
          <div
            onClick={() => {
              if (onClick) onClick();
            }}
          >
            <Listbox.Button className="border-primary ring-primary focus:ring-primary focus:border-primary focus:outline-none flex flex-row justify-between w-full px-3 py-2.5 border-2 rounded-md">
              <span className="font-md text-sm">{selected}</span>
              {open ? (
                <img src="assets/images/chevron-up.svg" alt="chevron" />
              ) : (
                <img
                  className="transform -rotate-90"
                  src="assets/images/chevron-left.svg"
                  alt="chevron"
                />
              )}
            </Listbox.Button>
          </div>
          {open && (
            <div>
              <Listbox.Options
                className="focus:outline-none px-3 py-2 text-left rounded-md shadow-lg"
                static
              >
                {list.map((item) => (
                  <Listbox.Option
                    className="focus:outline-none py-3 cursor-pointer"
                    key={item}
                    value={item}
                  >
                    {item}
                  </Listbox.Option>
                ))}
              </Listbox.Options>
            </div>
          )}
        </>
      )}
    </Listbox>
  );
}
Example #7
Source File: Nav.tsx    From vignette-web with MIT License 5 votes vote down vote up
// en: `ENG`,
// ja: `日本`,
// ko: `한국`,
// 'zh-CN': `中国`,
// 'zh-TW': `中国`,
// fil: `FIL`,
// fr: `FR`,
// id: `IDN`,
// de: `DE`,
// it: `IT`,
// nl: `NL`,
function MyListbox({ router }: { router: NextRouter }) {
  const [selectedLocale, setSelectedLocale] = useState(router.locale)

  return (
    <div>
      <Listbox
        value={selectedLocale}
        onChange={(selected) => {
          setSelectedLocale(selected)
          setCookies(`NEXT_LOCALE`, selected)
          router.push(router.asPath, undefined, {
            locale: selected,
          })
        }}
      >
        <Listbox.Button className="relative flex w-full cursor-default items-center rounded-lg bg-transparent pl-1 text-left text-sm font-semibold outline-none  sm:font-normal">
          <ReactCountryFlag
            countryCode={locales[selectedLocale as string].flag}
            svg
          />
          <span className="mx-1">
            {locales[selectedLocale as string].shortName}
          </span>
          <BiChevronDown />
        </Listbox.Button>
        <Transition
          as={Fragment}
          leave="transition ease-in duration-100"
          leaveFrom="opacity-100"
          leaveTo="opacity-0"
        >
          <Listbox.Options className="w-18 absolute z-100 mt-1 max-h-96 overflow-auto rounded-md border bg-white text-black shadow-lg focus:outline-none dark:border-neutral-700 dark:bg-[#181a1b] dark:text-white">
            {Object.keys(locales).map((key) => (
              /* Use the `active` state to conditionally style the active option. */
              /* Use the `selected` state to conditionally style the selected option. */
              <Listbox.Option key={key} value={key} as={Fragment}>
                {({ active, selected }) => (
                  <li
                    className={`flex cursor-default items-center px-2 py-1 sm:px-1 lg:py-0  ${
                      active
                        ? `bg-gray-100 dark:bg-neutral-700 `
                        : `bg-white  dark:bg-[#181a1b]`
                    }`}
                  >
                    <ReactCountryFlag countryCode={locales[key].flag} svg />
                    <span className="mx-1 text-sm text-black dark:text-white">
                      {locales[key].name}
                    </span>
                    {selected && (
                      <AiOutlineCheck className="fill-black dark:fill-white" />
                    )}
                  </li>
                )}
              </Listbox.Option>
            ))}
          </Listbox.Options>
        </Transition>
      </Listbox>
    </div>
  )
}
Example #8
Source File: BlogNav.tsx    From vignette-web with MIT License 5 votes vote down vote up
// en: `ENG`,
// ja: `日本`,
// ko: `한국`,
// 'zh-CN': `中国`,
// 'zh-TW': `中国`,
// fil: `FIL`,
// fr: `FR`,
// id: `IDN`,
// de: `DE`,
// it: `IT`,
// nl: `NL`,
function MyListbox({ router }: { router: NextRouter }) {
  const [selectedLocale, setSelectedLocale] = useState(router.locale)

  return (
    <div>
      <Listbox
        value={selectedLocale}
        onChange={(selected) => {
          setSelectedLocale(selected)
          setCookies(`NEXT_LOCALE`, selected)
          router.push(router.asPath, undefined, {
            locale: selected,
          })
        }}
      >
        <Listbox.Button className="relative flex w-full cursor-default items-center rounded-lg bg-transparent pl-1 text-left text-sm font-semibold outline-none  sm:font-normal">
          <ReactCountryFlag
            countryCode={locales[selectedLocale as string].flag}
            svg
          />
          <span className="mx-1">
            {locales[selectedLocale as string].shortName}
          </span>
          <BiChevronDown />
        </Listbox.Button>
        <Transition
          as={Fragment}
          leave="transition ease-in duration-100"
          leaveFrom="opacity-100"
          leaveTo="opacity-0"
        >
          <Listbox.Options className="w-18 absolute z-100 mt-1 max-h-96 overflow-auto rounded-md border bg-white text-black shadow-lg focus:outline-none dark:border-neutral-700 dark:bg-[#181a1b] dark:text-white">
            {Object.keys(locales).map((key) => (
              /* Use the `active` state to conditionally style the active option. */
              /* Use the `selected` state to conditionally style the selected option. */
              <Listbox.Option key={key} value={key} as={Fragment}>
                {({ active, selected }) => (
                  <li
                    className={`flex cursor-default items-center px-2 py-1 sm:px-1 lg:py-0  ${
                      active
                        ? `bg-gray-100 dark:bg-neutral-700 `
                        : `bg-white  dark:bg-[#181a1b]`
                    }`}
                  >
                    <ReactCountryFlag countryCode={locales[key].flag} svg />
                    <span className="mx-1 text-sm text-black dark:text-white">
                      {locales[key].name}
                    </span>
                    {selected && (
                      <AiOutlineCheck className="fill-black dark:fill-white" />
                    )}
                  </li>
                )}
              </Listbox.Option>
            ))}
          </Listbox.Options>
        </Transition>
      </Listbox>
    </div>
  )
}
Example #9
Source File: DropdownMenu.tsx    From sdk with ISC License 5 votes vote down vote up
export default function DropdownMenu({title, selectedItem, setSelectedItem, items}: DropdownMenuProps) {
    return(
        <div className={"flex items-center justify-center"}>
            <div className={"w-60 max-w-xs"}>
                <Listbox value={selectedItem} onChange={setSelectedItem} as={"div"} className={"space-y-1"}>
                    {({open}) => (
                        <>
                            <Listbox.Label className={"block text-sm font-medium"}>{title}</Listbox.Label>
                            <div className={"relative"}>
                                <span className={"inline-block w-full rounded-md"}>
                                    <Listbox.Button
                                        className={classNames(
                                            "cursor-default relative w-full",
                                            "py-2 pl-3 pr-10 text-center",
                                            "rounded-md shadow-md",
                                            "sm:text-sm",
                                            "focus:outline-none focus-visible:ring-2",
                                            "focus-visible:ring-opacity-75 focus-visible:ring-white",
                                            "focus-visible:ring-offset-2 focus-visible:border-indigo-500"
                                        )}
                                    >
                                        <span className={"block truncate text-lg"}>{selectedItem?.label || ""}</span>
                                        <span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
                                            <SelectorIcon className="w-5 h-5 text-gray-400" aria-hidden="true"/>
                                        </span>
                                    </Listbox.Button>
                                </span>
                                <Transition
                                    // as={Fragment}
                                    show={open}
                                    unmount={false}
                                    enter="transition duration-100 ease-in"
                                    enterFrom="transform opacity-0"
                                    enterTo="transform opacity-100"
                                    leave="transition duration-75 ease-out"
                                    leaveFrom="transform opacity-100"
                                    leaveTo="transform opacity-0"
                                    className={"absolute mt-1 w-full rounded-md dark:bg-gray-600"}
                                >
                                    <Listbox.Options static className={"z-10 overflow-auto max-h-60 sm:text-sm"}>
                                        {items.map((item) => {
                                            const selected = item.key === selectedItem.key;
                                            return(<Listbox.Option
                                                    key={item.key}
                                                    value={item}
                                                    disabled={item.disabled || selected}
                                                >
                                                    {({ active }) => (
                                                        <div className={classNames(
                                                            "cursor-default select-none relative",
                                                            active ? "bg-blue-500" : ""
                                                        )}>
                                                            <span
                                                                className={classNames(
                                                                    selected ? "font-semibold" : "font-normal",
                                                                    "block truncate"
                                                                )}
                                                            >
                                                                {item.label}
                                                            </span>
                                                        </div>
                                                    )}
                                                </Listbox.Option>
                                            )})}
                                    </Listbox.Options>
                                </Transition>
                            </div>
                        </>
                    )}
                </Listbox>
            </div>
        </div>
    )
}
Example #10
Source File: VariantSelectButton.tsx    From Meshtastic with GNU General Public License v3.0 5 votes vote down vote up
VariantSelectButton = ({
  options,
}: VariantSelectButtonProps): JSX.Element => {
  const [selected, setSelected] = useState(options[options.length - 1]);

  return (
    <Listbox value={selected} onChange={setSelected}>
      {({ open }) => (
        <>
          <div className="relative select-none">
            <Listbox.Button as={Fragment}>
              <motion.button
                whileHover={{ backgroundColor: 'var(--tertiary)' }}
                whileTap={{ scale: 0.99 }}
                className="relative -mt-5 ml-2 flex w-fit gap-1 rounded-lg bg-secondary p-2 py-2 pl-3 pr-10 text-lg font-medium leading-6 shadow-md md:mt-2"
              >
                <span className="block truncate">{selected.name}</span>
                <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
                  <HiSelector
                    className="h-5 w-5 text-gray-400"
                    aria-hidden="true"
                  />
                </span>
              </motion.button>
            </Listbox.Button>

            <Transition
              show={open}
              as={Fragment}
              leave="transition ease-in duration-100"
              leaveFrom="opacity-100"
              leaveTo="opacity-0"
            >
              <Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-primary py-1 shadow-md ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
                {options.map((variant, index) => (
                  <Listbox.Option
                    key={index}
                    className={({ active }) =>
                      `relative cursor-default select-none py-2 pl-3 pr-9 ${
                        active ? 'bg-secondary' : ''
                      }`
                    }
                    value={variant}
                  >
                    {({ selected, active }) => (
                      <>
                        <span
                          className={`block truncate ${
                            selected ? 'font-semibold' : 'font-normal'
                          }`}
                        >
                          {variant.name}
                        </span>

                        {selected ? (
                          <span
                            className={`absolute inset-y-0 right-0 flex items-center pr-4 ${
                              active ? '' : 'text-primaryInv'
                            }`}
                          >
                            <FiCheck className="h-5 w-5" aria-hidden="true" />
                          </span>
                        ) : null}
                      </>
                    )}
                  </Listbox.Option>
                ))}
              </Listbox.Options>
            </Transition>
          </div>
        </>
      )}
    </Listbox>
  );
}
Example #11
Source File: index.tsx    From interbtc-ui with Apache License 2.0 5 votes vote down vote up
Select = ({ value, onChange, children }: SelectProps): JSX.Element => {
  return (
    <Listbox value={value} onChange={onChange}>
      {children}
    </Listbox>
  );
}
Example #12
Source File: index.tsx    From interbtc-ui with Apache License 2.0 5 votes vote down vote up
SelectOptions = ({
  open,
  className,
  variant = SELECT_VARIANTS.optionSelector,
  ...rest
}: SelectOptionsProps): JSX.Element => (
  <Transition
    show={open}
    as={React.Fragment}
    leave={clsx('transition', 'ease-in', 'duration-100')}
    leaveFrom='opacity-100'
    leaveTo='opacity-0'
  >
    <Listbox.Options
      static
      className={clsx(
        'absolute',
        'z-interlaySpeedDial',
        'mt-1',
        'w-full',
        'bg-white',
        'max-h-56',
        'rounded',
        'py-1',
        'text-base',
        'ring-1',

        'ring-black',
        'ring-opacity-25',
        'dark:ring-white',
        'dark:ring-opacity-25',

        'overflow-auto',
        'focus:outline-none',
        'sm:text-sm',
        {
          [clsx('bg-white')]: process.env.REACT_APP_RELAY_CHAIN_NAME === POLKADOT
        },
        {
          [clsx('dark:bg-kintsugiMidnight')]: process.env.REACT_APP_RELAY_CHAIN_NAME === KUSAMA
        },
        {
          [clsx('dark:bg-kintsugiMidnight-900')]:
            process.env.REACT_APP_RELAY_CHAIN_NAME === KUSAMA && variant === SELECT_VARIANTS.formField
        },
        className
      )}
      {...rest}
    />
  </Transition>
)
Example #13
Source File: index.tsx    From interbtc-ui with Apache License 2.0 5 votes vote down vote up
SelectButton = ({
  className,
  children,
  variant = 'optionSelector',
  error,
  ...rest
}: SelectButtonProps): JSX.Element => (
  <Listbox.Button
    className={clsx(
      'focus:outline-none',
      'focus:ring',
      'focus:ring-opacity-50',

      'relative',
      'w-full',
      BORDER_CLASSES,

      'rounded-md',
      'pl-3',
      'pr-10',
      'py-2',
      'text-left',
      'cursor-default',
      'leading-7',
      {
        [clsx('bg-white', 'focus:border-interlayDenim-300', 'focus:ring-interlayDenim-200')]:
          process.env.REACT_APP_RELAY_CHAIN_NAME === POLKADOT
      },
      {
        [clsx(
          'dark:bg-kintsugiMidnight-900',
          'dark:focus:border-kintsugiSupernova-300',
          'dark:focus:ring-kintsugiSupernova-200'
        )]: process.env.REACT_APP_RELAY_CHAIN_NAME === KUSAMA
      },
      {
        [clsx('dark:bg-kintsugiMidnight')]: variant === SELECT_VARIANTS.formField
      },
      {
        [clsx(
          { 'border-interlayCinnabar': process.env.REACT_APP_RELAY_CHAIN_NAME === POLKADOT },
          { 'dark:border-kintsugiThunderbird': process.env.REACT_APP_RELAY_CHAIN_NAME === KUSAMA }
        )]: error
      },
      className
    )}
    {...rest}
  >
    {children}
    <span
      className={clsx(
        'ml-3',
        'absolute',
        'inset-y-0',
        'right-0',
        'flex',
        'items-center',
        'pr-2',
        'pointer-events-none'
      )}
    >
      <SelectorIcon className={clsx('h-5', 'w-5', 'text-textSecondary')} aria-hidden='true' />
    </span>
  </Listbox.Button>
)
Example #14
Source File: ThemeToggle.tsx    From aljoseph.co with MIT License 5 votes vote down vote up
export function ThemeToggle({ panelClassName = 'mt-4' }) {
	const { setTheme, theme } = useTheme();

  return (
    <Listbox value={theme} onChange={setTheme}>
      <Listbox.Label className="sr-only">Theme</Listbox.Label>
      <Listbox.Button type="button">
        <span className="dark:hidden">
          <SunIcon className="w-6 h-6" selected={theme !== 'system'} />
        </span>
        <span className="hidden dark:inline">
          <MoonIcon className="w-6 h-6" selected={theme !== 'system'} />
        </span>
      </Listbox.Button>
      <Listbox.Options
        className={cx(
          'absolute z-50 top-full right-0 bg-white rounded-lg ring-1 ring-slate-900/10 shadow-lg overflow-hidden w-36 py-1 text-sm text-slate-700 font-semibold dark:bg-slate-800 dark:ring-0 dark:highlight-white/5 dark:text-slate-300',
          panelClassName
        )}
      >
        {settings.map(({ value, label, icon: Icon }) => (
          <Listbox.Option key={value} value={value} as={Fragment}>
            {({ active, selected }) => (
              <li
                className={cx(
                  'py-1 px-2 flex items-center cursor-pointer',
                  selected && 'text-pink-300',
                  active && 'bg-slate-50 dark:bg-slate-600/30'
                )}
              >
                <Icon selected={selected} className="w-6 h-6 mr-2" />
                {label}
              </li>
            )}
          </Listbox.Option>
        ))}
      </Listbox.Options>
    </Listbox>
  )
}
Example #15
Source File: token-price-select.tsx    From arkadiko with GNU General Public License v3.0 5 votes vote down vote up
export function TokenPriceSelect({ tokenPrices, selected, setSelected }: Props) {
  return (
    <Listbox value={selected} onChange={setSelected}>
      {({ open }) => (
        <>
          <Listbox.Label className="block text-sm font-medium text-gray-700 sr-only">Token price</Listbox.Label>
          <div className="relative mt-1">
            <Listbox.Button className="relative w-full py-2 pl-3 pr-10 text-left bg-white border border-gray-300 rounded-md shadow-sm cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
              <span className="block truncate">{selected.name}</span>
              <span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
                <SelectorIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
              </span>
            </Listbox.Button>

            <Transition
              show={open}
              as={Fragment}
              leave="transition ease-in duration-100"
              leaveFrom="opacity-100"
              leaveTo="opacity-0"
            >
              <Listbox.Options className="absolute z-10 w-full py-1 mt-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
                {tokenPrices.map((token:any) => (
                  <Listbox.Option
                    key={token.id}
                    className={({ active }) =>
                      classNames(
                        active ? 'text-white bg-indigo-600' : 'text-gray-900',
                        'cursor-default select-none relative py-2 pl-3 pr-9'
                      )
                    }
                    value={token}
                  >
                    {({ selected, active }) => (
                      <>
                        <span className={classNames(selected ? 'font-semibold' : 'font-normal', 'block truncate')}>
                          {token.name}
                        </span>

                        {selected ? (
                          <span
                            className={classNames(
                              active ? 'text-white' : 'text-indigo-600',
                              'absolute inset-y-0 right-0 flex items-center pr-4'
                            )}
                          >
                            <CheckIcon className="w-5 h-5" aria-hidden="true" />
                          </span>
                        ) : null}
                      </>
                    )}
                  </Listbox.Option>
                ))}
              </Listbox.Options>
            </Transition>
          </div>
        </>
      )}
    </Listbox>
  )
}
Example #16
Source File: select.tsx    From website with Apache License 2.0 5 votes vote down vote up
export function Select<T>(props: {
	items: Array<Value<T>>;
	selected: Value<T>;
	setSelected: (value: Value<T>) => unknown;
}) {
	const {selected, setSelected} = props;

	return (
		<div className="w-24">
			<Listbox value={selected} onChange={setSelected}>
				<div className="relative mt-1">
					<Listbox.Button className="relative py-2 pr-10 pl-3 w-full text-left bg-black hover:bg-gray-800 rounded-lg focus-visible:border-indigo-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 shadow-md cursor-default sm:text-sm">
						<span className="block truncate">{selected.name}</span>
						<span className="flex absolute inset-y-0 right-0 items-center pr-2 pointer-events-none">
							<HiChevronDown
								className="w-4 h-4 text-gray-400"
								aria-hidden="true"
							/>
						</span>
					</Listbox.Button>

					<Transition
						as={Fragment}
						leave="transition ease-in duration-100"
						leaveFrom="opacity-100"
						leaveTo="opacity-0"
					>
						<Listbox.Options className="overflow-auto absolute py-1 mt-1 w-full max-h-60 text-base bg-white rounded-md focus:outline-none ring-1 ring-black/5 shadow-lg sm:text-sm">
							{props.items.map(item => (
								<Listbox.Option
									key={item.name}
									className={({active}) =>
										`${active ? 'text-amber-900 bg-amber-100' : 'text-gray-900'}
                          cursor-default select-none relative py-2 pl-10 pr-4`
									}
									value={item}
								>
									{({selected, active}) => (
										<>
											<span
												className={`${
													selected ? 'font-medium' : 'font-normal'
												} block truncate`}
											>
												{item.name}
											</span>
											{selected && (
												<span
													className={`${
														active ? 'text-amber-600' : 'text-amber-600'
													}
                                absolute inset-y-0 left-0 flex items-center pl-3`}
												>
													<HiCheck className="w-5 h-5" aria-hidden="true" />
												</span>
											)}
										</>
									)}
								</Listbox.Option>
							))}
						</Listbox.Options>
					</Transition>
				</div>
			</Listbox>
		</div>
	);
}
Example #17
Source File: SelectOption.tsx    From yet-another-generic-startpage with MIT License 5 votes vote down vote up
SelectOption = ({ value, label }: Option) => (
  <StyledOption>
    <Listbox.Option value={value} className={getOptionClasses}>
      {label}
    </Listbox.Option>
  </StyledOption>
)
Example #18
Source File: index.tsx    From ledokku with MIT License 4 votes vote down vote up
App = () => {
  const history = useHistory();
  const toast = useToast();
  const { id: appId } = useParams<{ id: string }>();
  const [isUnlinkModalOpen, setIsUnlinkModalOpen] = useState(false);
  const [isLinkModalOpen, setIsLinkModalOpen] = useState(false);
  const [arrayOfLinkLogs, setArrayOfLinkLogs] = useState<RealTimeLog[]>([]);
  const [arrayOfUnlinkLogs, setArrayOfUnlinkLogs] = useState<RealTimeLog[]>([]);
  const [databaseAboutToUnlink, setdatabaseAboutToUnlink] = useState<string>();
  const [isTerminalVisible, setIsTerminalVisible] = useState(false);
  const [processStatus, setProcessStatus] = useState<
    'running' | 'notStarted' | 'finished'
  >('notStarted');
  const [unlinkLoading, setUnlinkLoading] = useState(false);
  const [linkLoading, setLinkLoading] = useState(false);

  const [selectedDb, setSelectedDb] = useState({
    value: { name: '', id: '', type: '' },
    label: 'Please select database',
  });

  const [
    linkDatabaseMutation,
    {
      data: databaseLinkData,
      loading: databaseLinkLoading,
      error: databaseLinkError,
    },
  ] = useLinkDatabaseMutation();

  const [unlinkDatabaseMutation] = useUnlinkDatabaseMutation();
  useUnlinkDatabaseLogsSubscription({
    onSubscriptionData: (data) => {
      const logsExist = data.subscriptionData.data?.unlinkDatabaseLogs;
      if (logsExist) {
        setArrayOfUnlinkLogs((currentLogs) => {
          return [...currentLogs, logsExist];
        });
        if (
          logsExist.type === 'end:success' ||
          logsExist.type === 'end:failure'
        ) {
          setProcessStatus('finished');
        }
      }
    },
  });

  useLinkDatabaseLogsSubscription({
    onSubscriptionData: (data) => {
      const logsExist = data.subscriptionData.data?.linkDatabaseLogs;
      if (logsExist) {
        setArrayOfLinkLogs((currentLogs) => {
          return [...currentLogs, logsExist];
        });

        if (
          logsExist.type === 'end:success' ||
          logsExist.type === 'end:failure'
        ) {
          setProcessStatus('finished');
        }
      }
    },
  });

  const {
    data: databaseData,
    loading: databaseDataLoading,
  } = useDatabaseQuery();

  const { data, loading, refetch /* error */ } = useAppByIdQuery({
    variables: {
      appId,
    },
    fetchPolicy: 'cache-and-network',
    ssr: false,
    skip: !appId,
  });

  if (!data || !databaseData) {
    return null;
  }

  // // TODO display error

  if (loading || databaseDataLoading) {
    // TODO nice loading
    return <p>Loading...</p>;
  }

  const { databases } = databaseData;

  const { app } = data;

  if (!app) {
    // TODO nice 404
    return <p>App not found.</p>;
  }

  const linkedDatabases = app.databases;
  const linkedIds = linkedDatabases?.map((db) => db.id);
  const notLinkedDatabases = databases.filter((db) => {
    return linkedIds?.indexOf(db.id) === -1;
  });
  // Hacky way to add create new database to link db select
  notLinkedDatabases.length > 0 &&
    notLinkedDatabases.push({ name: 'Create new database' } as any);

  const dbOptions = notLinkedDatabases.map((db) => {
    return {
      value: { name: db.name, id: db.id, type: db.type },
      label: <DatabaseLabel type={db.type} name={db.name} />,
    };
  });

  const handleUnlink = async (databaseId: string, appId: string) => {
    try {
      await unlinkDatabaseMutation({
        variables: {
          input: {
            databaseId,
            appId,
          },
        },
      });
      setIsTerminalVisible(true);
      setUnlinkLoading(true);
    } catch (e) {
      toast.error(e.message);
    }
  };

  const handleConnect = async (databaseId: string, appId: string) => {
    try {
      await linkDatabaseMutation({
        variables: {
          input: {
            databaseId,
            appId,
          },
        },
      });
      setSelectedDb({
        value: { name: '', id: '', type: '' },
        label: 'Please select database',
      });
      setIsTerminalVisible(true);
      setLinkLoading(true);
    } catch (e) {
      toast.error(e.message);
    }
  };

  return (
    <div>
      <HeaderContainer>
        <Header />
        <AppHeaderInfo app={app} />
        <AppHeaderTabNav app={app} />
      </HeaderContainer>

      <Container maxW="5xl" mt={10}>
        <div className="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2 gap-4 mt-10">
          <div>
            <Heading as="h2" size="md" py={5}>
              App info
            </Heading>
            <div className="bg-gray-100 shadow overflow-hidden rounded-lg border-b border-gray-200">
              <Table mt="4" mb="4" variant="simple">
                <Tbody mt="10">
                  <Tr py="4">
                    <Td className="font-semibold" py="3" px="4">
                      App name
                    </Td>
                    <Td py="3" px="4">
                      {app.name}
                    </Td>
                  </Tr>
                  <Tr>
                    <Td className="font-semibold" py="7" px="4">
                      id
                    </Td>
                    <Td w="1/3" py="3" px="4">
                      {app.id}
                    </Td>
                  </Tr>
                  <Tr>
                    <Td className="font-semibold" py="3" px="4">
                      Created at
                    </Td>
                    <Td py="3" px="4">
                      {app.createdAt}
                    </Td>
                  </Tr>
                </Tbody>
              </Table>
            </div>
          </div>
          <div className="w-full">
            <Heading as="h2" size="md" py={5}>
              Databases
            </Heading>
            {databases.length === 0 ? (
              <>
                <div className="mt-4 mb-4">
                  <h2 className="text-gray-400">
                    Currently you haven't created any databases, to do so
                    proceed with the database creation flow
                  </h2>
                </div>
                <RouterLink
                  to={{
                    pathname: '/create-database/',
                    state: app.name,
                  }}
                >
                  <Button width="large" color={'grey'}>
                    Create a database
                  </Button>
                </RouterLink>
              </>
            ) : (
              <>
                {notLinkedDatabases.length !== 0 ? (
                  <div>
                    <Listbox
                      as="div"
                      value={selectedDb}
                      //@ts-ignore
                      onChange={
                        selectedDb.value.name !== 'Create new database'
                          ? setSelectedDb
                          : history.push({
                              pathname: '/create-database',
                              state: app.name,
                            })
                      }
                    >
                      {({ open }) => (
                        <div className="relative w-80">
                          <Listbox.Button className="cursor-default relative w-full rounded-md border border-gray-300 bg-white pl-3 pr-10 py-2 text-left focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition ease-in-out duration-150 sm:text-sm sm:leading-5">
                            <span className="block truncate">
                              {selectedDb.label}
                            </span>
                            <span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
                              <svg
                                className="h-5 w-5 text-gray-400"
                                viewBox="0 0 20 20"
                                fill="none"
                                stroke="currentColor"
                              >
                                <path
                                  d="M7 7l3-3 3 3m0 6l-3 3-3-3"
                                  strokeWidth="1.5"
                                  strokeLinecap="round"
                                  strokeLinejoin="round"
                                />
                              </svg>
                            </span>
                          </Listbox.Button>
                          {open && (
                            <Transition
                              show={open}
                              enter="transition ease-out duration-100"
                              enterFrom="transform opacity-0 scale-95"
                              enterTo="transform opacity-100 scale-100"
                              leave="transition ease-in duration-75"
                              leaveFrom="transform opacity-100 scale-100"
                              leaveTo="transform opacity-0 scale-95"
                              className="absolute mt-1 w-full rounded-md bg-white shadow-lg z-10"
                            >
                              <Listbox.Options
                                static
                                className="max-h-60 rounded-md py-1 text-base leading-6 ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm sm:leading-5"
                              >
                                {dbOptions.map(
                                  (db) =>
                                    db.value.id !== selectedDb.value.id && (
                                      <Listbox.Option
                                        key={dbOptions.indexOf(db)}
                                        value={db as any}
                                      >
                                        {({ active }) => (
                                          <div
                                            className={cx(
                                              'cursor-default select-none relative py-2 px-4',
                                              {
                                                'bg-gray-200': active,
                                                'bg-white text-black': !active,
                                              }
                                            )}
                                          >
                                            {db.label}
                                          </div>
                                        )}
                                      </Listbox.Option>
                                    )
                                )}
                              </Listbox.Options>
                            </Transition>
                          )}
                        </div>
                      )}
                    </Listbox>
                    {databaseLinkError && (
                      <p className="text-red-500 text-sm font-semibold">
                        {databaseLinkError.graphQLErrors[0].message}
                      </p>
                    )}

                    <Button
                      color="grey"
                      width="large"
                      className="mt-2"
                      isLoading={
                        databaseLinkLoading &&
                        !databaseLinkData &&
                        !databaseLinkError
                      }
                      disabled={!selectedDb.value.id || linkLoading}
                      onClick={() => {
                        setIsLinkModalOpen(true);
                      }}
                    >
                      Link database
                    </Button>
                    {isLinkModalOpen && (
                      <Modal>
                        <ModalTitle>Link database</ModalTitle>
                        <ModalDescription>
                          {isTerminalVisible ? (
                            <>
                              <p className="mb-2 ">
                                Linking <b>{selectedDb.value.name}</b> with{' '}
                                <b>{app.name}</b>!
                              </p>
                              <p className="text-gray-500 mb-2">
                                Linking process usually takes a couple of
                                minutes. Breathe in, breathe out, logs are about
                                to appear below:
                              </p>
                              <Terminal className={'w-6/6'}>
                                {arrayOfLinkLogs.map((log) => (
                                  <p
                                    key={arrayOfLinkLogs.indexOf(log)}
                                    className="text-s leading-5"
                                  >
                                    {log.message}
                                  </p>
                                ))}
                              </Terminal>
                            </>
                          ) : (
                            <p>
                              Are you sure, you want to link{' '}
                              <b>{selectedDb.value.name}</b> to{' '}
                              <b>{app.name}</b>?
                            </p>
                          )}
                        </ModalDescription>
                        <ModalButton
                          ctaFn={() => {
                            setProcessStatus('running');
                            handleConnect(selectedDb.value.id, appId);
                          }}
                          ctaText={'Link'}
                          otherButtonText={'Cancel'}
                          isCtaLoading={isTerminalVisible ? false : linkLoading}
                          isCtaDisabled={isTerminalVisible}
                          isOtherButtonDisabled={processStatus === 'running'}
                          closeModal={() => {
                            setIsLinkModalOpen(false);
                            refetch({ appId });
                            setLinkLoading(false);
                            setIsTerminalVisible(false);
                            setProcessStatus('notStarted');
                          }}
                        />
                      </Modal>
                    )}
                  </div>
                ) : (
                  <>
                    <p className="mt-3 mb-3 text-cool-gray-400">
                      All your databases are already linked to this app! If you
                      want to create more databases proceed with create database
                      flow.
                    </p>
                    <div className="ml-80">
                      <Link to="/create-database">
                        <Button
                          color={'grey'}
                          variant="outline"
                          className="text-sm mr-3"
                        >
                          Create database
                        </Button>
                      </Link>
                    </div>
                  </>
                )}
                {!loading && app && app.databases && (
                  <>
                    <h2 className="mb-1 mt-3 font-semibold">
                      {app.databases.length > 0 && 'Linked databases'}
                    </h2>
                    {app.databases.map((database) => (
                      <div
                        key={app.databases?.indexOf(database)}
                        className="flex flex-row justify-start"
                      >
                        <Link
                          to={`/database/${database.id}`}
                          className="py-2 block"
                        >
                          <div className="w-64 flex items-center py-3 px-2 shadow hover:shadow-md transition-shadow duration-100 ease-in-out rounded bg-white">
                            {database.type === 'POSTGRESQL' ? (
                              <>
                                <PostgreSQLIcon size={16} className="mr-1" />
                              </>
                            ) : undefined}
                            {database.type === 'MONGODB' ? (
                              <>
                                <MongoIcon size={16} className="mr-1" />
                              </>
                            ) : undefined}
                            {database.type === 'REDIS' ? (
                              <>
                                <RedisIcon size={16} className="mr-1" />
                              </>
                            ) : undefined}
                            {database.type === 'MYSQL' ? (
                              <>
                                <MySQLIcon size={16} className="mr-1" />
                              </>
                            ) : undefined}
                            {database.name}
                          </div>
                        </Link>
                        <Button
                          width="normal"
                          className="mt-4 ml-2 h-10"
                          color="red"
                          onClick={() => {
                            setIsUnlinkModalOpen(true);
                            setdatabaseAboutToUnlink(database.name);
                          }}
                        >
                          Unlink
                        </Button>

                        {isUnlinkModalOpen && (
                          <Modal>
                            <ModalTitle>Unlink database</ModalTitle>
                            <ModalDescription>
                              {isTerminalVisible ? (
                                <>
                                  <p className="mb-2 ">
                                    Unlinking <b>{app.name}</b>
                                    from <b>{databaseAboutToUnlink}</b>!
                                  </p>
                                  <p className="text-gray-500 mb-2">
                                    Unlinking process usually takes a couple of
                                    minutes. Breathe in, breathe out, logs are
                                    about to appear below:
                                  </p>
                                  <Terminal className={'w-6/6'}>
                                    {arrayOfUnlinkLogs.map((log) => (
                                      <p
                                        key={arrayOfUnlinkLogs.indexOf(log)}
                                        className="text-s leading-5"
                                      >
                                        {log.message}
                                      </p>
                                    ))}
                                  </Terminal>
                                </>
                              ) : (
                                <p>
                                  Are you sure, you want to unlink{' '}
                                  <b>{app.name} </b>
                                  from <b>{databaseAboutToUnlink}</b> ?
                                </p>
                              )}
                            </ModalDescription>
                            <ModalButton
                              ctaFn={() => {
                                setProcessStatus('running');
                                handleUnlink(database.id, appId);
                              }}
                              ctaText={'Unlink'}
                              otherButtonText={'Cancel'}
                              isOtherButtonDisabled={
                                processStatus === 'running'
                              }
                              isCtaLoading={
                                isTerminalVisible ? false : unlinkLoading
                              }
                              isCtaDisabled={isTerminalVisible === true}
                              closeModal={() => {
                                setIsUnlinkModalOpen(false);
                                refetch({ appId });
                                setUnlinkLoading(false);
                                setIsTerminalVisible(false);
                                setdatabaseAboutToUnlink('');
                                setProcessStatus('notStarted');
                              }}
                            />
                          </Modal>
                        )}
                      </div>
                    ))}
                  </>
                )}
              </>
            )}
          </div>
        </div>
      </Container>
    </div>
  );
}
Example #19
Source File: index.tsx    From ledokku with MIT License 4 votes vote down vote up
Database = () => {
  const { id: databaseId } = useParams<{ id: string }>();
  const toast = useToast();
  const [isUnlinkModalOpen, setIsUnlinkModalOpen] = useState(false);
  const [isLinkModalOpen, setIsLinkModalOpen] = useState(false);
  const [arrayOfUnlinkLogs, setArrayOfUnlinkLogs] = useState<RealTimeLog[]>([]);
  const [arrayOfLinkLogs, setArrayOfLinkLogs] = useState<RealTimeLog[]>([]);
  const [appAboutToUnlink, setAppAboutToUnlink] = useState<string>();
  const [isTerminalVisible, setIsTerminalVisible] = useState(false);
  const [processStatus, setProcessStatus] = useState<
    'running' | 'notStarted' | 'finished'
  >('notStarted');
  const [unlinkLoading, setUnlinkLoading] = useState(false);
  const [linkLoading, setLinkLoading] = useState(false);

  const [selectedApp, setSelectedApp] = useState({
    value: { name: '', id: '' },
    label: 'Please select an app',
  });
  const [
    linkDatabaseMutation,
    {
      data: databaseLinkData,
      loading: databaseLinkLoading,
      error: databaseLinkError,
    },
  ] = useLinkDatabaseMutation();

  const { data: appsData } = useAppsQuery();

  const { data, loading, refetch /* error */ } = useDatabaseByIdQuery({
    variables: {
      databaseId,
    },
    ssr: false,
    skip: !databaseId,
  });

  const [unlinkDatabaseMutation] = useUnlinkDatabaseMutation();

  useUnlinkDatabaseLogsSubscription({
    onSubscriptionData: (data) => {
      const logsExist = data.subscriptionData.data?.unlinkDatabaseLogs;
      if (logsExist) {
        setArrayOfUnlinkLogs((currentLogs) => {
          return [...currentLogs, logsExist];
        });
        if (
          logsExist.type === 'end:success' ||
          logsExist.type === 'end:failure'
        ) {
          setProcessStatus('finished');
        }
      }
    },
  });

  useLinkDatabaseLogsSubscription({
    onSubscriptionData: (data) => {
      const logsExist = data.subscriptionData.data?.linkDatabaseLogs;
      if (logsExist) {
        setArrayOfLinkLogs((currentLogs) => {
          return [...currentLogs, logsExist];
        });
        if (
          logsExist.type === 'end:success' ||
          logsExist.type === 'end:failure'
        ) {
          setProcessStatus('finished');
        }
      }
    },
  });

  if (!data || !appsData) {
    return null;
  }

  // // TODO display error

  if (loading) {
    // TODO nice loading
    return <p>Loading...</p>;
  }

  const { database } = data;
  const { apps } = appsData;

  const handleUnlink = async (databaseId: string, appId: string) => {
    try {
      await unlinkDatabaseMutation({
        variables: {
          input: {
            databaseId,
            appId,
          },
        },
      });
      setIsTerminalVisible(true);
      setUnlinkLoading(true);
    } catch (e) {
      toast.error(e.message);
    }
  };

  if (!database) {
    // TODO nice 404
    return <p>Database not found.</p>;
  }

  const linkedApps = database.apps;
  const linkedIds = linkedApps?.map((db) => db.id);
  const notLinkedApps = apps.filter((db) => {
    return linkedIds?.indexOf(db.id) === -1;
  });

  const appOptions = notLinkedApps.map((app) => {
    return {
      value: { name: app.name, id: app.id },
      label: app.name,
    };
  });

  const handleConnect = async (databaseId: string, appId: string) => {
    try {
      await linkDatabaseMutation({
        variables: {
          input: {
            databaseId,
            appId,
          },
        },
      });
      setSelectedApp({
        value: { name: '', id: '' },
        label: 'Please select an app',
      });
      setIsTerminalVisible(true);
      setLinkLoading(true);
    } catch (e) {
      toast.error(e.message);
    }
  };

  return (
    <div>
      <HeaderContainer>
        <Header />
        <DatabaseHeaderInfo database={database} />
        <DatabaseHeaderTabNav database={database} />
      </HeaderContainer>

      <Container maxW="5xl" mt={10}>
        <div className="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2 gap-4 mt-10">
          <div>
            <Heading as="h2" size="md" py={5}>
              Database info
            </Heading>
            <div className="bg-gray-100 shadow overflow-hidden rounded-lg border-b border-gray-200">
              <Table mt="4" mb="4" variant="simple">
                <Tbody mt="10">
                  <Tr py="4">
                    <Td className="font-semibold" py="3" px="4">
                      Database name
                    </Td>
                    <Td py="3" px="4">
                      {database.name}
                    </Td>
                  </Tr>
                  <Tr>
                    <Td className="font-semibold" py="7" px="4">
                      id
                    </Td>
                    <Td w="1/3" py="3" px="4">
                      {database.id}
                    </Td>
                  </Tr>
                  <Tr>
                    <Td className="font-semibold" py="3" px="4">
                      Type
                    </Td>
                    <Td py="3" px="4">
                      {database.type}
                    </Td>
                  </Tr>
                </Tbody>
              </Table>
            </div>
          </div>

          <div className="w-full">
            <h1 className="font-bold text-lg font-bold py-5">Apps</h1>
            {apps.length === 0 ? (
              <>
                <div className="mt-3 mb-4">
                  <h2 className="text-gray-400">
                    Currently you haven't created apps, to do so proceed with
                    the app creation flow
                  </h2>
                </div>
                <Link to="/create-app">
                  <Button width="large" color={'grey'}>
                    Create app
                  </Button>
                </Link>
              </>
            ) : (
              <>
                {notLinkedApps.length !== 0 ? (
                  <div>
                    <Listbox
                      as="div"
                      value={selectedApp}
                      //@ts-ignore
                      onChange={setSelectedApp}
                    >
                      {({ open }) => (
                        <div className="relative w-80">
                          <Listbox.Button className="cursor-default relative w-full rounded-md border border-gray-300 bg-white pl-3 pr-10 py-2 text-left focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition ease-in-out duration-150 sm:text-sm sm:leading-5">
                            <span className="block truncate">
                              {selectedApp.label}
                            </span>
                            <span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
                              <svg
                                className="h-5 w-5 text-gray-400"
                                viewBox="0 0 20 20"
                                fill="none"
                                stroke="currentColor"
                              >
                                <path
                                  d="M7 7l3-3 3 3m0 6l-3 3-3-3"
                                  strokeWidth="1.5"
                                  strokeLinecap="round"
                                  strokeLinejoin="round"
                                />
                              </svg>
                            </span>
                          </Listbox.Button>
                          {open && (
                            <Transition
                              show={open}
                              enter="transition ease-out duration-100"
                              enterFrom="transform opacity-0 scale-95"
                              enterTo="transform opacity-100 scale-100"
                              leave="transition ease-in duration-75"
                              leaveFrom="transform opacity-100 scale-100"
                              leaveTo="transform opacity-0 scale-95"
                              className="absolute mt-1 w-full rounded-md bg-white shadow-lg z-10"
                            >
                              <Listbox.Options
                                static
                                className="max-h-60 rounded-md py-1 text-base leading-6 shadow-ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm sm:leading-5"
                              >
                                {appOptions.map(
                                  (app) =>
                                    app.value.id !== selectedApp.value.id && (
                                      <Listbox.Option
                                        key={appOptions.indexOf(app)}
                                        value={app as any}
                                      >
                                        {({ active }) => (
                                          <div
                                            className={cx(
                                              'cursor-default select-none relative py-2 px-4',
                                              {
                                                'bg-gray-200': active,
                                                'bg-white text-black': !active,
                                              }
                                            )}
                                          >
                                            {app.label}
                                          </div>
                                        )}
                                      </Listbox.Option>
                                    )
                                )}
                              </Listbox.Options>
                            </Transition>
                          )}
                        </div>
                      )}
                    </Listbox>

                    {databaseLinkError && (
                      <p className="text-red-500 text-sm font-semibold">
                        {databaseLinkError.graphQLErrors[0].message}
                      </p>
                    )}
                    <Button
                      color="grey"
                      width="large"
                      className="mt-2"
                      isLoading={
                        databaseLinkLoading &&
                        !databaseLinkData &&
                        !databaseLinkError
                      }
                      disabled={!selectedApp.value.id}
                      onClick={() => setIsLinkModalOpen(true)}
                    >
                      Link app
                    </Button>
                    {isLinkModalOpen && (
                      <Modal>
                        <ModalTitle>Link app</ModalTitle>
                        <ModalDescription>
                          {isTerminalVisible ? (
                            <>
                              <p className="mb-2 ">
                                Linking <b>{selectedApp.value.name}</b> with{' '}
                                <b>{database.name}</b>!
                              </p>
                              <p className="text-gray-500 mb-2">
                                Linking process usually takes a couple of
                                minutes. Breathe in, breathe out, logs are about
                                to appear below:
                              </p>
                              <Terminal className={'w-6/6'}>
                                {arrayOfLinkLogs.map((log) => (
                                  <p
                                    key={arrayOfLinkLogs.indexOf(log)}
                                    className="text-s leading-5"
                                  >
                                    {log.message}
                                  </p>
                                ))}
                              </Terminal>
                            </>
                          ) : (
                            <p>
                              Are you sure, you want to link{' '}
                              <b>{selectedApp.value.name}</b> to{' '}
                              <b>{database.name}</b>?
                            </p>
                          )}
                        </ModalDescription>
                        <ModalButton
                          ctaFn={() => {
                            setProcessStatus('running');
                            handleConnect(databaseId, selectedApp.value.id);
                          }}
                          ctaText={'Link'}
                          otherButtonText={'Cancel'}
                          isCtaLoading={isTerminalVisible ? false : linkLoading}
                          isCtaDisabled={isTerminalVisible}
                          isOtherButtonDisabled={processStatus === 'running'}
                          closeModal={() => {
                            setIsLinkModalOpen(false);
                            refetch({ databaseId });
                            setLinkLoading(false);
                            setIsTerminalVisible(false);
                            setProcessStatus('notStarted');
                          }}
                        />
                      </Modal>
                    )}
                  </div>
                ) : (
                  <>
                    <p className="mt-3 mb-3 mr-8 text-cool-gray-400">
                      All your apps are already linked to this database! If you
                      want to create more apps proceed with create app flow.
                    </p>
                    <div className="ml-80">
                      <Link to="/create-app">
                        <Button
                          color={'grey'}
                          variant="outline"
                          className="text-sm mr-3"
                        >
                          Create app
                        </Button>
                      </Link>
                    </div>
                  </>
                )}

                {!loading && database && database.apps && (
                  <>
                    <h2 className="mb-1 mt-3 font-semibold">
                      {database.apps.length > 0 && 'Linked apps'}
                    </h2>
                    {database.apps.map((app) => (
                      <div
                        key={database.apps?.indexOf(app)}
                        className="flex flex-row justify-start"
                      >
                        <Link to={`/app/${app.id}`} className="py-2 block">
                          <div className="w-64 flex items-center py-3 px-2 shadow hover:shadow-md transition-shadow duration-100 ease-in-out rounded bg-white">
                            {app.name}
                          </div>
                        </Link>
                        <Button
                          width="normal"
                          className="mt-4 ml-2 h-10"
                          color="red"
                          onClick={() => {
                            setIsUnlinkModalOpen(true);
                            setAppAboutToUnlink(app.name);
                          }}
                        >
                          Unlink
                        </Button>

                        {isUnlinkModalOpen && (
                          <Modal>
                            <ModalTitle>Unlink app</ModalTitle>
                            <ModalDescription>
                              {isTerminalVisible ? (
                                <>
                                  <p className="mb-2 ">
                                    Unlinking <b>{database.name}</b> from{' '}
                                    <b>{appAboutToUnlink}</b>!
                                  </p>
                                  <p className="text-gray-500 mb-2">
                                    Unlinking process usually takes a couple of
                                    minutes. Breathe in, breathe out, logs are
                                    about to appear below:
                                  </p>
                                  <Terminal className={'w-6/6'}>
                                    {arrayOfUnlinkLogs.map((log) => (
                                      <p
                                        key={arrayOfUnlinkLogs.indexOf(log)}
                                        className="text-s leading-5"
                                      >
                                        {log.message}
                                      </p>
                                    ))}
                                  </Terminal>
                                </>
                              ) : (
                                <p>
                                  Are you sure, you want to unlink{' '}
                                  <b>{database.name}</b> from{' '}
                                  <b>{appAboutToUnlink}</b>?
                                </p>
                              )}
                            </ModalDescription>
                            <ModalButton
                              ctaFn={() => {
                                setProcessStatus('running');
                                handleUnlink(database.id, app.id);
                              }}
                              ctaText={'Unlink'}
                              otherButtonText={'Cancel'}
                              isCtaLoading={
                                isTerminalVisible ? false : unlinkLoading
                              }
                              isOtherButtonDisabled={
                                processStatus === 'running'
                              }
                              isCtaDisabled={isTerminalVisible}
                              closeModal={() => {
                                setIsUnlinkModalOpen(false);
                                refetch({ databaseId });
                                setUnlinkLoading(false);
                                setIsTerminalVisible(false);
                                setAppAboutToUnlink('');
                                setProcessStatus('notStarted');
                              }}
                            />
                          </Modal>
                        )}
                      </div>
                    ))}
                  </>
                )}
              </>
            )}
          </div>
        </div>
      </Container>
    </div>
  );
}
Example #20
Source File: tx-sidebar.tsx    From arkadiko with GNU General Public License v3.0 4 votes vote down vote up
TxSidebar = ({ showSidebar, setShowSidebar }) => {
  const address = useSTXAddress();
  const [isLoading, setIsLoading] = useState(true);
  const [transactions, setTransactions] = useState<JSX.Element[]>();
  const [pendingTransactions, setPendingTransactions] = useState<JSX.Element[]>();

  const [networks, setNetworks] = useState([]);
  const [selectedNetworkKey, setSelectedNetworkKey] = useState(
    JSON.parse(localStorage.getItem('arkadiko-stacks-node') || JSON.stringify(DEFAULT_NETWORKS[0]))
      .key
  );
  const selectedNetwork = networks.find(network => network.key === selectedNetworkKey);

  const [networkName, setNetworkName] = useState('');
  const [networkAddress, setNetworkAddress] = useState('');
  const [networkKey, setNetworkKey] = useState('');

  const onInputChange = (event: { target: { name: any; value: any } }) => {
    const value = event.target.value;
    if (event.target.name === 'networkName') {
      setNetworkName(value);
    } else if (event.target.name === 'networkAddress') {
      setNetworkAddress(value);
    } else if (event.target.name === 'networkKey') {
      setNetworkKey(value);
    }
  };

  const addNewNetwork = () => {
    const networks = JSON.parse(localStorage.getItem('arkadiko-stacks-nodes') || '[]');
    const network = {
      name: networkName,
      url: networkAddress,
      key: networkKey,
    };
    networks.push(network);
    localStorage.setItem('arkadiko-stacks-nodes', JSON.stringify(networks));
    setSelectedNetworkKey(network);
  };

  useEffect(() => {
    const network = JSON.parse(localStorage.getItem('arkadiko-stacks-node')) || networks[0];
    if (showSidebar && selectedNetwork['url'] != network['url']) {
      localStorage.setItem('arkadiko-stacks-node', JSON.stringify(selectedNetwork));
      window.location.reload();
    }
  }, [selectedNetwork]);

  useEffect(() => {
    let mounted = true;

    const fetchTransactions = async () => {
      if (mounted && address) {
        setIsLoading(true);
        const txs = await getAccountTransactions(address || '', CONTRACT_ADDRESS || '');
        let index = 0;
        const txMap = txs.map((tx: ContractCallTransaction) => {
          let status = 'error';
          if (tx.tx_status === 'success') {
            status = 'success';
          } else if (tx.tx_status === 'pending') {
            status = 'pending';
          }
          index += 1;
          return <ContractTransaction key={index} transaction={tx} status={status} />;
        });

        setTransactions(txMap);
        const pending = await getPendingTransactions(address || '', CONTRACT_ADDRESS || '');
        const pendingMap = pending.map((tx: MempoolContractCallTransaction) => {
          index += 1;
          return <ContractTransaction key={index} transaction={tx} status="pending" />;
        });
        setPendingTransactions(pendingMap);
        setIsLoading(false);
      }
    };

    const setAllNetworks = () => {
      const addedNetworks = JSON.parse(localStorage.getItem('arkadiko-stacks-nodes') || '[]');
      setNetworks(DEFAULT_NETWORKS.concat(addedNetworks));
    };

    setAllNetworks();
    if (showSidebar) {
      fetchTransactions();
    }
    return () => {
      mounted = false;
    };
  }, [showSidebar]);

  return (
    <Transition show={showSidebar} as={Fragment}>
      <Dialog
        as="div"
        className="fixed inset-0 z-50 overflow-hidden"
        onClose={() => {
          setShowSidebar(false);
        }}
      >
        <div className="absolute inset-0 overflow-hidden">
          <Transition.Child
            as={Fragment}
            enter="ease-in-out duration-500"
            enterFrom="opacity-0"
            enterTo="opacity-100"
            leave="ease-in-out duration-500"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
          >
            <Dialog.Overlay className="absolute inset-0 transition-opacity bg-gray-700 bg-opacity-50" />
          </Transition.Child>

          <div className="fixed inset-y-0 right-0 flex max-w-full pl-10">
            <Transition.Child
              as={Fragment}
              enter="transform transition ease-in-out duration-500 sm:duration-700"
              enterFrom="translate-x-full"
              enterTo="translate-x-0"
              leave="transform transition ease-in-out duration-500 sm:duration-700"
              leaveFrom="translate-x-0"
              leaveTo="translate-x-full"
            >
              <div className="w-screen max-w-md">
                <div className="flex flex-col h-full overflow-y-scroll bg-white shadow-xl dark:bg-zinc-800">
                  <div className="px-4 py-6 bg-indigo-700 sm:px-6">
                    <div className="flex items-start justify-between">
                      <Dialog.Title className="text-lg text-white font-headings">
                        Network Settings
                      </Dialog.Title>
                      <div className="flex items-center ml-3 h-7">
                        <button
                          type="button"
                          className="text-indigo-200 bg-indigo-700 rounded-md hover:text-white focus:outline-none focus:ring-2 focus:ring-white"
                          onClick={() => {
                            setShowSidebar(false);
                          }}
                        >
                          <span className="sr-only">Close panel</span>
                          <StyledIcon as="XIcon" size={6} solid={false} />
                        </button>
                      </div>
                    </div>
                    <div className="mt-1">
                      <p className="text-sm text-indigo-300">Switch between networks easily</p>
                    </div>
                  </div>
                  <div className="relative px-4 my-6 sm:px-6">
                    <div className="relative w-72">
                      <Listbox value={selectedNetworkKey} onChange={setSelectedNetworkKey}>
                        <Listbox.Button className="relative w-full py-2 pl-3 pr-10 text-left bg-white border border-gray-300 rounded-md shadow-sm cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-zinc-900 dark:border-zinc-800">
                          <span className="block truncate dark:text-zinc-50">
                            {selectedNetwork?.name}
                          </span>
                          <span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
                            <StyledIcon as="SelectorIcon" size={5} className="text-gray-400" />
                          </span>
                        </Listbox.Button>
                        <Transition
                          as={Fragment}
                          leave="transition ease-in duration-100"
                          leaveFrom="opacity-100"
                          leaveTo="opacity-0"
                        >
                          <Listbox.Options className="absolute right-0 w-full py-1 mt-1 overflow-auto text-base bg-white rounded-md shadow-lg dark:text-zinc-50 dark:bg-zinc-900 max-h-56 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
                            {networks.map(network => (
                              <Listbox.Option
                                key={network.key}
                                value={network.key}
                                className={({ active }) =>
                                  `${active ? 'text-white bg-indigo-600' : 'text-gray-900'}
                              cursor-default select-none relative py-2 pl-10 pr-4`
                                }
                              >
                                {({ selected, active }) => (
                                  <div>
                                    <span
                                      className={`${
                                        selected ? 'font-semibold' : 'font-normal'
                                      } block truncate dark:text-zinc-50`}
                                    >
                                      {network.name} ({network.url})
                                    </span>
                                    {selected ? (
                                      <span
                                        className={`${active ? 'text-white' : 'text-indigo-600'}
                                      absolute inset-y-0 left-0 flex items-center pl-3`}
                                      >
                                        <StyledIcon as="CheckIcon" size={5} />
                                      </span>
                                    ) : null}
                                  </div>
                                )}
                              </Listbox.Option>
                            ))}
                          </Listbox.Options>
                        </Transition>
                      </Listbox>
                    </div>
                    <Disclosure>
                      {() => (
                        <>
                          <Disclosure.Button className="flex items-center px-3 py-2 mt-4 text-sm font-medium leading-4 text-white bg-indigo-600 border border-transparent rounded-md shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                            <span>Add a network</span>
                          </Disclosure.Button>
                          <Disclosure.Panel className="p-4 mt-4 text-sm text-gray-700 bg-gray-100 rounded-md dark:bg-zinc-700 dark:text-zinc-100">
                            Use this form to add a new instance of the Stacks Blockchain API. Make
                            sure you review and trust the host before you add it.
                            <form className="mt-4">
                              <div className="flex flex-col">
                                <label
                                  htmlFor="name"
                                  className="block text-sm font-medium text-gray-500 dark:text-gray-300"
                                >
                                  Name
                                </label>
                                <div className="flex mt-1 rounded-md shadow-sm">
                                  <input
                                    value={networkName}
                                    onChange={onInputChange}
                                    type="text"
                                    name="networkName"
                                    id="networkName"
                                    className="flex-1 block w-full min-w-0 text-black border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
                                  />
                                </div>
                              </div>

                              <div className="flex flex-col mt-3">
                                <label
                                  htmlFor="address"
                                  className="block text-sm font-medium text-gray-500 dark:text-gray-300"
                                >
                                  Address (include https://)
                                </label>
                                <div className="flex mt-1 rounded-md shadow-sm">
                                  <input
                                    value={networkAddress}
                                    onChange={onInputChange}
                                    type="text"
                                    name="networkAddress"
                                    id="networkAddress"
                                    className="flex-1 block w-full min-w-0 text-black border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
                                  />
                                </div>
                              </div>

                              <div className="flex flex-col mt-3">
                                <label
                                  htmlFor="key"
                                  className="block text-sm font-medium text-gray-500 dark:text-gray-300"
                                >
                                  Key
                                </label>
                                <div className="flex mt-1 rounded-md shadow-sm">
                                  <input
                                    value={networkKey}
                                    onChange={onInputChange}
                                    type="text"
                                    name="networkKey"
                                    id="networkKey"
                                    className="flex-1 block w-full min-w-0 text-black border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
                                  />
                                </div>
                              </div>

                              <button
                                onClick={() => addNewNetwork()}
                                className="flex items-center px-3 py-2 mt-5 text-sm font-medium leading-4 text-white bg-indigo-600 border border-transparent rounded-md shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
                              >
                                Add network
                              </button>
                            </form>
                          </Disclosure.Panel>
                        </>
                      )}
                    </Disclosure>
                  </div>

                  <div className="px-4 py-6 mt-6 bg-indigo-700 sm:px-6">
                    <div className="flex items-start justify-between">
                      <Dialog.Title className="text-lg text-white font-headings">
                        Transaction History
                      </Dialog.Title>
                    </div>
                    <div className="mt-1">
                      <p className="text-sm text-indigo-300">
                        Your pending and confirmed transactions.
                      </p>
                    </div>
                  </div>
                  {isLoading ? (
                    <div className="relative flex-1 px-4 mt-6 sm:px-6">
                      <ul className="divide-y divide-gray-200 dark:divide-zinc-700">
                        <li className="py-4">
                          <div className="flex flex-col space-y-3">
                            <Placeholder width={Placeholder.width.FULL} />
                            <Placeholder width={Placeholder.width.THIRD} />
                            <Placeholder width={Placeholder.width.HALF} />
                          </div>
                        </li>
                        <li className="py-4">
                          <div className="flex flex-col space-y-3">
                            <Placeholder width={Placeholder.width.FULL} />
                            <Placeholder width={Placeholder.width.THIRD} />
                            <Placeholder width={Placeholder.width.HALF} />
                          </div>
                        </li>
                      </ul>
                    </div>
                  ) : (
                    <div className="relative flex-1 px-4 mt-6 sm:px-6">
                      <ul className="divide-y divide-gray-200 dark:divide-zinc-700">
                        {pendingTransactions}
                        {transactions}
                      </ul>
                    </div>
                  )}
                </div>
              </div>
            </Transition.Child>
          </div>
        </div>
      </Dialog>
    </Transition>
  );
}
Example #21
Source File: token-swap-list.tsx    From arkadiko with GNU General Public License v3.0 4 votes vote down vote up
TokenSwapList: React.FC<Props> = ({ selected, setSelected, disabled }) => {
  return (
    <Listbox value={selected} onChange={setSelected} disabled={disabled}>
      {({ open }) => (
        <>
          <div className="relative flex-1">
            <Listbox.Button
              className={`relative w-full py-2 pl-3 ${
                disabled ? 'pr-3' : 'pr-10'
              } text-left bg-white border border-gray-300 rounded-md shadow-sm cursor-default md:w-36 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm dark:bg-zinc-800 dark:border-zinc-900`}
            >
              <span className="flex items-center">
                <img src={selected.logo} alt="" className="w-6 h-6 rounded-full shrink-0" />
                <span className="block ml-3 truncate dark:text-zinc-50">{selected.name}</span>
              </span>
              {!disabled ? (
                <span className="absolute inset-y-0 right-0 flex items-center pr-2 ml-3 pointer-events-none">
                  <StyledIcon as="SelectorIcon" size={5} className="text-gray-400" />
                </span>
              ) : null}
            </Listbox.Button>

            <Transition
              show={open}
              as={Fragment}
              leave="transition ease-in duration-100"
              leaveFrom="opacity-100"
              leaveTo="opacity-0"
            >
              <Listbox.Options
                static
                className="absolute z-20 w-full py-1 mt-1 overflow-auto text-base bg-white rounded-md shadow-lg dark:text-zinc-50 dark:bg-zinc-800 max-h-56 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
              >
                {tokenList
                  .filter(token => token.listed)
                  .map(token => (
                    <Listbox.Option
                      key={token.id}
                      className={({ active }) =>
                        classNames(
                          active ? 'text-white bg-indigo-600' : 'text-gray-900',
                          'cursor-default select-none relative py-2 pl-3 pr-9'
                        )
                      }
                      value={token}
                    >
                      {({ selected, active }) => (
                        <>
                          <div className="flex items-center">
                            <img
                              src={token.logo}
                              alt=""
                              className="w-6 h-6 rounded-full shrink-0"
                            />
                            <span
                              className={classNames(
                                selected ? 'font-semibold' : 'font-normal',
                                'ml-3 block truncate dark:text-zinc-50'
                              )}
                            >
                              {token.name}
                            </span>
                          </div>

                          {selected ? (
                            <span
                              className={classNames(
                                active ? 'text-white' : 'text-indigo-600',
                                'absolute inset-y-0 right-0 flex items-center pr-4'
                              )}
                            >
                              <StyledIcon as="CheckIcon" size={5} />
                            </span>
                          ) : null}
                        </>
                      )}
                    </Listbox.Option>
                  ))}
              </Listbox.Options>
            </Transition>
          </div>
        </>
      )}
    </Listbox>
  );
}