@chakra-ui/react#useTheme TypeScript Examples

The following examples show how to use @chakra-ui/react#useTheme. 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: ReactSelectCustomization.tsx    From ke with MIT License 6 votes vote down vote up
function SingleValue<OptionType>({ ...props }: SingleValueProps<OptionType>): JSX.Element {
  const { singleValue } = useStyles()
  const { className, isDisabled } = props
  const theme = useTheme()
  return (
    <ClassNames>
      {({ cx: emotionCx, css }) => (
        <selectComponents.SingleValue
          {...props}
          className={emotionCx(className, {
            [css(chakraCss((singleValue as any)?._disabled)(theme))]: isDisabled,
          })}
        />
      )}
    </ClassNames>
  )
}
Example #2
Source File: ContentContainer.tsx    From coindrop with GNU General Public License v3.0 6 votes vote down vote up
ContentContainer: FC<ContentContainerProps> = ({ boxProps, children }) => {
    const theme = useTheme();
    return (
    <Container
        my={24}
        maxW={theme.breakpoints.xl}
        // eslint-disable-next-line react/jsx-props-no-spreading
        {...boxProps}
    >
        {children}
    </Container>
    );
}
Example #3
Source File: HeaderFooterContainer.tsx    From coindrop with GNU General Public License v3.0 6 votes vote down vote up
HeaderFooterContainer: FC = ({ children }) => {
    const theme = useTheme();
    return (
        <Container
            maxW={theme.breakpoints.xl}
        >
            {children}
        </Container>
    );
}
Example #4
Source File: DefaultLayoutHOC.tsx    From coindrop with GNU General Public License v3.0 6 votes vote down vote up
withDefaultLayout = (Component: ComponentType): FunctionComponent<Props> => {
    const WrappedComponent: FunctionComponent = (props: Props) => {
        const theme = useTheme();
        return (
            <Container
                maxW={theme.breakpoints.lg}
                mx="auto"
                px={4}
                mb={6}
            >
                <Navbar />
                <Box mb={[2, 0]} />
                <hr />
                <Component {...props} />
                <Footer />
            </Container>
        );
    };
    return WrappedComponent;
}
Example #5
Source File: Logo.tsx    From coindrop with GNU General Public License v3.0 6 votes vote down vote up
PiggyLogo: FC<IconProps> = (iconProps) => {
    const theme = useTheme();
    const { colorMode } = useColorMode();
    const logoOutlineColor = useColorModeValue(theme.colors.gray['800'], theme.colors.gray['900']);
    // eslint-disable-next-line react/jsx-props-no-spreading
    return colorMode === 'light'
        ? <PiggyLogoIcon color={logoOutlineColor} {...iconProps} />
        : <PiggyLogoIconDarkMode color={logoOutlineColor} {...iconProps} />;
}
Example #6
Source File: Logo.tsx    From coindrop with GNU General Public License v3.0 6 votes vote down vote up
Logo: FC<Props> = ({ text = 'coindrop', subtitle }) => {
    const theme = useTheme();
    const fontColor = useColorModeValue(theme.colors.gray['600'], theme.colors.gray['50']);
    const LogoText = () => (
        <Text
            fontSize={["4xl", "5xl"]}
            fontFamily="Changa, system-ui, sans-serif"
            fontWeight={500}
            color={fontColor}
            ml={2}
            lineHeight={["1.5rem", "2.5rem"]}
        >
            {text}
        </Text>
    );
    return (
        <Flex
            ml={1}
            mr={2}
            mb={[2, 0]}
            align="center"
        >
            <PiggyLogo boxSize={["48px", "64px"]} />
            <Box>
                <LogoText />
                {subtitle && (
                    <Text textAlign="center">
                        {subtitle}
                    </Text>
                )}
            </Box>
        </Flex>
    );
}
Example #7
Source File: PiggybankListItem.tsx    From coindrop with GNU General Public License v3.0 5 votes vote down vote up
PiggybankListItem: FunctionComponent<Props> = ({ id }) => {
    const { colors } = useTheme();
    const [isLoading, setIsLoading] = useState(false);
    const hoverBg = useColorModeValue(colors.gray['100'], colors.gray['600']);
    const activeBg = useColorModeValue(colors.gray['200'], colors.gray['700']);
    return (
        <Box
            onClick={() => setIsLoading(true)}
            cursor="pointer"
            mt={3}
            bg={isLoading ? hoverBg : undefined}
            _hover={{
                bg: hoverBg,
                textDecoration: "none",
            }}
            _active={{
                bg: activeBg,
            }}
        >
            <NextLink href={`/${id}`} passHref>
                <a id={`link-to-coindrop-${id}`}>
                    <Box
                        py={5}
                        shadow="md"
                        borderWidth="1px"
                        borderRadius="10px"
                    >
                        {isLoading ? (
                            <Flex align="center" justify="center">
                                <Spinner boxSize="32px" />
                                <Heading ml={2} fontSize="xl">
                                    Loading
                                </Heading>
                            </Flex>
                        ) : (
                            <Flex
                                justify="space-between"
                                align="center"
                                mx={4}
                            >
                                <Heading fontSize="xl" wordBreak="break-word">
                                    coindrop.to/
                                    {id}
                                </Heading>
                                <ChevronRightIcon boxSize="32px" />
                            </Flex>
                        )}
                    </Box>
                </a>
            </NextLink>
        </Box>
    );
}
Example #8
Source File: PaymentMethodButton.tsx    From coindrop with GNU General Public License v3.0 5 votes vote down vote up
PaymentMethodButton: FunctionComponent<Props> = ({
    paymentMethod,
    paymentMethodValue,
    isPreferred,
    accentColor,
}) => {
    const theme = useTheme();
    const { isOpen, onOpen, onClose } = useDisclosure();
    const paymentMethodDisplayName = paymentMethodNames[paymentMethod];
    const Icon = paymentMethodIcons[paymentMethod];
    if (!paymentMethodDisplayName || !Icon) return null;
    const textColor = useColorModeValue("gray.800", "white");
    const bgColor = useColorModeValue("gray.50", "gray.700");
    const bgColorHover = useColorModeValue("gray.100", "gray.600");
    const bgColorActive = useColorModeValue("gray.200", "gray.500");
    const borderColor = isPreferred
        ? theme.colors[accentColor]['500']
        : useColorModeValue("gray.300", "gray.500");
    const borderColorHover = isPreferred
        ? theme.colors[accentColor]['500']
        : useColorModeValue("gray.300", "gray.500");
    return (
        <>
        <PaymentMethodButtonModal
            isOpen={isOpen}
            onClose={onClose}
            paymentMethod={paymentMethod}
            paymentMethodDisplayName={paymentMethodDisplayName}
            paymentMethodValue={paymentMethodValue}
        />
        <Box
            as="button"
            id={`payment-method-button-${paymentMethod}`}
            onClick={onOpen}
            lineHeight="1.2"
            transition="all 0.2s cubic-bezier(.08,.52,.52,1)"
            borderWidth={isPreferred ? "2px" : "1px"}
            rounded="6px"
            fontSize="18px"
            fontWeight="semibold"
            bg={bgColor}
            borderColor={borderColor}
            p={4}
            m={2}
            shadow="md"
            color={textColor}
            _hover={{
                bg: bgColorHover,
                transform: "scale(0.98)",
                borderColor: borderColorHover,
            }}
            _active={{
                bg: bgColorActive,
                transform: "scale(0.96)",
                borderColor: borderColorHover,
            }}
            _focus={{
                outline: "none",
            }}
        >
            <Flex align="center">
                <Icon boxSize="32px" />
                <Text ml={2}>{paymentMethodDisplayName}</Text>
            </Flex>
            {isPreferred && (
                <Text
                    fontSize="xs"
                    color={textColor}
                >
                    Preferred
                </Text>
            )}
        </Box>
        </>
    );
}
Example #9
Source File: faq.tsx    From coindrop with GNU General Public License v3.0 5 votes vote down vote up
FAQ: FunctionComponent = () => {
    const theme = useTheme();
    const panelBgColor = useColorModeValue("gray.50", undefined);
    return (
        <Box>
            <Box my={6}>
                <Heading as="h1" textAlign="center">
                    FAQ
                </Heading>
                <Text textAlign="center">
                    Frequently Asked Questions
                </Text>
            </Box>
            <Container maxW={theme.breakpoints.lg}>
                <Accordion defaultIndex={-1} allowToggle>
                    {accordionText.map(({title, body}) => (
                        <AccordionItem key={title}>
                            <AccordionButton>
                            <Box flex="1" textAlign="left">
                                {title}
                            </Box>
                            <AccordionIcon />
                            </AccordionButton>
                            <AccordionPanel>
                                <Box
                                    p={4}
                                    bg={panelBgColor}
                                >
                                    {body}
                                </Box>
                            </AccordionPanel>
                        </AccordionItem>
                    ))}
                </Accordion>
            </Container>
            <Text textAlign="center" mt={4} fontSize="sm">
                {"Do you have a question that's not answered here? Send it to "}
                <Link href={`mailto:${coindropEmail}`} isExternal>
                    {coindropEmail}
                </Link>
            </Text>
        </Box>
    );
}
Example #10
Source File: ColorPickerControl.tsx    From openchakra with MIT License 4 votes vote down vote up
ColorPickerControl = (props: ColorPickerPropType) => {
  const theme = useTheme()
  const themeColors: any = omit(theme.colors, [
    'transparent',
    'current',
    'black',
    'white',
  ])

  const { setValue, setValueFromEvent } = useForm()
  const value = usePropsSelector(props.name)

  let propsIconButton: any = { bg: value }
  if (value && themeColors[value]) {
    propsIconButton = { colorScheme: value }
  }

  return (
    <>
      <Popover placement="bottom">
        <PopoverTrigger>
          {props.gradient ? (
            <IconButton
              mr={2}
              boxShadow="md"
              border={props.gradientColor ? 'none' : '2px solid grey'}
              isRound
              aria-label="Color"
              size="xs"
              colorScheme={props.gradientColor}
              bg={props.gradientColor}
            />
          ) : (
            <IconButton
              mr={2}
              boxShadow="md"
              border={value ? 'none' : '2px solid grey'}
              isRound
              aria-label="Color"
              size="xs"
              {...propsIconButton}
            />
          )}
        </PopoverTrigger>
        <Portal>
          <PopoverContent width="200px">
            <PopoverArrow />
            <PopoverBody>
              {props.withFullColor ? (
                <Tabs size="sm" variant="soft-rounded" colorScheme="green">
                  <TabList>
                    <Tab>Theme</Tab>
                    <Tab>All</Tab>
                  </TabList>
                  <TabPanels mt={4}>
                    <TabPanel p={0}>
                      {props.gradient ? (
                        <HuesPickerControl
                          name={props.name}
                          themeColors={themeColors}
                          enableHues
                          setValue={setValue}
                          gradient={true}
                          index={props.index}
                          updateGradient={props.updateGradient}
                        />
                      ) : (
                        <HuesPickerControl
                          name={props.name}
                          themeColors={themeColors}
                          enableHues
                          setValue={setValue}
                          gradient={props.gradient}
                        />
                      )}
                    </TabPanel>

                    <TabPanel p={0}>
                      <Box position="relative" height="150px">
                        <ColorPicker
                          color={props.gradient ? props.gradientColor : value}
                          onChange={(color: any) => {
                            props.gradient
                              ? props.updateGradient!(
                                  `#${color.hex}`,
                                  props.index!,
                                )
                              : setValue(props.name, `#${color.hex}`)
                          }}
                        />
                        );
                      </Box>
                    </TabPanel>
                  </TabPanels>
                </Tabs>
              ) : props.gradient ? (
                <HuesPickerControl
                  name={props.name}
                  themeColors={themeColors}
                  enableHues
                  setValue={setValue}
                  gradient={true}
                  index={props.index}
                  updateGradient={props.updateGradient}
                />
              ) : (
                <HuesPickerControl
                  name={props.name}
                  themeColors={themeColors}
                  enableHues={false}
                  setValue={setValue}
                  gradient={props.gradient}
                />
              )}
            </PopoverBody>
          </PopoverContent>
        </Portal>
      </Popover>
      {props.gradient ? (
        <Input
          width="100px"
          size="sm"
          name={props.name}
          onChange={(e: ChangeEvent<HTMLInputElement>) => {
            props.gradient
              ? props.updateGradient!(e.target.value, props.index!)
              : setValue(props.name, e.target.value)
          }}
          value={props.gradientColor}
        />
      ) : (
        <Input
          width="100px"
          size="sm"
          name={props.name}
          onChange={setValueFromEvent}
          value={value}
        />
      )}
    </>
  )
}
Example #11
Source File: TextPanel.tsx    From openchakra with MIT License 4 votes vote down vote up
TextPanel = () => {
  const { setValue, setValueFromEvent } = useForm()
  const theme = useTheme()

  const fontWeight = usePropsSelector('fontWeight')
  const fontStyle = usePropsSelector('fontStyle')
  const textAlign = usePropsSelector('textAlign')
  const fontSize = usePropsSelector('fontSize')
  const letterSpacing = usePropsSelector('letterSpacing')
  const lineHeight = usePropsSelector('lineHeight')

  return (
    <>
      <FormControl label="Style">
        <IconButton
          mr={1}
          aria-label="bold"
          icon={<GoBold />}
          onClick={() => {
            setValue('fontWeight', fontWeight ? null : 'bold')
          }}
          size="xs"
          colorScheme={fontWeight ? 'whatsapp' : 'gray'}
          variant={fontWeight ? 'solid' : 'outline'}
        >
          Bold
        </IconButton>
        <IconButton
          aria-label="italic"
          icon={<GoItalic />}
          onClick={() => {
            setValue('fontStyle', fontStyle === 'italic' ? null : 'italic')
          }}
          size="xs"
          colorScheme={fontStyle === 'italic' ? 'whatsapp' : 'gray'}
          variant={fontStyle === 'italic' ? 'solid' : 'outline'}
        >
          Italic
        </IconButton>
      </FormControl>

      <FormControl label="Text align">
        <ButtonGroup size="xs" isAttached>
          <IconButton
            aria-label="bold"
            icon={<MdFormatAlignLeft />}
            onClick={() => {
              setValue('textAlign', 'left')
            }}
            colorScheme={textAlign === 'left' ? 'whatsapp' : 'gray'}
            variant={textAlign === 'left' ? 'solid' : 'outline'}
          />

          <IconButton
            aria-label="italic"
            icon={<MdFormatAlignCenter />}
            onClick={() => {
              setValue('textAlign', 'center')
            }}
            colorScheme={textAlign === 'center' ? 'whatsapp' : 'gray'}
            variant={textAlign === 'center' ? 'solid' : 'outline'}
          />

          <IconButton
            aria-label="italic"
            icon={<MdFormatAlignRight />}
            onClick={() => {
              setValue('textAlign', 'right')
            }}
            colorScheme={textAlign === 'right' ? 'whatsapp' : 'gray'}
            variant={textAlign === 'right' ? 'solid' : 'outline'}
          />

          <IconButton
            aria-label="italic"
            icon={<MdFormatAlignJustify />}
            onClick={() => {
              setValue('textAlign', 'justify')
            }}
            colorScheme={textAlign === 'justify' ? 'whatsapp' : 'gray'}
            variant={textAlign === 'justify' ? 'solid' : 'outline'}
          />
        </ButtonGroup>
      </FormControl>

      <FormControl label="Font size" htmlFor="fontSize">
        <InputSuggestion
          value={fontSize}
          handleChange={setValueFromEvent}
          name="fontSize"
        >
          {Object.keys(theme.fontSizes).map(option => (
            <ComboboxOption key={option} value={option} />
          ))}
        </InputSuggestion>
      </FormControl>

      <ColorsControl withFullColor enableHues name="color" label="Color" />

      <FormControl label="Line height" htmlFor="lineHeight">
        <InputSuggestion
          value={lineHeight}
          handleChange={setValueFromEvent}
          name="lineHeight"
        >
          {Object.keys(theme.lineHeights).map(option => (
            <ComboboxOption key={option} value={option} />
          ))}
        </InputSuggestion>
      </FormControl>

      <FormControl label="Letter spacing" htmlFor="letterSpacing">
        <InputSuggestion
          value={letterSpacing}
          handleChange={setValueFromEvent}
          name="letterSpacing"
        >
          {Object.keys(theme.letterSpacings).map(option => (
            <ComboboxOption key={option} value={option} />
          ))}
        </InputSuggestion>
      </FormControl>
    </>
  )
}
Example #12
Source File: Post.tsx    From coindrop with GNU General Public License v3.0 4 votes vote down vote up
Post: FunctionComponent<PostTypePostHydrate> = ({
        author,
        datePublished,
        dateModified,
        title,
        description,
        images,
        slug,
        content,
    }) => {
    const { avatar: authorAvatar, handle: authorHandle, url: authorUrl } = authors[author];
    const theme = useTheme();
    return (
        <>
            <NextSeo
                title={`${title} | Coindrop blog`}
                description={description}
            />
            <ArticleJsonLd
                url={`https://coindrop.to/blog/${slug}`}
                title={title}
                images={images}
                datePublished={datePublished}
                dateModified={dateModified}
                authorName={author}
                publisherName="Coindrop"
                publisherLogo="https://coindrop.to/piggy-256.png" // TODO: change this to a valid AMP logo size https://developers.google.com/search/docs/data-types/article
                description={description}
            />
            <article>
                <Heading
                    as="h1"
                    size="2xl"
                    textAlign="center"
                    my={6}
                >
                    {title}
                </Heading>
                <Flex
                    direction={["column", null, "row"]}
                    align={["initial", null, "center"]}
                    alignItems="center"
                    justify="center"
                    mb={4}
                >
                    <Text
                        id="publish-date"
                        mb={[4, null, "auto"]}
                        mt={[0, null, "auto"]}
                        textAlign="center"
                        mr={[null, null, 12]}
                    >
                        {`${dayjs(datePublished).format('dddd, MMMM D, YYYY')} (${dayjs(datePublished).fromNow()})`}
                    </Text>
                    <Flex
                        id="author"
                        align="center"
                        justify="center"
                    >
                        <Avatar name={author} src={authorAvatar} size="sm" />
                            <Flex direction="column" ml={1} align="flex-start">
                                <Text fontSize="sm">
                                    <Text>
                                        {author}
                                    </Text>
                                    <Link href={authorUrl} isExternal>
                                        {authorHandle}
                                    </Link>
                                </Text>
                            </Flex>
                    </Flex>
                </Flex>
                <hr />
                <Container
                    mt={8}
                    maxW={theme.breakpoints.md}
                >
                    {content}
                    {dateModified && (
                        <>
                        <Text
                            id="modified-date"
                            fontSize="sm"
                            textAlign="center"
                        >
                            {`Last updated ${dayjs(dateModified).format('dddd, MMMM D, YYYY')} (${dayjs(dateModified).fromNow()})`}
                        </Text>
                        <Text
                            fontSize="xs"
                            textAlign="center"
                        >
                            <Link
                                href={`https://github.com/remjx/coindrop/commits/master/blog/posts/${slug}/index.mdx`}
                            >
                                <u>View edits</u>
                            </Link>
                        </Text>
                        </>
                    )}
                </Container>
            </article>
        </>
    );
}
Example #13
Source File: CompetitorComparisonTable.tsx    From coindrop with GNU General Public License v3.0 4 votes vote down vote up
CompetitorComparisonTable: FunctionComponent = () => {
    const theme = useTheme();
    const green = useColorModeValue(theme.colors.green['500'], theme.colors.green['300']);
    const red = useColorModeValue(theme.colors.red['500'], theme.colors.red['300']);
    const orange = useColorModeValue(theme.colors.orange['500'], theme.colors.orange['300']);
    const logoOutlineColor = useColorModeValue(theme.colors.gray['800'], theme.colors.gray['900']);
    const StyledTd: FunctionComponent<{ value: string | number }> = ({ value }) => {
        let backgroundColor;
        switch (value) {
            case 'Unlimited':
            case 'Any':
            case 'None':
            case 'Freemium':
            case 'Yes':
                backgroundColor = green;
                break;
            case '$9/mo':
                backgroundColor = orange;
                break;
            default:
                backgroundColor = red;
        }
        return (
            <td style={{backgroundColor, color: "white"}}>
                {value}
            </td>
        );
    };
    return (
        <Box>
            <Flex
                id="full-width-comparison-table"
                justify="center"
                textAlign="center"
                display={['none', 'none', 'flex']}
            >
                <table className={styles.comparisontable}>
                    <thead>
                        <tr>
                            <th> </th>
                            <th>
                                <Flex align="center">
                                    {coindropData.displayName}
                                    <PiggyLogoIcon ml={1} size="19px" color={logoOutlineColor} />
                                </Flex>
                            </th>
                            {competitorData.map(obj => (
                                <th key={obj.id}>
                                    {obj.displayName}
                                    {obj.icon}
                                </th>
                            ))}
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>Fees</td>
                            {data.map(obj => (
                                <StyledTd value={obj.fees} key={obj.id} />
                            ))}
                        </tr>
                        <tr>
                            <td>Payment methods</td>
                            {data.map(obj => (
                                <StyledTd value={obj.paymentMethods} key={obj.id} />
                            ))}
                        </tr>
                        <tr>
                            <td># Pages per account</td>
                            {data.map(obj => (
                                <StyledTd value={obj.numPagesPerAccount} key={obj.id} />
                            ))}
                        </tr>
                        <tr>
                            <td>Open-source</td>
                            {data.map(obj => (
                                <StyledTd value={obj.isOpenSource} key={obj.id} />
                            ))}
                        </tr>
                        <tr>
                            <td>Subscribers</td>
                            {data.map(obj => (
                                <StyledTd value={obj.subscriberFeatures} key={obj.id} />
                            ))}
                        </tr>
                    </tbody>
                </table>
            </Flex>

            {competitorData.map(obj => (
                <Flex
                    key={obj.id}
                    id={`partial-width-comparison-table-${obj.id}`}
                    justify="center"
                    textAlign="center"
                    display={['flex', 'flex', 'none']}
                >
                    <table className={styles.comparisontable}>
                        <thead>
                            <tr>
                                <th> </th>
                                <th>
                                    <Flex align="center">
                                        Coindrop
                                        <PiggyLogoIcon ml={1} size="19px" color={logoOutlineColor} />
                                    </Flex>
                                </th>
                                <th>
                                    {obj.displayName}
                                    {obj.icon}
                                </th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td>Fees</td>
                                <StyledTd value={coindropData.fees} />
                                <StyledTd value={obj.fees} />
                            </tr>
                            <tr>
                                <td>Payment methods</td>
                                <StyledTd value={coindropData.paymentMethods} />
                                <StyledTd value={obj.paymentMethods} />
                            </tr>
                            <tr>
                                <td># Pages per account</td>
                                <StyledTd value={coindropData.numPagesPerAccount} />
                                <StyledTd value={obj.numPagesPerAccount} />
                            </tr>
                            <tr>
                                <td>Open-source</td>
                                <StyledTd value={coindropData.isOpenSource} />
                                <StyledTd value={obj.isOpenSource} />
                            </tr>
                            <tr>
                                <td>Subscribers</td>
                                <StyledTd value={coindropData.subscriberFeatures} />
                                <StyledTd value={obj.subscriberFeatures} />
                            </tr>
                        </tbody>
                    </table>
                </Flex>
            ))}
        </Box>
    );
}
Example #14
Source File: EditPiggybankModal.tsx    From coindrop with GNU General Public License v3.0 4 votes vote down vote up
EditPiggybankModal: FunctionComponent<Props> = ({ isOpen, onClose }) => {
    const [isSubmitting, setIsSubmitting] = useState(false);
    const { colors } = useTheme();
    const { user } = useUser();
    const { colorMode } = useColorMode();
    const accentColorLevelInitial = getAccentColorLevelInitial(colorMode);
    const accentColorLevelHover = getAccentColorLevelHover(colorMode);
    const { push: routerPush, query: { piggybankName } } = useRouter();
    const initialPiggybankId = Array.isArray(piggybankName) ? piggybankName[0] : piggybankName;
    const { piggybankDbData } = useContext(PublicPiggybankDataContext);
    const { avatar_storage_id: currentAvatarStorageId } = piggybankDbData;
    const initialPaymentMethodsDataFieldArray = convertPaymentMethodsDataToFieldArray(piggybankDbData.paymentMethods);
    const initialAccentColor = piggybankDbData.accentColor ?? 'orange';
    const {
        register,
        handleSubmit,
        setValue,
        watch,
        control,
        formState: { isDirty },
    } = useForm({
        defaultValues: {
            piggybankId: initialPiggybankId,
            accentColor: initialAccentColor,
            website: piggybankDbData.website ?? '',
            name: piggybankDbData.name ?? '',
            verb: piggybankDbData.verb ?? 'pay',
            paymentMethods: sortByIsPreferredThenAlphabetical(initialPaymentMethodsDataFieldArray),
        },
    });
    const paymentMethodsFieldArrayName = "paymentMethods";
    const { fields, append, remove } = useFieldArray({
        control,
        name: paymentMethodsFieldArrayName,
    });
    const {
        accentColor: watchedAccentColor,
        piggybankId: watchedPiggybankId,
    } = watch(["accentColor", "piggybankId"]);
    const isAccentColorDirty = initialAccentColor !== watchedAccentColor;
    const isUrlUnchanged = initialPiggybankId === watchedPiggybankId;
    const { isPiggybankIdAvailable, setIsAddressTouched } = useContext(AdditionalValidation);
    const onSubmit = async (formData) => {
        try {
            setIsSubmitting(true);
            const dataToSubmit = {
                ...formData,
                paymentMethods: convertPaymentMethodsFieldArrayToDbMap(formData.paymentMethods ?? []),
                owner_uid: user.id,
                avatar_storage_id: currentAvatarStorageId ?? null,
            };
            if (isUrlUnchanged) {
                await db.collection('piggybanks').doc(initialPiggybankId).set(dataToSubmit);
                mutate(['publicPiggybankData', initialPiggybankId], dataToSubmit);
            } else {
                await axios.post(
                    '/api/createPiggybank',
                    {
                        oldPiggybankName: initialPiggybankId,
                        newPiggybankName: formData.piggybankId,
                        piggybankData: dataToSubmit,
                    },
                    {
                        headers: {
                            token: user.token,
                        },
                    },
                );
                try {
                    await db.collection('piggybanks').doc(initialPiggybankId).delete();
                } catch (err) {
                    console.log('error deleting old Coindrop page');
                }
                routerPush(`/${formData.piggybankId}`);
            }
            fetch(`/${initialPiggybankId}`, { headers: { isToForceStaticRegeneration: "true" }});
            onClose();
        } catch (error) {
            setIsSubmitting(false);
            // TODO: handle errors
            throw error;
        }
    };
    const handleAccentColorChange = (e) => {
        e.preventDefault();
        setValue("accentColor", e.target.dataset.colorname);
    };
    useEffect(() => {
        register("accentColor");
    }, [register]);
    const formControlTopMargin = 2;
    return (
        <Modal
            isOpen={isOpen}
            onClose={onClose}
            size="xl"
            closeOnOverlayClick={false}
        >
            <ModalOverlay />
            <ModalContent>
                <ModalHeader>Configure</ModalHeader>
                <ModalCloseButton />
                <form id="configure-coindrop-form" onSubmit={handleSubmit(onSubmit)}>
                    <ModalBody>
                        <AvatarInput />
                        <FormControl
                            isRequired
                            mt={formControlTopMargin}
                        >
                            <FormLabel htmlFor="input-piggybankId">URL</FormLabel>
                            <EditUrlInput
                                register={register}
                                value={watchedPiggybankId}
                            />
                        </FormControl>
                        <FormControl
                            mt={formControlTopMargin}
                        >
                            <FormLabel
                                htmlFor="input-accentColor"
                            >
                                Theme
                            </FormLabel>
                            <Flex wrap="wrap" justify="center">
                                {themeColorOptions.map(colorName => {
                                    const isColorSelected = watchedAccentColor === colorName;
                                    const accentColorInitial = colors[colorName][accentColorLevelInitial];
                                    const accentColorHover = colors[colorName][accentColorLevelHover];
                                    return (
                                    <Box
                                        key={colorName}
                                        as="button"
                                        bg={isColorSelected ? accentColorHover : accentColorInitial}
                                        _hover={{
                                            bg: accentColorHover,
                                        }}
                                        w="36px"
                                        h="36px"
                                        borderRadius="50%"
                                        mx={1}
                                        my={1}
                                        onClick={handleAccentColorChange}
                                        data-colorname={colorName}
                                    >
                                        {isColorSelected && (
                                            <CheckIcon color="#FFF" />
                                        )}
                                    </Box>
                                    );
                                })}
                            </Flex>
                        </FormControl>
                        <FormControl
                            isRequired
                            mt={formControlTopMargin}
                        >
                            <FormLabel
                                htmlFor="input-name"
                            >
                                Name
                            </FormLabel>
                            <Input
                                id="input-name"
                                name="name"
                                ref={register}
                            />
                        </FormControl>
                        <FormControl
                            isRequired
                            mt={formControlTopMargin}
                        >
                            <FormLabel
                                htmlFor="input-verb"
                            >
                                Payment action name
                            </FormLabel>
                            <Select
                                id="input-verb"
                                name="verb"
                                ref={register}
                            >
                                <option value="pay">Pay</option>
                                <option value="donate to">Donate to</option>
                                <option value="support">Support</option>
                                <option value="tip">Tip</option>
                            </Select>
                        </FormControl>
                        <FormControl
                            mt={formControlTopMargin}
                        >
                            <FormLabel
                                htmlFor="input-website"
                            >
                                Website
                            </FormLabel>
                            <Input
                                id="input-website"
                                name="website"
                                ref={register}
                                placeholder="http://"
                                type="url"
                            />
                        </FormControl>
                        <FormControl
                            mt={formControlTopMargin}
                        >
                            <FormLabel
                                htmlFor="input-paymentmethods"
                            >
                                Payment Methods
                            </FormLabel>
                            <PaymentMethodsInput
                                fields={fields}
                                control={control}
                                register={register}
                                remove={remove}
                                append={append}
                                fieldArrayName={paymentMethodsFieldArrayName}
                            />
                        </FormControl>
                    </ModalBody>
                    <Flex
                        id="modal-footer"
                        justify="space-between"
                        m={6}
                    >
                        <DeleteButton
                            piggybankName={initialPiggybankId}
                        />
                        <Flex>
                            <Button
                                variant="ghost"
                                onClick={onClose}
                            >
                                Cancel
                            </Button>
                            <Button
                                id="save-configuration-btn"
                                colorScheme="green"
                                mx={1}
                                type="submit"
                                isLoading={isSubmitting}
                                loadingText="Saving"
                                isDisabled={
                                    (
                                        !isDirty
                                        && !isAccentColorDirty // controlled accentColor field is not showing up in formState.dirtyFields
                                    )
                                    || !isPiggybankIdAvailable
                                    || !initialPiggybankId
                                }
                                onClick={() => setIsAddressTouched(true)}
                            >
                                Save
                            </Button>
                        </Flex>
                    </Flex>
                </form>
            </ModalContent>
        </Modal>
    );
}
Example #15
Source File: PaymentMethodsInput.tsx    From coindrop with GNU General Public License v3.0 4 votes vote down vote up
PaymentMethodsInput: FC<Props> = ({ fieldArrayName, fields, control, register, remove, append }) => {
    const { colors } = useTheme();
    const paymentMethodsDataWatch: PaymentMethod[] = useWatch({
        control,
        name: fieldArrayName,
    });
    const [openAccordionItemIndex, setOpenAccordionItemIndex] = useState(-1);
    useEffect(() => {
        if (
            paymentMethodsDataWatch[paymentMethodsDataWatch.length - 1]?.paymentMethodId === "default-blank"
            || paymentMethodsDataWatch[paymentMethodsDataWatch.length - 1]?.address === ""
        ) {
            setOpenAccordionItemIndex(paymentMethodsDataWatch.length - 1);
        }
    }, [paymentMethodsDataWatch]);
    const containsInvalidAddress = paymentMethodsDataWatch.some(paymentMethod => !paymentMethod.address);
    const { isAddressTouched, setIsAddressTouched } = useContext(AdditionalValidation);
    // optgroup not compatible with Chakra dark mode: https://github.com/chakra-ui/chakra-ui/issues/2853
        // const optionsGroup = (category: Category) => {
        //     const optgroupLabels: Record<Category, string> = {
        //         "digital-wallet": 'Digital Wallets',
        //         "digital-asset": "Digital Assets",
        //         "subscription-platform": "Subscription Platforms",
        //     };
        //     return (
        //         <optgroup label={optgroupLabels[category]}>
        //             {paymentMethods
        //             .filter(paymentMethod => paymentMethod.category === category)
        //             .sort((a, b) => (a.id < b.id ? -1 : 1))
        //             .map(({ id, displayName }) => (
        //                 <option
        //                     key={id}
        //                     value={id}
        //                     style={{display: paymentMethodsDataWatch.map(paymentMethodDataWatch => paymentMethodDataWatch.paymentMethodId).includes(id) ? "none" : undefined }}
        //                 >
        //                     {displayName}
        //                 </option>
        //             ))}
        //         </optgroup>
        //     );
        // };
    return (
        <>
        {fields.length < 1
            ? 'No payment methods defined yet.'
        : (
            <Accordion
                allowToggle
                defaultIndex={-1}
                index={openAccordionItemIndex}
            >
                {
                fields
                    .map((item, index) => {
                    const watchedData = paymentMethodsDataWatch.find(watchedPaymentMethod => watchedPaymentMethod.id === item.id);
                    const PaymentMethodIcon = paymentMethodIcons[watchedData?.paymentMethodId];
                    return (
                        <AccordionItem
                            key={item.id}
                        >
                            <AccordionButton
                                onClick={() => {
                                    setIsAddressTouched(true);
                                    if (openAccordionItemIndex !== index && !paymentMethodsDataWatch.find(paymentMethod => paymentMethod.address === "")) {
                                        setOpenAccordionItemIndex(index);
                                    } else {
                                        setOpenAccordionItemIndex(undefined);
                                    }
                                }}
                            >
                                <Flex flex="1" textAlign="left" align="center">
                                    <Flex mr={1} align="center">
                                        {PaymentMethodIcon ? <PaymentMethodIcon mr={2} /> : <QuestionOutlineIcon mr={2} />}
                                        {paymentMethodNames[watchedData?.paymentMethodId] ?? 'Add payment method'}
                                    </Flex>
                                    {watchedData?.isPreferred && (
                                        <Flex>
                                            <StarIcon
                                                ml={2}
                                                size="16px"
                                                color={colors.yellow['400']}
                                            />
                                            <Text
                                                as="span"
                                                fontSize="xs"
                                                ml={1}
                                            >
                                                <i>Preferred</i>
                                            </Text>
                                        </Flex>
                                    )}
                                </Flex>
                                <AccordionIcon />
                            </AccordionButton>
                            <AccordionPanel pb={4} id={`accordion-panel-${watchedData.paymentMethodId}`}>
                                <input
                                    ref={register()}
                                    name={`${fieldArrayName}[${index}].id`}
                                    defaultValue={item.id}
                                    style={{display: 'none'}}
                                />
                                <Box
                                    display={paymentMethodNames[watchedData?.paymentMethodId] ? "none" : "block"}
                                    data-cy={`select-payment-method-container-${watchedData.paymentMethodId}`}
                                >
                                    <Select
                                        name={`${fieldArrayName}[${index}].paymentMethodId`}
                                        ref={register()}
                                        defaultValue={paymentMethodNames[item.paymentMethodId] ? item.paymentMethodId : 'default-blank'}
                                        isInvalid={containsInvalidAddress && isAddressTouched}
                                        onChange={() => setIsAddressTouched(false)}
                                    >
                                        <option hidden disabled value="default-blank">Select...</option>
                                        {/* optgroup not compatible with Chakra dark mode: https://github.com/chakra-ui/chakra-ui/issues/2853 */}
                                        {Object.entries(paymentMethodNames)
                                            .sort((a, b) => {
                                                const [aId] = a;
                                                const [bId] = b;
                                                return aId < bId ? -1 : 1;
                                            })
                                            .map(([paymentMethodId, paymentMethodDisplayName]) => (
                                                <option
                                                    key={paymentMethodId}
                                                    value={paymentMethodId}
                                                    style={{display: paymentMethodsDataWatch.map(paymentMethod => paymentMethod.paymentMethodId).includes(paymentMethodId) ? "none" : undefined }}
                                                >
                                                    {paymentMethodDisplayName}
                                                </option>
                                            ))}
                                    </Select>
                                </Box>
                                <Box
                                    mx={3}
                                    display={paymentMethodNames[watchedData?.paymentMethodId] ? "block" : "none"}
                                >
                                    <FormControl isRequired>
                                        <FormLabel htmlFor={`${fieldArrayName}[${index}].address`}>Address</FormLabel>
                                        <Input
                                            id={`address-input-${watchedData.paymentMethodId}`}
                                            name={`${fieldArrayName}[${index}].address`}
                                            ref={register()}
                                            defaultValue={item.address}
                                            isInvalid={containsInvalidAddress && isAddressTouched}
                                        />
                                        <Box>
                                            <Checkbox
                                                name={`${fieldArrayName}[${index}].isPreferred`}
                                                ref={register()}
                                                defaultValue={item?.isPreferred ? 1 : 0}
                                                defaultIsChecked={item?.isPreferred}
                                                mt={1}
                                                colorScheme="yellow"
                                            >
                                                Preferred
                                            </Checkbox>
                                        </Box>
                                    </FormControl>
                                </Box>
                                <Flex
                                    justify={watchedData?.paymentMethodId === 'default-blank' ? 'space-between' : 'flex-end'}
                                    mt={3}
                                    wrap="wrap"
                                    align="center"
                                >
                                    {watchedData?.paymentMethodId === 'default-blank' && (
                                        <Text fontSize="xs" ml={1}>
                                            <Link href="/blog/payment-method-request" isExternal>
                                                Payment method not listed?
                                            </Link>
                                        </Text>
                                    )}
                                    <Button
                                        onClick={() => {
                                            remove(index);
                                            setIsAddressTouched(false);
                                        }}
                                        leftIcon={<MinusIcon />}
                                        size="sm"
                                    >
                                        {'Remove '}
                                        {/* {paymentMethodNames[watchedData?.paymentMethodId]} */}
                                    </Button>
                                </Flex>
                            </AccordionPanel>
                        </AccordionItem>
                    );
                })
}
            </Accordion>
        )}
        <Flex
            justify="center"
            mt={2}
        >
            <Button
                id="add-payment-method-button"
                onClick={() => {
                    append({});
                    setIsAddressTouched(false);
                }}
                leftIcon={<AddIcon />}
                variant="ghost"
                size="sm"
                isDisabled={
                    (
                        fields.length > 0
                        && !paymentMethodNames[paymentMethodsDataWatch[paymentMethodsDataWatch.length - 1]?.paymentMethodId]
                    )
                    || containsInvalidAddress
                }
            >
                Add payment method
            </Button>
        </Flex>
        </>
    );
}
Example #16
Source File: PublicPiggybankPage.tsx    From coindrop with GNU General Public License v3.0 4 votes vote down vote up
PublicPiggybankPage: FunctionComponent = () => {
    const { query: { piggybankName } } = useRouter();
    const { piggybankDbData } = useContext(PublicPiggybankDataContext);
    const theme = useTheme();
    const { user } = useUser();
    const { colorMode } = useColorMode();
    const accentColorLevelInitial = getAccentColorLevelInitial(colorMode);
    const accentColorLevelHover = getAccentColorLevelHover(colorMode);
    const {
        name,
        website,
        accentColor = "orange",
        verb,
        owner_uid,
    } = piggybankDbData;
    const accentColorInitial = theme.colors[accentColor][accentColorLevelInitial];
    const accentColorHover = theme.colors[accentColor][accentColorLevelHover];
    const pagePaymentMethodsDataEntries = Object.entries(piggybankDbData.paymentMethods ?? {});
    const preferredAddresses = pagePaymentMethodsDataEntries.filter(([, paymentMethodData]: any) => paymentMethodData.isPreferred);
    const otherAddresses = pagePaymentMethodsDataEntries.filter(([, paymentMethodData]: any) => !paymentMethodData.isPreferred);
    const WrapGroup: FunctionComponent = ({ children }) => (
        <Wrap
            justify="center"
        >
            {children}
        </Wrap>
    );

    type PaymentMethodButtonsFromEntriesProps = {
        entries: PaymentMethodDbObjEntry[]
    }
    const PaymentMethodButtonsFromEntries: FunctionComponent<PaymentMethodButtonsFromEntriesProps> = ({ entries }) => (
        <WrapGroup>
            {entries
            .sort(sortArrayByEntriesKeyAlphabetical)
            .map(([paymentMethodId, paymentMethodData]) => (
                <WrapItem key={paymentMethodId}>
                    <PaymentMethodButton
                        key={paymentMethodId}
                        paymentMethod={paymentMethodId}
                        paymentMethodValue={paymentMethodData.address}
                        isPreferred={paymentMethodData.isPreferred}
                        accentColor={accentColor}
                    />
                </WrapItem>
            ))}
        </WrapGroup>
    );
    const piggybankExists = !!owner_uid;
    const initialSetupComplete = name && accentColor && verb && pagePaymentMethodsDataEntries.length > 0;
    return (
        <>
        <NextSeo
            title={`${name ?? piggybankName}'s Coindrop (coindrop.to/${piggybankName})`}
            description={`Send money to ${name} with no fees`}
        />
        <Container
            maxW={theme.breakpoints.lg}
            mx="auto"
        >
            {user?.id
            && user.id === owner_uid
            && (
                <>
                <DataRefetcher />
                <ManagePiggybankBar
                    editButtonOptions={
                        initialSetupComplete
                        ? ({
                            text: 'Configure',
                            color: undefined,
                            icon: <SettingsIcon />,
                        }) : ({
                            text: 'Set up',
                            color: 'green',
                            icon: <SettingsIcon />,
                        })
                    }
                    initialSetupComplete={initialSetupComplete}
                />
                </>
            )}
            {initialSetupComplete ? (
                <Box
                    mb={6}
                >
                    <Box
                        padding="10px"
                        my={2}
                        mx={3}
                    >
                        <Center>
                            <Avatar />
                        </Center>
                        <Heading textAlign="center">
                            Choose a payment method to
                            {` ${verb} `}
                            {website ? (
                                <Link href={website} target="_blank" rel="noreferrer">
                                    <Heading
                                        as="span"
                                        color={accentColorInitial}
                                        textDecoration="underline"
                                        css={css`
                                            &:hover {
                                                color: ${accentColorHover};
                                            }
                                        `}
                                    >
                                            {name}
                                    </Heading>
                                </Link>
                            ) : (
                                <Heading
                                    as="span"
                                    color={accentColorInitial}
                                >
                                        {name}
                                </Heading>
                            )}
                            :
                        </Heading>
                    </Box>
                    <PaymentMethodButtonsFromEntries
                        entries={preferredAddresses}
                    />
                    <PaymentMethodButtonsFromEntries
                        entries={otherAddresses}
                    />
                    <PoweredByCoindropLink />
                </Box>
            ) : (
                <Heading mt={4} textAlign="center">
                    {piggybankExists ? 'This Coindrop has not been set up yet.' : 'This Coindrop does not exist'}
                    {/* TODO: Include action buttons to log in or landing page */}
                </Heading>
            )}
        </Container>
        </>
    );
}