@chakra-ui/react#Grid TypeScript Examples

The following examples show how to use @chakra-ui/react#Grid. 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: FormControl.tsx    From openchakra with MIT License 6 votes vote down vote up
FormControl: React.FC<FormControlPropType> = ({
  label,
  htmlFor,
  children,
  hasColumn,
}) => (
  <ChakraFormControl
    mb={3}
    as={Grid}
    display="flex"
    alignItems="center"
    justifyItems="center"
  >
    <FormLabel
      p={0}
      mr={2}
      color="gray.500"
      lineHeight="1rem"
      width={hasColumn ? '2.5rem' : '90px'}
      fontSize="xs"
      htmlFor={htmlFor}
    >
      {label}
    </FormLabel>
    <Box
      display="flex"
      alignItems="center"
      justifyItems="center"
      width={hasColumn ? '30px' : '130px'}
    >
      {children}
    </Box>
  </ChakraFormControl>
)
Example #2
Source File: Recommend.tsx    From phonepare with MIT License 6 votes vote down vote up
Recommend : FC = () => {
  const [ questionIndex, setQuestionIndex ] = useRecoilState(questionIndexState)
  const [ questionTags, setQuestionTags ] = useRecoilState(questionTagsState)
  const setSelecetedPhones =  useSetRecoilState(selectedPhonesState)

  return <Box py={10} textAlign='center'>
    <Heading mb={3}>폰 추천받기</Heading>
    {
      questionIndex < Questions.length ? <Box px={{ base: 10, md: 32 }}>
        <Flex height='60vh' alignItems='center' justifyContent='center'>
          <Box>
            <Heading fontSize='2xl'>{Questions[questionIndex].question}</Heading>
            <Progress mt={4} mb={6} value={questionIndex} max={Questions.length} size='md' rounded='2xl' />
            <Grid templateColumns={{ base: 'repeat(1, 1fr)', md: 'repeat(2, 1fr)' }} gap={5}>
              {
                Questions[questionIndex].options.map((option, index) => <Card key={index} onClick={() => {
                  setQuestionTags({ ...questionTags, [Questions[questionIndex].id]: option.value })
                  setQuestionIndex(questionIndex + 1)
                  if(questionIndex < Questions.length) setSelecetedPhones(getRecommended(questionTags).map(x => x.data.id) as [ string, string, string ])
                }}>
                  <Text>{option.subLabel}</Text>
                  <Heading fontSize='xl'>{option.label}</Heading>
                </Card>
                )
              }
            </Grid>
          </Box>
        </Flex> 
      </Box> : <Box>
        <Heading fontSize='2xl'>아래 휴대폰을 추천드립니다!</Heading>
        <Button mt={4} onClick={() => setQuestionIndex(0)}>다시 고르기</Button>
        <Compare asComponent />
      </Box>
    }     
  </Box>
}
Example #3
Source File: Index.tsx    From phonepare with MIT License 6 votes vote down vote up
Index: FC = () => {
  return <Box minHeight='100vh'>
    <Flex height='100vh' alignItems='center' justifyContent='center'>
      <Box textAlign='center'>
        <Heading mb={10}>어떤 메뉴를 찾으시나요?</Heading>
        <Grid templateColumns={{ base: 'repeat(1, 1fr)', md: 'repeat(2, 1fr)' }} gap={5}>
          <Link to='/compare'>
            <Card>
              <Text>전 휴대폰을 잘 알아요!</Text>
              <Heading fontSize='2xl'>폰 비교하기</Heading>
            </Card>
          </Link>
          <Link to='/recommend'>
            <Card>
              <Text>저에게 맞는 휴대폰을 찾아보고 싶어요!</Text>
              <Heading fontSize='2xl'>폰 추천받기</Heading>
            </Card>
          </Link>
        </Grid>
      </Box>
    </Flex>
  </Box>
}
Example #4
Source File: Spec.tsx    From phonepare with MIT License 6 votes vote down vote up
Spec: FC = () => {
  const phones = useRecoilValue(selectedPhonesDataState)
  return <Box>
    {
      <PhoneGrid>
        {
          phones.map((phone, index) => {
            return <Box key={index}>
              {
                phone ? <Grid gridRowGap={4}>
                  <Information name='프로세서' data={phone.data.cpu} icon={MdMemory} />
                  <Information name='메모리' data={phone.data.ram} icon={FaMemory} />
                  <Information name='출시일' data={phone.data.releaseDate} icon={FaCalendarDay} />
                </Grid>
                  : ''
              }
            </Box>
          }
          )}
      </PhoneGrid>
    }
  </Box>
}
Example #5
Source File: Size.tsx    From phonepare with MIT License 6 votes vote down vote up
Size: FC = () => {
  const phones = useRecoilValue(selectedPhonesDataState)
  return <Box>
    {
      <PhoneGrid>
        {
          phones.map((phone, index) => {
            return <Box key={index}>
              {
                phone ? <Grid gridRowGap={4}>
                  <Information name='크기 (가로 x 세로 x 높이)' data={`${phone.data.size}mm`} />
                  <Information name='무게' data={`${phone.data.weight}g`} />
                </Grid>
                  : ''
              }
            </Box>
          }
          )}
      </PhoneGrid>
    }
  </Box>
}
Example #6
Source File: Others.tsx    From phonepare with MIT License 6 votes vote down vote up
Others: FC = () => {
  const phones = useRecoilValue(selectedPhonesDataState)
  return <Box>
    {
      <PhoneGrid>
        {
          phones.map((phone, index) => {
            return <Box key={index}>
              {
                phone ? <Grid gridRowGap={4}>
                  <Information name='방수 등급' data={phone.data.waterproof} />
                </Grid>
                  : ''
              }
            </Box>
          }
          )}
      </PhoneGrid>
    }
  </Box>
}
Example #7
Source File: Display.tsx    From phonepare with MIT License 6 votes vote down vote up
Display: FC = () => {
  const phones = useRecoilValue(selectedPhonesDataState)
  return <Box>
    {
      <PhoneGrid>
        {
          phones.map((phone, index) => {
            return <Box key={index}>
              {
                phone ? <Grid gridRowGap={4}>
                  <Box textAlign='center'>
                    <Text fontSize='7xl' fontWeight='semibold'>{phone.data.display.size}&quot;</Text>
                    <Text fontSize='2xl' mt={-4}>{phone.data.display.type}</Text>
                  </Box>
                  <Information name='해상도' data={phone.data.display.resolution} />
                  <Information name='PPI' data={String(phone.data.display.ppi)} />
                </Grid>
                  : ''
              }
            </Box>
          }
          )}
      </PhoneGrid>
    }
  </Box>
}
Example #8
Source File: Camera.tsx    From phonepare with MIT License 6 votes vote down vote up
Camera: FC = () => {
  const phones = useRecoilValue(selectedPhonesDataState)
  return <Box>
    {
      <PhoneGrid>
        {
          phones.map((phone, index) => {
            return <Box key={index}>
              {
                phone ? <Grid gridRowGap={4}>
                  <Information name='전면 카메라' data={joinWithBr(phone.data.camera.front)} />
                  <Information name='후면 카메라' data={joinWithBr(phone.data.camera.back)} />
                </Grid>
                  : ''
              }
            </Box>
          }
          )}
      </PhoneGrid>
    }
  </Box>
}
Example #9
Source File: Battery.tsx    From phonepare with MIT License 6 votes vote down vote up
Battery: FC = () => {
  const phones = useRecoilValue(selectedPhonesDataState)
  return <Box>
    {
      <PhoneGrid>
        {
          phones.map((phone, index) => {
            return <Box key={index}>
              {
                phone ? <Grid gridRowGap={4}>
                  <Information name='배터리 용량' data={`${phone.data.battery}mAh`} />
                  <Information name='충전 방식' data={phone.data.charge} />
                </Grid>
                  : ''
              }
            </Box>
          }
          )}
      </PhoneGrid>
    }
  </Box>
}
Example #10
Source File: learning.tsx    From website with MIT License 6 votes vote down vote up
LearningPage: NextPage<LearningPageProps> = ({ featuredMaterials, theRest }) => (
  <Page title="Learning">
    <Content>
      <Box as="section" backgroundColor="reactBlue.100" px={[4, null, null, 8]} py={['3.1em', null, null, '6.1em']}>
        <Grid gap="2px" justifyContent="center" gridAutoFlow="row" textAlign="center" mx="auto" maxWidth="6xl">
          <Heading as="span" fontFamily="body" fontWeight={300} mb={2} textTransform="uppercase" size="md" textAlign="center">
            Ingin Belajar React?
          </Heading>
          <Heading as="h1" fontFamily="body" fontWeight={600} size="2xl" textAlign="center">
            Materi Pembelajaran
          </Heading>
          <Text as="h2" mt="20px" mb="16px" fontSize={16} textAlign="center">
            Beberapa konsep React memang terlihat janggal, tapi di luar itu React sangat mudah untuk dipelajari dan dipahami, baik mereka
            yang sudah mahir dalam JavaScript modern ataupun yang baru saja memulai. Cobalah memulai dari salah satu materi di bawah.
          </Text>
          <Grid templateColumns="repeat(auto-fit, minmax(calc(296px), 1fr))" gap="24px" mt="36px">
            {featuredMaterials.map(item => (
              <LearningCard heading={item.type} title={item.title} desc={item.description} href={item.url} key={item.title} />
            ))}
            {theRest.map(item => (
              <LearningCard heading={item.type} title={item.title} desc={item.description} href={item.url} key={item.title} />
            ))}
          </Grid>
        </Grid>
      </Box>
    </Content>
  </Page>
)
Example #11
Source File: Footer.tsx    From website with MIT License 6 votes vote down vote up
Footer: React.FC<FooterProps> = ({ className }) => (
  <Grid
    as="footer"
    px="24px"
    py="36px"
    color="gray.300"
    bg="gray.900"
    templateColumns="1fr 1fr minmax(auto, 1140px) 1fr 1fr"
    className={className}
  >
    <Stack align="center" justify="center" gridColumn="3/4" textAlign="center">
      <section>
        <Text margin="0" lineHeight="20px" fontSize="12px">
          &copy; 2020 ReactJS ID.
        </Text>
        <Text margin="0" lineHeight="20px" fontSize="12px">
          Kode sumber situs ini tersedia di{' '}
          <Link href="https://github.com/reactjs-id/website" isExternal color="#fff" rel="noopener noreferrer">
            GitHub
          </Link>
          . Gambar latar disediakan oleh{' '}
          <Link href="https://www.transparenttextures.com/" isExternal color="#fff" rel="noopener noreferrer">
            Transparent Textures
          </Link>
          .
        </Text>
      </section>
    </Stack>
  </Grid>
)
Example #12
Source File: IconList.tsx    From lucide with ISC License 6 votes vote down vote up
IconList = memo(({ icons }: IconListProps) => {
  const router = useRouter();

  return (
    <Grid templateColumns={`repeat(auto-fill, minmax(150px, 1fr))`} gap={5} marginBottom="320px">
      {icons.map(icon => {
        return (
          <Link
            key={icon.name}
            scroll={false}
            shallow={true}
            href={{
              pathname: '/icon/[iconName]',
              query: {
                ...router.query,
                iconName: icon.name,
              },
            }}
          >
            <IconListItem {...icon} />
          </Link>
        );
      })}
    </Grid>
  );
})
Example #13
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 #14
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 #15
Source File: index.tsx    From dendron with GNU Affero General Public License v3.0 5 votes vote down vote up
export default function Home() {
  const { isError, gardens, error } = useDendronGardens();
  if (isError) return <div>failed to load: {JSON.stringify(error)}</div>;
  let extra: any;
  // if (_.isEmpty(gardens)) {
  //   extra = <Button>New Garden from Git</Button>;
  // }
  if (_.isEmpty(gardens)) {
    extra = (
      <Box maxW="32rem">
        <VStack spacing={4} align="stretch">
          <Center>
            <Heading mb={4}>Welcome to Dendron</Heading>
          </Center>
          <Text fontSize="xl">
            If you haven&apos;t already done so, you can install Dendron by following
            the instructions &nbsp;
            <Link
              href="https://dendron.so/notes/678c77d9-ef2c-4537-97b5-64556d6337f1.html"
              isExternal
            >
              here
            </Link>
          </Text>
          <Button>Publish a new site from Git (coming soon) </Button>
        </VStack>
      </Box>
    );
  }
  return (
    <>
      <Head>
        <title>Dendron</title>
        <link rel="icon" href="/favicon.ico" />
        <script type="text/javascript" src="/static/memberful.js" />
      </Head>

      <Grid justifyContent="center">{extra}</Grid>
    </>
  );
}
Example #16
Source File: PhoneGrid.tsx    From phonepare with MIT License 5 votes vote down vote up
PhoneGrid: FC<{ children: ReactNode[] }> = ({ children }) => {
  const [ largeScreen ] = useMediaQuery('(min-width: 62em)')
  return <Grid templateColumns={{ base: 'repeat(2, 1fr)', lg: 'repeat(3, 1fr)' }} columnGap={10}>
    {children.slice(0, largeScreen ? 3 : 2)}
  </Grid>
}
Example #17
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 #18
Source File: index.tsx    From website with MIT License 5 votes vote down vote up
IndexPage: NextPage = () => (
  <Page>
    <Content>
      <HomePageHeader
        welcomeMessage="Selamat Datang"
        title="Komunitas Developer ReactJS Indonesia"
        desc="ReactJS ID adalah komunitas para developer React dan React Native. Kami mengadakan ajang meetup setiap bulannya, dimana para developer React bertukar informasi mengenai React dan ekosistemnya."
      />
      <Box backgroundColor="#f2f2f2" px={[4, null, null, 8]} py={['3.1em', null, null, '6.1em']}>
        <Box mx="auto" maxWidth="6xl">
          <Grid gridTemplateColumns={['1fr', null, null, '1fr 1fr']} gridGap={8} alignItems="center">
            <Img
              display={['none', null, null, 'block']}
              gridColumn={1}
              width="400px"
              height="400px"
              src="/rumah-komunitas/tshirt.jpg"
              alt="tshirt from rumah komunitas"
            />
            <Grid gridColumn={[1, null, null, 2]}>
              <Heading as="h2" mb={2} size="md" textTransform="uppercase" fontWeight={300}>
                Kabar Gembira!
              </Heading>
              <Heading as="h3" size="2xl" fontWeight={600}>
                Merchandise Resmi ReactJS Indonesia
              </Heading>
              <Text as="p" my="18px">
                Merchandise resmi ReactJS Indonesia kini tersedia berkat kerjasama oleh Rumah Komunitas. Klik link di bawah untuk
                mendapatkan T-shirt dan jaket ReactJS Indonesia.
              </Text>
              <LinkButton ml="-16px" href="https://www.rumahkomunitas.com/react-indonesia" isExternal>
                Dapatkan Segera
              </LinkButton>
            </Grid>
          </Grid>
        </Box>
      </Box>

      <Box backgroundColor="reactBlue.100" px={[4, null, null, 8]} py={['3.1em', null, null, '6.1em']}>
        <Grid gap="2px" justifyContent="center" gridAutoFlow="row" textAlign="center" mx="auto" maxWidth="6xl">
          <Heading as="h2" mb={2} color="gray.900" size="md" textTransform="uppercase" fontWeight={300}>
            Ingin Belajar React?
          </Heading>
          <Heading as="h3" color="gray.900" size="2xl" fontWeight={600}>
            Materi Pembelajaran
          </Heading>
          <Text as="p" color="gray.900" my="1.2em">
            Beberapa konsep React memang terlihat janggal, tapi diluar itu React sangat mudah untuk dipelajari dan dipahami, baik mereka
            yang sudah mahir dalam JavaScript modern ataupun yang baru saja memulai. Cobalah memulai dari salah satu materi di bawah.
          </Text>
          <Grid gridTemplateColumns="repeat(auto-fit, minmax(calc(296px), 1fr))" gap="24px" mt="24px">
            {learningResources
              .filter(resource => resource.featured)
              .map(item => (
                <LearningCard heading={item.type} title={item.title} desc={item.description} href={item.url} key={item.title} />
              ))}
          </Grid>
          <Flex justifyContent="center" mt="4em">
            <NextLink href="/learning">
              <LinkButton _hover={{ backgroundColor: 'reactBlue.900' }} mt="auto" backgroundColor="reactBlue.800" color="white">
                Lihat Selengkapnya
              </LinkButton>
            </NextLink>
          </Flex>
        </Grid>
      </Box>
    </Content>
  </Page>
)
Example #19
Source File: HuesPickerControl.tsx    From openchakra with MIT License 5 votes vote down vote up
HuesPickerControl = (props: HuesPickerPropType) => {
  const [hue, setHue] = useState(500)

  return (
    <>
      <Grid mb={2} templateColumns="repeat(5, 1fr)" gap={0}>
        {Object.keys(props.themeColors).map(colorName =>
          props.gradient ? (
            <Box
              border={colorName.includes('white') ? '1px solid lightgrey' : ''}
              key={colorName}
              _hover={{ boxShadow: 'lg' }}
              cursor="pointer"
              bg={`${colorName}.${props.enableHues ? hue : 500}`}
              onClick={() => {
                if (props.updateGradient) {
                  props.updateGradient(`${colorName}.${hue}`, props.index!)
                }
              }}
              mt={2}
              borderRadius="full"
              height="30px"
              width="30px"
            />
          ) : (
            <Box
              border={colorName.includes('white') ? '1px solid lightgrey' : ''}
              key={colorName}
              _hover={{ boxShadow: 'lg' }}
              cursor="pointer"
              bg={`${colorName}.${props.enableHues ? hue : 500}`}
              onClick={() =>
                props.setValue(
                  props.name,
                  props.enableHues ? `${colorName}.${hue}` : colorName,
                )
              }
              mt={2}
              borderRadius="full"
              height="30px"
              width="30px"
            />
          ),
        )}
      </Grid>

      {props.enableHues && (
        <>
          <Slider
            onChange={value => {
              value = value === 0 ? 50 : value
              setHue(value)
            }}
            min={0}
            max={900}
            step={100}
            value={hue}
          >
            <SliderTrack>
              <SliderFilledTrack />
            </SliderTrack>
            <SliderThumb boxSize={8}>
              <Box borderRadius="full" fontSize="xs">
                {hue}
              </Box>
            </SliderThumb>
          </Slider>
        </>
      )}
    </>
  )
}
Example #20
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 #21
Source File: IconCustomizerDrawer.tsx    From lucide with ISC License 5 votes vote down vote up
export function IconCustomizerDrawer() {
  const [showCustomize, setShowCustomize] = useState(false);
  const { color, setColor, size, setSize, strokeWidth, setStroke, resetStyle } = useContext(IconStyleContext);

  return (
    <>
      <Button as="a" leftIcon={<Edit />} size="lg" onClick={() => setShowCustomize(true)}>
        Customize
      </Button>
      <Drawer isOpen={showCustomize} placement="right" onClose={() => setShowCustomize(false)}>
        <DrawerOverlay />
        <DrawerContent>
          <DrawerCloseButton />
          <DrawerHeader>Customize Icons</DrawerHeader>

          <DrawerBody>
            <Grid gridGap={'1em'}>
              <FormControl>
                <ColorPicker
                  color={color}
                  value={color}
                  onChangeComplete={(col) => setColor(col.hex)}
                />
              </FormControl>
              <FormControl>
                <FormLabel htmlFor="stroke">
                  <Flex>
                    <Text flexGrow={1} fontWeight={'bold'}>
                      Stroke
                    </Text>
                    <Text>{strokeWidth}px</Text>
                  </Flex>
                </FormLabel>
                <Slider
                  value={strokeWidth}
                  onChange={setStroke}
                  min={0.5}
                  max={3}
                  step={0.5}
                  name={'stroke'}
                >
                  <SliderTrack>
                    <SliderFilledTrack bg={color} />
                  </SliderTrack>
                  <SliderThumb />
                </Slider>
              </FormControl>
              <FormControl>
                <FormLabel htmlFor="size">
                  <Flex>
                    <Text flexGrow={1} fontWeight={'bold'}>
                      Size
                    </Text>
                    <Text>{size}px</Text>
                  </Flex>
                </FormLabel>
                <Slider value={size} onChange={setSize} min={12} max={64} step={1} name={'size'}>
                  <SliderTrack>
                    <SliderFilledTrack bg={color} />
                  </SliderTrack>
                  <SliderThumb />
                </Slider>
              </FormControl>
              <FormControl>
                <Button onClick={resetStyle}>Reset</Button>
              </FormControl>
            </Grid>
          </DrawerBody>
        </DrawerContent>
      </Drawer>
    </>
  );
}
Example #22
Source File: TopNavigation.tsx    From website with MIT License 4 votes vote down vote up
TopNavigation: React.FC<TopNavigationProps> = ({ title }) => {
  const { isOpen, onOpen, onClose } = useDisclosure()
  return (
    <Grid
      as="nav"
      templateColumns={`1fr 1fr minmax(auto, ${theme.sizes['6xl']}) 1fr 1fr`}
      backgroundColor="gray.900"
      color="white"
      height={['88px', '80px']}
      zIndex={50}
    >
      <List display="flex" flexWrap="wrap" alignItems="center" gridColumn="3/4" m={0} p={0}>
        <ListItem display="flex" alignItems="center" pos="relative" h="100%" mr="auto">
          <NextChakraLink
            href="/"
            display="flex"
            alignItems="center"
            py=".5rem"
            px="1rem"
            h="100%"
            _hover={{ bg: 'rgba(255, 255, 255, 0.1)', textDecoration: 'none' }}
          >
            <Logo height={40} fill={theme.colors.white} title={title} />
          </NextChakraLink>
        </ListItem>

        <ListItem display={['none', 'flex']} alignItems="center" h="100%">
          <NextChakraLink
            href="/community"
            display="flex"
            alignItems="center"
            py="1.5rem"
            px="1rem"
            color="inherit"
            h="100%"
            lineHeight={1}
            _hover={{ bg: 'rgba(255, 255, 255, 0.1)', textDecoration: 'none' }}
          >
            Komunitas
          </NextChakraLink>
        </ListItem>

        <ListItem display={['none', 'flex']} alignItems="center" h="100%">
          <NextChakraLink
            href="/submit-a-talk"
            display="flex"
            alignItems="center"
            py="1.5rem"
            px="1rem"
            color="inherit"
            h="100%"
            lineHeight={1}
            _hover={{ bg: 'rgba(255, 255, 255, 0.1)', textDecoration: 'none' }}
          >
            Ajukan Topik
          </NextChakraLink>
        </ListItem>

        <ListItem display={['none', 'flex']} alignItems="center" h="100%">
          <NextChakraLink
            href="/faq"
            display="flex"
            alignItems="center"
            py="1.5rem"
            px="1rem"
            color="inherit"
            h="100%"
            lineHeight={1}
            _hover={{ bg: 'rgba(255, 255, 255, 0.1)', textDecoration: 'none' }}
          >
            FAQ
          </NextChakraLink>
        </ListItem>

        <ListItem py="1.5rem" px="1rem" display={['flex', 'none']} alignItems="center" h="100%">
          <IconButton variant="outline" aria-label="Open menu" icon={<HamburgerIcon />} onClick={onOpen} />

          <Drawer isOpen={isOpen} placement="right" onClose={onClose}>
            <DrawerOverlay />
            <DrawerContent>
              <DrawerCloseButton />
              <DrawerHeader>Menu Utama</DrawerHeader>

              <DrawerBody>
                <NextChakraLink
                  href="/community"
                  display="flex"
                  alignItems="center"
                  py="1.5rem"
                  px="1rem"
                  color="inherit"
                  lineHeight={1}
                  _hover={{ bg: 'rgba(255, 255, 255, 0.1)', textDecoration: 'none' }}
                >
                  Komunitas
                </NextChakraLink>

                <NextChakraLink
                  href="/submit-a-talk"
                  display="flex"
                  alignItems="center"
                  py="1.5rem"
                  px="1rem"
                  color="inherit"
                  lineHeight={1}
                  _hover={{ bg: 'rgba(255, 255, 255, 0.1)', textDecoration: 'none' }}
                >
                  Ajukan Topik
                </NextChakraLink>

                <NextChakraLink
                  href="/faq"
                  display="flex"
                  alignItems="center"
                  py="1.5rem"
                  px="1rem"
                  color="inherit"
                  lineHeight={1}
                  _hover={{ bg: 'rgba(255, 255, 255, 0.1)', textDecoration: 'none' }}
                >
                  FAQ
                </NextChakraLink>
              </DrawerBody>
            </DrawerContent>
          </Drawer>
        </ListItem>
      </List>
    </Grid>
  )
}
Example #23
Source File: create-database.tsx    From ledokku with MIT License 4 votes vote down vote up
CreateDatabase = () => {
  const location = useLocation();
  const history = useHistory();
  const toast = useToast();

  const { data: dataDb } = useDatabaseQuery();
  const [arrayOfCreateDbLogs, setArrayofCreateDbLogs] = useState<RealTimeLog[]>(
    []
  );
  const [isTerminalVisible, setIsTerminalVisible] = useState(false);
  const [createDatabaseMutation] = useCreateDatabaseMutation();
  const [
    isDbCreationSuccess,
    setIsDbCreationSuccess,
  ] = useState<DbCreationStatus>();

  useCreateDatabaseLogsSubscription({
    onSubscriptionData: (data) => {
      const logsExist = data.subscriptionData.data?.createDatabaseLogs;

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

  const createDatabaseSchema = yup.object({
    type: yup
      .string()
      .oneOf(['POSTGRESQL', 'MYSQL', 'MONGODB', 'REDIS'])
      .required(),
    name: yup.string().when('type', (type: DatabaseTypes) => {
      return yup
        .string()
        .required('Database name is required')
        .matches(/^[a-z0-9-]+$/)
        .test(
          'Name already exists',
          `You already have created ${type} database with this name`,
          (name) =>
            !dataDb?.databases.find(
              (db) => db.name === name && type === db.type
            )
        );
    }),
  });

  const [
    isDokkuPluginInstalled,
    { data, loading, error: isDokkuPluginInstalledError },
  ] = useIsPluginInstalledLazyQuery({
    // we poll every 5 sec
    pollInterval: 5000,
  });
  const formik = useFormik<{ name: string; type: DatabaseTypes }>({
    initialValues: {
      name: location.state ? (location.state as string) : '',
      type: 'POSTGRESQL',
    },
    validateOnChange: true,
    validationSchema: createDatabaseSchema,
    onSubmit: async (values) => {
      try {
        await createDatabaseMutation({
          variables: {
            input: { name: values.name, type: values.type },
          },
        });
        setIsTerminalVisible(true);

        trackGoal(trackingGoals.createDatabase, 0);
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

  const isPluginInstalled = data?.isPluginInstalled.isPluginInstalled;

  const handleNext = () => {
    setIsTerminalVisible(false);
    const dbId = arrayOfCreateDbLogs[arrayOfCreateDbLogs.length - 1].message;
    history.push(`database/${dbId}`);
  };

  // Effect for checking whether plugin is installed
  useEffect(() => {
    isDokkuPluginInstalled({
      variables: {
        pluginName: dbTypeToDokkuPlugin(formik.values.type),
      },
    });
  }, [formik.values.type, isPluginInstalled, isDokkuPluginInstalled]);

  // Effect for db creation
  useEffect(() => {
    isDbCreationSuccess === DbCreationStatus.FAILURE
      ? toast.error('Failed to create database')
      : isDbCreationSuccess === DbCreationStatus.SUCCESS &&
        toast.success('Database created successfully');
  }, [isDbCreationSuccess, toast]);

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

      <Container maxW="5xl" my="4">
        <Heading as="h2" size="md">
          Create a new database
        </Heading>
        <Box mt="12">
          {isTerminalVisible ? (
            <>
              <Text mb="2">
                Creating <b>{formik.values.type}</b> database{' '}
                <b>{formik.values.name}</b>
              </Text>
              <Text mb="2" color="gray.500">
                Creating database usually takes a couple of minutes. Breathe in,
                breathe out, logs are about to appear below:
              </Text>
              <Terminal>
                {arrayOfCreateDbLogs.map((log) => (
                  <Text key={arrayOfCreateDbLogs.indexOf(log)} size="small">
                    {log.message}
                  </Text>
                ))}
              </Terminal>

              {!!isDbCreationSuccess &&
              isDbCreationSuccess === DbCreationStatus.SUCCESS ? (
                <Box mt="12" display="flex" justifyContent="flex-end">
                  <Button
                    onClick={() => handleNext()}
                    rightIcon={<FiArrowRight size={20} />}
                  >
                    Next
                  </Button>
                </Box>
              ) : !!isDbCreationSuccess &&
                isDbCreationSuccess === DbCreationStatus.FAILURE ? (
                <Box mt="12" display="flex" justifyContent="flex-end">
                  <Button
                    onClick={() => {
                      setIsTerminalVisible(false);
                      formik.resetForm();
                    }}
                    rightIcon={<FiArrowLeft size={20} />}
                  >
                    Back
                  </Button>
                </Box>
              ) : null}
            </>
          ) : (
            <Box mt="8">
              <form onSubmit={formik.handleSubmit}>
                <Box mt="12">
                  {loading && (
                    <Center>
                      <Spinner />
                    </Center>
                  )}
                  {isDokkuPluginInstalledError ? (
                    <Alert
                      status="error"
                      variant="top-accent"
                      flexDirection="column"
                      alignItems="flex-start"
                      borderBottomRadius="base"
                      boxShadow="md"
                    >
                      <AlertTitle mr={2}>Request failed</AlertTitle>
                      <AlertDescription>
                        {isDokkuPluginInstalledError.message}
                      </AlertDescription>
                    </Alert>
                  ) : null}
                  {data?.isPluginInstalled.isPluginInstalled === false &&
                    !loading && (
                      <>
                        <Text mt="3">
                          Before creating a{' '}
                          <b>{formik.values.type.toLowerCase()}</b> database,
                          you will need to run this command on your dokku
                          server.
                        </Text>
                        <Terminal>{`sudo dokku plugin:install https://github.com/dokku/dokku-${dbTypeToDokkuPlugin(
                          formik.values.type
                        )}.git ${dbTypeToDokkuPlugin(
                          formik.values.type
                        )}`}</Terminal>
                        <Text mt="3">
                          Couple of seconds later you will be able to proceed
                          further.
                        </Text>
                      </>
                    )}
                  {data?.isPluginInstalled.isPluginInstalled === true &&
                    !loading && (
                      <SimpleGrid columns={{ sm: 1, md: 3 }}>
                        <FormControl
                          id="name"
                          isInvalid={Boolean(
                            formik.errors.name && formik.touched.name
                          )}
                        >
                          <FormLabel>Database name</FormLabel>
                          <Input
                            autoComplete="off"
                            id="name"
                            name="name"
                            value={formik.values.name}
                            onChange={formik.handleChange}
                            onBlur={formik.handleBlur}
                          />
                          <FormErrorMessage>
                            {formik.errors.name}
                          </FormErrorMessage>
                        </FormControl>
                      </SimpleGrid>
                    )}
                </Box>

                <Box mt="12">
                  <Text mb="2">Choose your database</Text>
                  <Grid
                    templateColumns={{
                      base: 'repeat(2, minmax(0, 1fr))',
                      md: 'repeat(4, minmax(0, 1fr))',
                    }}
                    gap="4"
                  >
                    <DatabaseBox
                      selected={formik.values.type === 'POSTGRESQL'}
                      label="PostgreSQL"
                      icon={<PostgreSQLIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'POSTGRESQL')}
                    />
                    <DatabaseBox
                      selected={formik.values.type === 'MYSQL'}
                      label="MySQL"
                      icon={<MySQLIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'MYSQL')}
                    />
                    <DatabaseBox
                      selected={formik.values.type === 'MONGODB'}
                      label="Mongo"
                      icon={<MongoIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'MONGODB')}
                    />
                    <DatabaseBox
                      selected={formik.values.type === 'REDIS'}
                      label="Redis"
                      icon={<RedisIcon size={40} />}
                      onClick={() => formik.setFieldValue('type', 'REDIS')}
                    />
                  </Grid>
                </Box>

                <Box mt="12" display="flex" justifyContent="flex-end">
                  <Button
                    isLoading={formik.isSubmitting}
                    disabled={
                      data?.isPluginInstalled.isPluginInstalled === false ||
                      !formik.values.name ||
                      !!formik.errors.name ||
                      !dataDb?.databases
                    }
                    rightIcon={<FiArrowRight size={20} />}
                    type="submit"
                  >
                    Create
                  </Button>
                </Box>
              </form>
            </Box>
          )}
        </Box>
      </Container>
    </>
  );
}
Example #24
Source File: create-app.tsx    From ledokku with MIT License 4 votes vote down vote up
CreateApp = () => {
  const history = useHistory();
  const toast = useToast();

  const createAppSchema = yup.object({
    type: yup
      .string()
      .oneOf(['GITHUB', 'GITLAB', 'DOCKER', 'DOKKU'])
      .required(),
  });

  const formik = useFormik<{ type: AppTypes }>({
    initialValues: {
      type: AppTypes.GITHUB,
    },
    validateOnChange: true,
    validationSchema: createAppSchema,
    onSubmit: async (values) => {
      try {
        values.type === AppTypes.GITHUB
          ? history.push('/create-app-github')
          : history.push('/create-app-dokku');
      } catch (error) {
        toast.error(error.message);
      }
    },
  });

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

      <Container maxW="5xl" my="4">
        <Heading py="2" as="h2" size="md">
          App source
        </Heading>
        <Box mt="24">
          <Box mt="4">
            <form onSubmit={formik.handleSubmit}>
              <Box mt="20">
                <Text mb="5" color="gray.500">
                  Choose between creating app from a Github repository or
                  creating a standalone Dokku app.
                </Text>
                <Grid
                  templateColumns={{
                    base: 'repeat(2, minmax(0, 1fr))',
                    md: 'repeat(4, minmax(0, 1fr))',
                  }}
                  gap="4"
                >
                  <SourceBox
                    selected={formik.values.type === AppTypes.GITHUB}
                    label="GitHub"
                    icon={<GithubIcon size={40} />}
                    onClick={() => formik.setFieldValue('type', 'GITHUB')}
                  />
                  <SourceBox
                    selected={formik.values.type === AppTypes.DOKKU}
                    label="Dokku"
                    icon={
                      <Image
                        boxSize="48px"
                        objectFit="cover"
                        src="/dokku.png"
                        alt="dokkuLogo"
                      />
                    }
                    onClick={() => formik.setFieldValue('type', 'DOKKU')}
                  />
                  <SourceBox
                    selected={formik.values.type === AppTypes.GITLAB}
                    label="Gitlab"
                    icon={<GitlabIcon size={40} />}
                    badge={
                      <Badge ml="1" colorScheme="red">
                        Coming soon
                      </Badge>
                    }
                    // Uncomment this when we can handle docker deployments
                    // onClick={() => formik.setFieldValue('type', 'GITLAB')}
                  />
                  <SourceBox
                    selected={formik.values.type === AppTypes.DOCKER}
                    label="Docker"
                    icon={<DockerIcon size={40} />}
                    badge={
                      <Badge ml="1" colorScheme="red">
                        Coming soon
                      </Badge>
                    }
                    // Uncomment this when we can handle docker deployments
                    // onClick={() => formik.setFieldValue('type', 'DOCKER')}
                  />
                </Grid>
              </Box>

              <Box mt="36" display="flex" justifyContent="flex-end">
                <Button
                  isLoading={formik.isSubmitting}
                  disabled={!formik.values.type || !!formik.errors.type}
                  rightIcon={<FiArrowRight size={20} />}
                  type="submit"
                >
                  Next
                </Button>
              </Box>
            </form>
          </Box>
        </Box>
      </Container>
    </>
  );
}
Example #25
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 #26
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 #27
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 #28
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 #29
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>
    </>
  );
}