@chakra-ui/react#GridItem TypeScript Examples

The following examples show how to use @chakra-ui/react#GridItem. 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: InventoryComponent.tsx    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
export default function InventoryComponent(props: InventoryProps) {
  return (
    <ChakraProvider>
      <div style={inventoryBackgroundStyle}>
        <Flex width="100%" height="100vh" gap={15}>
          <Box width="35%" style={gridItemStyle}>
            {
              <Grid>
                {props.quests.map((quest, index) => (
                  <GridItem key={index}>
                    <Box style={questItemStyle}>
                      <Text style={questNameStyle}>{quest.name}</Text>
                    </Box>
                  </GridItem>
                ))}
              </Grid>
            }
          </Box>
          <Box width="65%" style={gridItemStyle}></Box>
        </Flex>
      </div>
    </ChakraProvider>
  );
}
Example #2
Source File: WebhooksTable.tsx    From bluebubbles-server with Apache License 2.0 5 votes vote down vote up
WebhooksTable = ({ webhooks }: { webhooks: Array<WebhookItem> }): JSX.Element => {
    const dispatch = useAppDispatch();
    const dialogRef = useRef(null);
    const [selectedId, setSelectedId] = useState(undefined as number | undefined);
    return (
        <Box>
            <Table variant="striped" colorScheme="blue">
                <TableCaption>These are callbacks to receive events from the BlueBubbles Server</TableCaption>
                <Thead>
                    <Tr>
                        <Th>URL</Th>
                        <Th>Event Subscriptions</Th>
                        <Th isNumeric>Actions</Th>
                    </Tr>
                </Thead>
                <Tbody>
                    {webhooks.map(item => (
                        <Tr key={item.id}>
                            <Td>{item.url}</Td>
                            <Td>{JSON.parse(item.events).map((e: string) => webhookEventValueToLabel(e)).join(', ')}</Td>
                            <Td isNumeric>
                                <Grid templateColumns="repeat(2, 1fr)">
                                    <Tooltip label='Edit' placement='bottom'>
                                        <GridItem _hover={{ cursor: 'pointer' }} onClick={() => setSelectedId(item.id)}>
                                            <Icon as={AiOutlineEdit} />
                                        </GridItem>
                                    </Tooltip>
                                    
                                    <Tooltip label='Delete' placement='bottom'>
                                        <GridItem _hover={{ cursor: 'pointer' }} onClick={() => dispatch(remove(item.id))}>
                                            <Icon as={FiTrash} />
                                        </GridItem>
                                    </Tooltip>
                                </Grid>
                            </Td>
                        </Tr>
                    ))}
                </Tbody>
            </Table>

            <AddWebhookDialog
                existingId={selectedId}
                modalRef={dialogRef}
                isOpen={!!selectedId}
                onClose={() => {
                    setSelectedId(undefined);
                }}
            />
        </Box>
    );
}
Example #3
Source File: Greeter.tsx    From eth-dapps-nextjs-boiletplate with MIT License 5 votes vote down vote up
// REF: https://dev.to/jacobedawson/send-react-web3-dapp-transactions-via-metamask-2b8n
export default function Greeter() {
  const { globalState, dispatch } = useContext(globalContext)
  const { account, web3 } = globalState
  const [greetingText, setGreetingText] = useState("")
  const [greetingOutput, setGreetingOutput] = useState("")
  const [greetingButtonLoading, greetingButton] = useButton(setGreeting, 'Set Greeting')
  const [greeting, greetingInput] = useInput(greetingButtonLoading as boolean)
  const [greetButtonLoading, greetButton] = useButton(handleGreet, 'Greet')
  const [greet, greetInput] = useInput(greetButtonLoading as boolean)
  const contractAddress = process.env.NEXT_PUBLIC_GREETER_CONTRACT_ADDRESS
  const abiItems: AbiItem[] = web3 && GreeterContract.abi as AbiItem[]
  const contract = web3 && contractAddress && new web3.eth.Contract(abiItems, contractAddress)
    
  function getGreeting() {
    console.log('getGreeting')
    contract.methods.greeting().call().then((result: any) => {
      setGreetingText(result)
    });
  }

  async function handleGreet() {
    console.log('handleGreet', greet)
    try {
      const result = await contract.methods.greet(greet).call()
      setGreetingOutput(result)
    } catch (error) {
      console.error(error)
    } 
  }

  async function setGreeting() {
    console.log('setGreeting')
    try {
      const result = await contract.methods.setGreeting(greeting).send({ from: account })
      console.log('result', result)
      getGreeting()
    } catch (error) {
      console.error('error in try...catch', error)
    } 
  }

  useEffect(() => {
    if (contract) {
      getGreeting()
    }
  })

  return (
    <div>
      { 
        account && (
        <Grid mt="5" templateColumns="repeat(2, 1fr)" templateRows="repeat(4, 1fr)" gap={3}>
          <GridItem><Text textAlign="right" fontWeight="bold">Greeting</Text></GridItem>
          <GridItem><Text>{greetingText}</Text></GridItem>
          <GridItem alignItems="end">{greetingButton}</GridItem>
          <GridItem>{greetingInput}</GridItem>
          <GridItem alignItems="end">{greetButton}</GridItem>
          <GridItem>{greetInput}</GridItem>
          <GridItem colSpan={2}>
            <Text fontWeight="bold" textAlign="center">{greetingOutput}</Text>
          </GridItem>
        </Grid>
        ) 
      }
    </div>
  )
}
Example #4
Source File: StatsLayout.tsx    From calories-in with MIT License 5 votes vote down vote up
function StatsLayout({
  nameElement,
  amountElement,
  energyElement,
  proteinElement,
  carbsElement,
  fatElement,
  menuElement,
  forwardedRef,
  prefersAmount = false,
  ...rest
}: Props) {
  const screenSize = useScreenSize()

  if (screenSize >= ScreenSize.Medium) {
    return (
      <Grid
        ref={forwardedRef}
        width="100%"
        gap={0}
        templateColumns="repeat(11, 1fr)"
        {...rest}
      >
        {amountElement && <GridItem colSpan={2}>{amountElement}</GridItem>}

        <GridItem colSpan={amountElement ? 4 : 6}>{nameElement}</GridItem>
        <GridItem colSpan={1}>{energyElement}</GridItem>
        <GridItem colSpan={1}>{proteinElement}</GridItem>
        <GridItem colSpan={1}>{carbsElement}</GridItem>
        <GridItem colSpan={1}>{fatElement}</GridItem>
        <GridItem colSpan={1}>{menuElement}</GridItem>
      </Grid>
    )
  }

  if (screenSize === 1) {
    return (
      <Grid
        ref={forwardedRef}
        width="100%"
        templateColumns="repeat(10, 1fr)"
        gap={0}
        {...rest}
      >
        {amountElement && <GridItem colSpan={2}>{amountElement}</GridItem>}

        <GridItem colSpan={amountElement ? 4 : 6}>{nameElement}</GridItem>
        <GridItem colSpan={2}>{energyElement}</GridItem>
        <GridItem colSpan={2}>{menuElement}</GridItem>
      </Grid>
    )
  }

  return (
    <Grid
      ref={forwardedRef}
      width="100%"
      templateColumns="repeat(10, 1fr)"
      gap={0}
      {...rest}
    >
      {prefersAmount && <GridItem colSpan={3}>{amountElement}</GridItem>}
      <GridItem colSpan={5}>{nameElement}</GridItem>
      {!prefersAmount && <GridItem colSpan={3}>{energyElement}</GridItem>}
      <GridItem colSpan={2}>{menuElement}</GridItem>
    </Grid>
  )
}
Example #5
Source File: index.tsx    From dope-monorepo with GNU General Public License v3.0 4 votes vote down vote up
Flex = () => {
  const hustler = useHustler();
  const router = useRouter();
  const { account } = useWeb3React();

  const [hustlerConfig, setHustlerConfig] = useState({} as Partial<HustlerCustomization>);
  const [onChainImage, setOnChainImage] = useState('');
  const { id: hustlerId } = router.query;

  const [isOwnedByConnectedAccount, setIsOwnedByConnectedAccount] = useState(false);

  // Check Contract see if this Hustler is owned by connected Account
  useEffect(() => {
    let isMounted = true;
    if (hustler && account && hustlerId && isMounted) {
      hustler.balanceOf(account, hustlerId.toString()).then(value => {
        setIsOwnedByConnectedAccount(value.eq(1));
      });
    }
    return () => {
      isMounted = false;
    };
  }, [hustler, account, hustlerId]);

  // Grab Hustler info from API
  const { data, isFetching: isLoading } = useHustlerQuery(
    {
      where: {
        id: String(hustlerId),
      },
    },
    {
      enabled: router.isReady && !!String(router.query.id),
    },
  );

  // Set Hustler Config and SVG Image after data returns
  useEffect(() => {
    if (data?.hustlers?.edges?.[0]?.node) {
      const h = data?.hustlers?.edges?.[0].node;

      if (h?.svg) setOnChainImage(h?.svg);
      setHustlerConfig({
        ...hustlerConfig,
        name: h?.name || '',
        title: h?.title || '',
        sex: (h?.sex.toLowerCase() || 'male') as HustlerSex,
        body: h.body?.id ? parseInt(h.body.id.split('-')[2]) : 0,
        hair: h.hair?.id ? parseInt(h.hair.id.split('-')[2]) : 0,
        facialHair: h.beard?.id ? parseInt(h.beard.id.split('-')[2]) : 0,
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [data, hustlerId]);

  const itemRles = useHustlerRles(data?.hustlers?.edges?.[0]?.node);

  const items = useMemo<Item[]>(() => {
    const hustler = data?.hustlers?.edges?.[0]?.node;
    if (hustler) {
      // Order matters
      return [
        hustler.weapon,
        hustler.vehicle,
        hustler.drug,
        hustler.clothes,
        hustler.hand,
        hustler.waist,
        hustler.foot,
        hustler.neck,
        hustler.ring,
        hustler.accessory,
      ].filter(i => !!i) as Item[];
    }
    return [];
  }, [data]);

  useEffect(() => {
    if (data?.hustlers?.edges?.[0]?.node) {
      const h = data?.hustlers?.edges?.[0].node;

      if (h?.svg) setOnChainImage(h?.svg);
      setHustlerConfig({
        ...hustlerConfig,
        name: h?.name || '',
        title: h?.title || '',
        bgColor: `#${h?.background}`,
        sex: (h?.sex.toLowerCase() || 'male') as HustlerSex,
        body: h.body?.id ? parseInt(h.body.id.split('-')[2]) : 0,
        hair: h.hair?.id ? parseInt(h.hair.id.split('-')[2]) : 0,
        facialHair: h.beard?.id ? parseInt(h.beard.id.split('-')[2]) : 0,
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [data, hustlerId]);

  return (
    <AppWindow padBody={true} navbar={<HustlerFlexNavBar />} scrollable>
      <Head
        title={`Dope Wars Hustler Flex`}
        ogImage={`https://api.dopewars.gg/hustlers/${hustlerId}/sprites/composite.png`}
      />
      {isLoading && <LoadingBlock />}
      {!isLoading && itemRles && (
        <Grid
          templateColumns="repeat(auto-fit, minmax(240px, 1fr))"
          gap="16px"
          justifyContent="center"
          alignItems="stretch"
          width="100%"
          padding="32px"
          paddingTop="8px"
        >
          <PanelContainer
            css={css`
              grid-column: unset;
              background-color: ${hustlerConfig.bgColor};
              padding: 16px;
              display: flex;
              align-items: center;
              ${media.tablet`
                grid-column: 1 / 3;
              `}
            `}
          >
            <Image src={onChainImage} alt={hustlerConfig.name} flex="1" />
          </PanelContainer>
          <PanelContainer>
            <PanelBody>
              <Grid
                templateRows="2fr 1fr"
                gap="8"
                justifyContent="center"
                alignItems="stretch"
                width="100%"
              >
                <GridItem
                  display="flex"
                  justifyContent="center"
                  alignItems="flex-end"
                  paddingBottom="30px"
                  background="#000 url(/images/lunar_new_year_2022/explosion_city-bg.png) center / contain repeat-x"
                  overflow="hidden"
                >
                  <HustlerSpriteSheetWalk id={hustlerId?.toString()} />
                </GridItem>
                <GridItem minWidth="256px">
                  <HustlerMugShot hustlerConfig={hustlerConfig} itemRles={itemRles} />
                </GridItem>
              </Grid>
            </PanelBody>
          </PanelContainer>
          <PanelContainer>
            <ProfileCardHeader>Equipped Gear</ProfileCardHeader>
            <PanelBody
              css={css`
                background-color: var(--gray-800);
                flex: 2;
              `}
            >
              {items?.map(({ id, name, namePrefix, nameSuffix, suffix, augmented, type, tier }) => {
                return (
                  <DopeItem
                    key={id}
                    name={name}
                    namePrefix={namePrefix}
                    nameSuffix={nameSuffix}
                    suffix={suffix}
                    augmented={augmented}
                    type={type}
                    color={DopeLegendColors[tier]}
                    isExpanded={true}
                    tier={tier}
                    showRarity={true}
                  />
                );
              })}
            </PanelBody>
          </PanelContainer>
          {items?.map(item => {
            return (
              <GearCard
                item={item}
                key={item.id}
                showUnEquipFooter={isOwnedByConnectedAccount}
                hustlerId={BigNumber.from(hustlerId)}
              />
            );
          })}
        </Grid>
      )}
    </AppWindow>
  );
}
Example #6
Source File: AppDomains.tsx    From ledokku with MIT License 4 votes vote down vote up
AppDomains = ({ appId }: AppDomainProps) => {
  const toast = useToast();
  const { data, loading /* error */ } = useAppByIdQuery({
    variables: {
      appId,
    },
    ssr: false,
    skip: !appId,
  });

  const {
    data: domainsData,
    loading: domainsDataLoading,
    refetch: appDomainsRefetch,
  } = useDomainsQuery({
    variables: {
      appId,
    },
  });

  const [
    removeDomainMutation,
    { loading: removeDomainMutationLoading },
  ] = useRemoveDomainMutation();

  const handleRemoveDomain = async (domain: string) => {
    try {
      await removeDomainMutation({
        variables: {
          input: {
            appId,
            domainName: domain,
          },
        },
        refetchQueries: [{ query: DomainsDocument, variables: { appId } }],
      });
      toast.success('Domain removed successfully');
    } catch (error) {
      toast.error(error.message);
    }
  };

  if (!data) {
    return null;
  }

  // // TODO display error

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

  const { app } = data;

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

  return (
    <>
      <Box py="5">
        <Heading as="h2" size="md">
          Domain management
        </Heading>
        <Text fontSize="sm" color="gray.400">
          List of domains you have added to {app.name} app
        </Text>
      </Box>

      <Grid templateColumns={{ sm: 'repeat(1, 1fr)', md: 'repeat(3, 1fr)' }}>
        <GridItem colSpan={2}>
          <Box mb="8">
            {domainsDataLoading ? <Spinner /> : null}
            {domainsData?.domains.domains.length === 0 ? (
              <Text fontSize="sm" color="gray.400">
                Currently you haven't added any custom domains to your app
              </Text>
            ) : null}
            {domainsData?.domains.domains.map((domain: any) => (
              <Flex
                key={domain}
                justifyContent="space-between"
                alignItems="center"
              >
                <Link
                  href={`http://${domain}`}
                  isExternal
                  display="flex"
                  alignItems="center"
                >
                  {domain} <Icon as={FiExternalLink} mx="2" />
                </Link>

                <IconButton
                  aria-label="Delete"
                  variant="ghost"
                  colorScheme="red"
                  icon={<FiTrash2 />}
                  disabled={removeDomainMutationLoading}
                  onClick={() => handleRemoveDomain(domain)}
                />
              </Flex>
            ))}
          </Box>

          <AddAppDomain appId={appId} appDomainsRefetch={appDomainsRefetch} />
        </GridItem>
      </Grid>
    </>
  );
}
Example #7
Source File: env.tsx    From ledokku with MIT License 4 votes vote down vote up
EnvForm = ({ name, value, appId, isNewVar }: EnvFormProps) => {
  const [inputType, setInputType] = useState('password');
  const toast = useToast();
  const [
    setEnvVarMutation,
    { loading: setEnvVarLoading },
  ] = useSetEnvVarMutation();
  const [
    unsetEnvVarMutation,
    { loading: unsetEnvVarLoading },
  ] = useUnsetEnvVarMutation();

  const handleDeleteEnvVar = async (event: any) => {
    event.preventDefault();
    try {
      await unsetEnvVarMutation({
        variables: { key: name, appId },
        refetchQueries: [{ query: EnvVarsDocument, variables: { appId } }],
      });
    } catch (error) {
      toast.error(error.message);
    }
  };

  const formik = useFormik<{ name: string; value: string }>({
    initialValues: {
      name,
      value,
    },
    onSubmit: async (values) => {
      // TODO validate values
      try {
        await setEnvVarMutation({
          variables: { key: values.name, value: values.value, appId },
          refetchQueries: [{ query: EnvVarsDocument, variables: { appId } }],
        });

        if (isNewVar) {
          formik.resetForm();
        }
        toast.success('Environment variable set successfully');
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  return (
    //TODO Handle visual feedback on changing env
    //TODO Provide infos about env vars
    <form onSubmit={formik.handleSubmit} autoComplete="off">
      <Grid
        templateColumns={{ sm: 'repeat(1, 1fr)', md: 'repeat(3, 1fr)' }}
        gap="3"
        mt="3"
      >
        <GridItem>
          <Input
            autoComplete="off"
            id={isNewVar ? 'newVarName' : name}
            name="name"
            placeholder="Name"
            key={name}
            value={formik.values.name}
            onChange={formik.handleChange}
          />
        </GridItem>
        <GridItem>
          <Input
            autoComplete="off"
            onMouseEnter={() => setInputType('text')}
            onMouseLeave={() => setInputType('password')}
            onFocus={() => setInputType('text')}
            onBlur={() => setInputType('password')}
            id={isNewVar ? 'newVarValue' : value}
            name="value"
            placeholder="Value"
            key={value}
            value={formik.values.value}
            onChange={formik.handleChange}
            type={inputType}
          />
        </GridItem>
        <GridItem display="flex">
          <Button isLoading={setEnvVarLoading} type="submit">
            {isNewVar ? 'Add' : 'Save'}
          </Button>
          {!isNewVar && (
            <IconButton
              aria-label="Delete"
              variant="outline"
              ml="3"
              icon={<FiTrash2 />}
              isLoading={unsetEnvVarLoading}
              onClick={handleDeleteEnvVar}
            />
          )}
        </GridItem>
      </Grid>
    </form>
  );
}
Example #8
Source File: advanced.tsx    From ledokku with MIT License 4 votes vote down vote up
AppSettingsAdvanced = () => {
  const { id: appId } = useParams<{ id: string }>();
  const toast = useToast();
  const history = useHistory();

  const { data, loading } = useAppByIdQuery({
    variables: {
      appId,
    },
  });

  const [
    destroyAppMutation,
    { loading: destroyAppMutationLoading },
  ] = useDestroyAppMutation();

  const DeleteAppNameSchema = yup.object().shape({
    appName: yup
      .string()
      .required('Required')
      .test(
        'Equals app name',
        'Must match app name',
        (val) => val === data?.app?.name
      ),
  });

  const formik = useFormik<{ appName: string }>({
    initialValues: {
      appName: '',
    },

    validateOnChange: true,
    validationSchema: DeleteAppNameSchema,

    onSubmit: async (values) => {
      try {
        await destroyAppMutation({
          variables: {
            input: { appId },
          },
          refetchQueries: [
            {
              query: DashboardDocument,
            },
          ],
        });
        toast.success('App deleted successfully');

        history.push('/dashboard');
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  // TODO display error

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

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

  const { app } = data;

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

      <Container maxW="5xl" mt={10}>
        <Grid
          templateColumns={{ sm: 'repeat(1, 1fr)', md: 'repeat(6, 1fr)' }}
          gap={{ sm: 0, md: 16 }}
        >
          <GridItem colSpan={2} py={5}>
            <AppSettingsMenu app={app} />
          </GridItem>
          <GridItem colSpan={4}>
            <AppRestart appId={app.id} />
            <AppRebuild appId={app.id} />

            <Box py="5">
              <Heading as="h2" size="md">
                Delete app
              </Heading>
              <Text fontSize="sm" color="gray.400">
                This action cannot be undone. This will permanently delete{' '}
                {app.name} app and everything related to it. Please type{' '}
                <b>{app.name}</b> to confirm deletion.
              </Text>
            </Box>

            <form onSubmit={formik.handleSubmit}>
              <FormControl
                id="appName"
                isInvalid={Boolean(
                  formik.errors.appName && formik.touched.appName
                )}
              >
                <Input
                  autoComplete="off"
                  id="appNme"
                  name="appName"
                  placeholder="App name"
                  value={formik.values.appName}
                  onChange={formik.handleChange}
                  onBlur={formik.handleBlur}
                />
                <FormErrorMessage>{formik.errors.appName}</FormErrorMessage>
              </FormControl>

              <Button
                my={4}
                type="submit"
                colorScheme="red"
                isLoading={destroyAppMutationLoading}
              >
                Delete
              </Button>
            </form>
          </GridItem>
        </Grid>
      </Container>
    </>
  );
}
Example #9
Source File: create-app-dokku.tsx    From ledokku with MIT License 4 votes vote down vote up
CreateAppDokku = () => {
  const history = useHistory();
  const toast = useToast();
  const { data: dataApps } = useAppsQuery();
  const [createAppDokkuMutation, { loading }] = useCreateAppDokkuMutation();

  const createAppSchema = yup.object().shape({
    name: yup
      .string()
      .required('App name is required')
      .matches(/^[a-z0-9-]+$/)
      .test(
        'Name exists',
        'App with this name already exists',
        (val) => !dataApps?.apps.find((app) => app.name === val)
      ),
  });

  const formik = useFormik<{
    name: string;
  }>({
    initialValues: {
      name: '',
    },

    validateOnChange: true,
    validationSchema: createAppSchema,
    onSubmit: async (values) => {
      try {
        const res = await createAppDokkuMutation({
          variables: {
            input: {
              name: values.name,
            },
          },
        });

        trackGoal(trackingGoals.createAppDokku, 0);

        if (res.data) {
          history.push(`app/${res.data?.createAppDokku.appId}`);
          toast.success('App created successfully');
        }
      } catch (error) {
        toast.error(error);
      }
    },
  });

  return (
    <>
      <HeaderContainer>
        <Header />
      </HeaderContainer>

      <Container maxW="5xl" mt={10}>
        <Heading as="h2" size="md">
          Create a new app
        </Heading>

        <Text mt="12" mb="4" color="gray.400">
          Enter app name, click create and voila!
        </Text>

        <Grid templateColumns={{ sm: 'repeat(1, 1fr)', md: 'repeat(3, 1fr)' }}>
          <GridItem colSpan={2}>
            <form onSubmit={formik.handleSubmit}>
              <FormControl
                id="v"
                isInvalid={Boolean(formik.errors.name && formik.touched.name)}
              >
                <FormLabel>App name:</FormLabel>
                <Input
                  autoComplete="off"
                  id="name"
                  name="name"
                  placeholder="Name"
                  value={formik.values.name}
                  onChange={formik.handleChange}
                  onBlur={formik.handleBlur}
                />
                <FormErrorMessage>{formik.errors.name}</FormErrorMessage>
              </FormControl>

              <Box mt="4" display="flex" justifyContent="flex-end">
                <Button
                  type="submit"
                  color="grey"
                  disabled={
                    loading || !formik.values.name || !!formik.errors.name
                  }
                  isLoading={loading}
                >
                  Create
                </Button>
              </Box>
            </form>
          </GridItem>
        </Grid>
      </Container>
    </>
  );
}
Example #10
Source File: create-app-github.tsx    From ledokku with MIT License 4 votes vote down vote up
CreateAppGithub = () => {
  const history = useHistory();
  const toast = useToast();
  const { user } = useAuth();

  const { data: dataApps } = useAppsQuery();
  const [isNewWindowClosed, setIsNewWindowClosed] = useState(false);
  const [selectedRepo, setSelectedRepo] = useState<Repository>();
  const [selectedBranch, setSelectedBranch] = useState('');
  const [isProceedModalOpen, setIsProceedModalOpen] = useState(false);
  const {
    data: installationData,
    loading: installationLoading,
  } = useGithubInstallationIdQuery({ fetchPolicy: 'network-only' });
  const [
    getRepos,
    { data: reposData, loading: reposLoading },
  ] = useRepositoriesLazyQuery({ fetchPolicy: 'network-only' });

  const [
    getBranches,
    { data: branchesData, loading: branchesLoading },
  ] = useBranchesLazyQuery({ fetchPolicy: 'network-only' });

  const [arrayOfCreateAppLogs, setArrayOfCreateAppLogs] = useState<
    RealTimeLog[]
  >([]);
  const [isTerminalVisible, setIsTerminalVisible] = useState(false);
  const [isToastShown, setIsToastShown] = useState(false);
  const [createAppGithubMutation, { loading }] = useCreateAppGithubMutation();
  const [
    isAppCreationSuccess,
    setIsAppCreationSuccess,
  ] = useState<AppCreationStatus>();

  useAppCreateLogsSubscription({
    onSubscriptionData: (data) => {
      const logsExist = data.subscriptionData.data?.appCreateLogs;

      if (logsExist) {
        setArrayOfCreateAppLogs((currentLogs) => {
          return [...currentLogs, logsExist];
        });
        if (logsExist.type === 'end:success') {
          setIsAppCreationSuccess(AppCreationStatus.SUCCESS);
        } else if (logsExist.type === 'end:failure') {
          setIsAppCreationSuccess(AppCreationStatus.FAILURE);
        }
      }
    },
  });

  const createAppGithubSchema = yup.object().shape({
    name: yup
      .string()
      .required('App name is required')
      .matches(/^[a-z0-9-]+$/)
      .test(
        'Name exists',
        'App with this name already exists',
        (val) => !dataApps?.apps.find((app) => app.name === val)
      ),
    repo: yup.object({
      fullName: yup.string().required(),
      id: yup.string().required(),
      name: yup.string().required(),
    }),
    installationId: yup.string().required(),
    gitBranch: yup.string().optional(),
  });

  const formik = useFormik<{
    name: string;
    repo: {
      fullName: string;
      id: string;
      name: string;
    };
    installationId: string;
    gitBranch: string;
  }>({
    initialValues: {
      name: '',
      repo: {
        fullName: '',
        id: '',
        name: '',
      },
      installationId: '',
      gitBranch: '',
    },

    validateOnChange: true,
    validationSchema: createAppGithubSchema,
    onSubmit: async (values) => {
      if (installationData) {
        try {
          await createAppGithubMutation({
            variables: {
              input: {
                name: values.name,
                gitRepoFullName: values.repo.fullName,
                branchName: values.gitBranch,
                gitRepoId: values.repo.id,
                githubInstallationId: values.installationId,
              },
            },
          });
          setIsTerminalVisible(true);
        } catch (error) {
          error.message === 'Not Found'
            ? toast.error(`Repository : ${values.repo.fullName} not found`)
            : toast.error(error.message);
        }
      }
    },
  });

  const handleNext = () => {
    setIsTerminalVisible(false);
    const appId = arrayOfCreateAppLogs[arrayOfCreateAppLogs.length - 1].message;
    history.push(`app/${appId}`, 'new');
    trackGoal(trackingGoals.createAppGithub, 0);
  };

  const handleOpen = () => {
    const newWindow = window.open(
      `https://github.com/apps/${config.githubAppName}/installations/new`,
      'Install App',
      'resizable=1, scrollbars=1, fullscreen=0, height=1000, width=1020,top=' +
        window.screen.width +
        ', left=' +
        window.screen.width +
        ', toolbar=0, menubar=0, status=0'
    );
    const timer = setInterval(async () => {
      if (newWindow && newWindow.closed) {
        setIsNewWindowClosed(true);
        clearInterval(timer);
      }
    }, 100);
  };

  useEffect(() => {
    if (!installationLoading && installationData && isNewWindowClosed) {
      getRepos({
        variables: {
          installationId: installationData.githubInstallationId.id,
        },
      });

      setIsNewWindowClosed(false);
    }
  }, [
    installationData,
    installationLoading,
    isNewWindowClosed,
    setIsNewWindowClosed,
    getRepos,
  ]);

  useEffect(() => {
    if (
      !installationLoading &&
      installationData &&
      !reposLoading &&
      reposData &&
      selectedRepo
    ) {
      getBranches({
        variables: {
          installationId: installationData.githubInstallationId.id,
          repositoryName: selectedRepo.name,
        },
      });
    }
  }, [
    installationData,
    installationLoading,
    reposData,
    reposLoading,
    getBranches,
    selectedRepo?.name,
    selectedRepo,
  ]);

  const handleChangeRepo = (active: RepoOption) => {
    setSelectedRepo(active.value);
    setSelectedBranch('');
    if (installationData) {
      formik.setValues({
        name: active.value.name,
        installationId: installationData?.githubInstallationId.id,
        repo: {
          fullName: active.value.fullName,
          name: active.value.name,
          id: active.value.id,
        },
        gitBranch: '',
      });
    }
  };

  const handleChangeBranch = (active: BranchOption) => {
    setSelectedBranch(active.value.name);
    formik.setFieldValue('gitBranch', active.value.name);
  };

  const repoOptions: RepoOption[] = [];

  if (reposData && !reposLoading) {
    reposData?.repositories.map((r) =>
      repoOptions.push({ value: r, label: r.fullName })
    );
  }

  let branchOptions: BranchOption[] = [];

  if (branchesData && !branchesLoading) {
    branchesData.branches.map((b) =>
      branchOptions.push({ value: b, label: b.name })
    );
  }

  useEffect(() => {
    if (installationData && !installationLoading) {
      getRepos({
        variables: {
          installationId: installationData?.githubInstallationId.id,
        },
      });
    }
  }, [installationLoading, getRepos, installationData]);

  useEffect(() => {
    if (selectedRepo && installationData) {
      getBranches({
        variables: {
          installationId: installationData?.githubInstallationId.id,
          repositoryName: selectedRepo.name,
        },
      });
    }
  }, [selectedRepo, getBranches, installationData]);

  // Effect for app creation
  useEffect(() => {
    isAppCreationSuccess === AppCreationStatus.FAILURE && !isToastShown
      ? toast.error('Failed to create an app') && setIsToastShown(true)
      : isAppCreationSuccess === AppCreationStatus.SUCCESS &&
        !isToastShown &&
        toast.success('App created successfully') &&
        setIsToastShown(true);
  }, [isToastShown, isAppCreationSuccess, toast]);

  return (
    <>
      <HeaderContainer>
        <Header />
      </HeaderContainer>

      <Container maxW="5xl" mt={10}>
        {isTerminalVisible ? (
          <>
            <p className="mb-2 ">
              Creating <b>{formik.values.name}</b> app from{' '}
              <b>{formik.values.repo.name}</b>
            </p>
            <p className="text-gray-500 mb-2">
              Creating app usually takes a couple of minutes. Breathe in,
              breathe out, logs are about to appear below:
            </p>
            <Terminal className={'w-6/6'}>
              {arrayOfCreateAppLogs.map((log) => (
                <p
                  key={arrayOfCreateAppLogs.indexOf(log)}
                  className={'text-s leading-5'}
                >
                  {log.message?.replaceAll('[1G', '')}
                </p>
              ))}
            </Terminal>

            {!!isAppCreationSuccess &&
            isAppCreationSuccess === AppCreationStatus.SUCCESS ? (
              <div className="mt-12 flex justify-end">
                <Button
                  onClick={() => handleNext()}
                  color="grey"
                  iconEnd={<FiArrowRight size={20} />}
                >
                  Next
                </Button>
              </div>
            ) : !!isAppCreationSuccess &&
              isAppCreationSuccess === AppCreationStatus.FAILURE ? (
              <div className="mt-12 flex justify-start">
                <Button
                  onClick={() => {
                    setIsTerminalVisible(false);
                    formik.resetForm();
                  }}
                  color="grey"
                  iconEnd={<FiArrowLeft size={20} />}
                >
                  Back
                </Button>
              </div>
            ) : null}
          </>
        ) : (
          <>
            <Heading as="h2" size="md">
              Create a new GitHub application
            </Heading>
            {installationData &&
            !installationLoading &&
            reposData &&
            !reposLoading ? (
              <>
                <Text color="gray.400">
                  When you push to Git, your application will be redeployed
                  automatically.
                </Text>

                <Grid
                  templateColumns={{
                    sm: 'repeat(1, 1fr)',
                    md: 'repeat(3, 1fr)',
                  }}
                >
                  <GridItem colSpan={2}>
                    <Flex alignItems="center" mt="12">
                      <Avatar
                        size="sm"
                        name={user?.userName}
                        src={user?.avatarUrl}
                      />
                      <Text ml="2" fontWeight="bold">
                        {user?.userName}
                      </Text>
                    </Flex>
                    <form onSubmit={formik.handleSubmit}>
                      <Box mt="8">
                        <FormLabel>Repository</FormLabel>
                        <Select
                          placeholder="Select repository"
                          isSearchable={false}
                          onChange={handleChangeRepo}
                          options={repoOptions}
                        />
                      </Box>

                      <Text mt="1" color="gray.400" fontSize="sm">
                        Can't see your repo in the list?{' '}
                        <Link
                          onClick={() => setIsProceedModalOpen(true)}
                          textDecoration="underline"
                        >
                          Configure the GitHub app.
                        </Link>
                      </Text>

                      <Box mt="8">
                        <FormLabel>Branch to deploy</FormLabel>
                        <Select
                          placeholder="Select branch"
                          isSearchable={false}
                          disabled={
                            !branchesData ||
                            branchesLoading ||
                            reposLoading ||
                            !reposData
                          }
                          onChange={handleChangeBranch}
                          options={branchOptions}
                        />
                      </Box>

                      <Box mt="8" display="flex" justifyContent="flex-end">
                        <Button
                          type="submit"
                          color="grey"
                          disabled={!selectedBranch || !selectedRepo}
                          isLoading={loading}
                        >
                          Create
                        </Button>
                      </Box>
                    </form>
                  </GridItem>
                </Grid>
              </>
            ) : !reposLoading && !installationLoading && !reposData ? (
              <>
                <Alert mb="4" mt="4" w="65%" status="info">
                  <AlertIcon />
                  <Box flex="1">
                    <AlertTitle>Set up repository permissions</AlertTitle>
                    <AlertDescription display="block">
                      First you will need to set up permissions for repositories
                      that you would like to use with Ledokku. Once that's done,
                      it's time to choose repo and branch that you would like to
                      create app from and off we go.
                    </AlertDescription>
                  </Box>
                </Alert>

                <Button
                  color="grey"
                  onClick={() => setIsProceedModalOpen(true)}
                >
                  Set up permissions
                </Button>
              </>
            ) : (
              <Spinner />
            )}
          </>
        )}
        <Modal
          isOpen={isProceedModalOpen}
          onClose={() => setIsProceedModalOpen(false)}
          isCentered
        >
          <ModalOverlay />
          <ModalContent>
            <ModalHeader>Github setup info</ModalHeader>
            <ModalCloseButton />
            <ModalBody>
              New window is about to open. After you are done selecting github
              repos, close the window and refresh page.
            </ModalBody>

            <ModalFooter>
              <Button
                color="grey"
                variant="outline"
                className="mr-3"
                onClick={() => setIsProceedModalOpen(false)}
              >
                Cancel
              </Button>
              <Button
                color="grey"
                onClick={() => {
                  handleOpen();
                  setIsProceedModalOpen(false);
                }}
              >
                Proceed
              </Button>
            </ModalFooter>
          </ModalContent>
        </Modal>
      </Container>
    </>
  );
}
Example #11
Source File: dashboard.tsx    From ledokku with MIT License 4 votes vote down vote up
Dashboard = () => {
  // const history = useHistory();
  const { data /* loading, error */ } = useDashboardQuery({
    fetchPolicy: 'cache-and-network',
  });

  // TODO show loading
  // TODO handle error

  // TODO if no apps or dbs show onboarding screen

  return (
    <div>
      <HeaderContainer>
        <Header />
        <HomeHeaderTabNav />
      </HeaderContainer>

      <Container maxW="5xl" py={6}>
        <Box display="flex" justifyContent="flex-end" pb={6}>
          <Link to="/create-database">
            <Button colorScheme="gray" variant="outline" fontSize="sm" mr={3}>
              Create database
            </Button>
          </Link>
          <Link to="/create-app">
            <Button colorScheme="gray" fontSize="sm">
              Create app
            </Button>
          </Link>
        </Box>

        <Grid
          as="main"
          templateColumns="repeat(12, 1fr)"
          gap={{ base: 6, md: 20 }}
          pt={4}
        >
          <GridItem colSpan={{ base: 12, md: 7 }}>
            <Heading as="h2" size="md" py={5}>
              Apps
            </Heading>
            {data?.apps.length === 0 ? (
              <Text fontSize="sm" color="gray.400">
                No apps deployed.
              </Text>
            ) : null}
            {data?.apps.map((app) => (
              <Box
                key={app.id}
                py={3}
                borderBottom={'1px'}
                borderColor="gray.200"
              >
                <Box mb={1} color="gray.900" fontWeight="medium">
                  <Link to={`/app/${app.id}`}>{app.name}</Link>
                </Box>
                <Box
                  fontSize="sm"
                  color="gray.400"
                  display="flex"
                  justifyContent="space-between"
                >
                  <Text display="flex" alignItems="center">
                    <Box mr={1} as="span">
                      {app.appMetaGithub ? (
                        <GithubIcon size={16} />
                      ) : (
                        <Image
                          boxSize="16px"
                          objectFit="cover"
                          src="/dokku.png"
                          alt="dokkuLogo"
                        />
                      )}
                    </Box>

                    {app.appMetaGithub
                      ? `${app.appMetaGithub.repoOwner}/${app.appMetaGithub.repoName}`
                      : ''}
                  </Text>
                  <Text>
                    Created on {format(new Date(app.createdAt), 'MM/DD/YYYY')}
                  </Text>
                </Box>
              </Box>
            ))}

            <Heading as="h2" size="md" py={5} mt={8}>
              Databases
            </Heading>
            {data?.databases.length === 0 ? (
              <Text fontSize="sm" color="gray.400">
                No databases created.
              </Text>
            ) : null}
            {data?.databases.map((database) => {
              const DbIcon = dbTypeToIcon(database.type);

              return (
                <Box
                  key={database.id}
                  py={3}
                  borderBottom={'1px'}
                  borderColor="gray.200"
                >
                  <Box mb={1} color="gray.900" fontWeight="medium">
                    <Link to={`/database/${database.id}`}>{database.name}</Link>
                  </Box>
                  <Box
                    fontSize="sm"
                    color="gray.400"
                    display="flex"
                    justifyContent="space-between"
                  >
                    <Text display="flex" alignItems="center">
                      <Box mr={1} as="span">
                        <DbIcon size={16} />
                      </Box>
                      {dbTypeToReadableName(database.type)}
                    </Text>
                    <Text>
                      Created on{' '}
                      {format(new Date(database.createdAt), 'MM/DD/YYYY')}
                    </Text>
                  </Box>
                </Box>
              );
            })}
          </GridItem>

          <GridItem colSpan={{ base: 12, md: 5 }}>
            <Heading as="h2" size="md" py={5}>
              Latest activity
            </Heading>
            <Text fontSize="sm" color="gray.400">
              Coming soon
            </Text>
          </GridItem>
        </Grid>
      </Container>
    </div>
  );
}