react-query#useQueryClient JavaScript Examples

The following examples show how to use react-query#useQueryClient. 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: use-mutation.js    From awesome-react-starter with MIT License 7 votes vote down vote up
useMutation = (fn, options = {}) => {
  const {
    successCallback,
    errorCallback,
    redirectOnSuccess,
    invalidateQueries,
    ...rest // pass your own options
  } = options;

  const router = useRouter();
  const queryClient = useQueryClient();
  const mutation = useQueryMutation(fn, {
    onSuccess: (data) => {
      if (invalidateQueries) {
        queryClient.invalidateQueries(invalidateQueries);
      }
      if (data?.message) {
        toaster.success(data?.message);
      }
      if (redirectOnSuccess) {
        router.push(redirectOnSuccess);
      }
      if (typeof successCallback === 'function') {
        successCallback();
      }
    },
    onError: (err) => {
      if (err?.message) {
        toaster.error(err?.message);
      }
      if (typeof errorCallback === 'function') {
        errorCallback();
      }
    },
    ...rest,
  });

  return mutation;
}
Example #2
Source File: List.jsx    From sitepoint-books-firebase with MIT License 7 votes vote down vote up
function ScreenAuthorList() {
  const { data, isLoading, error, status } = useQuery(
    'authors',
    AuthorService.getAll
  )

  const queryClient = useQueryClient()

  const deleteMutation = useMutation((id) => AuthorService.remove(id), {
    onSuccess: () => {
      queryClient.invalidateQueries('authors')
    },
  })

  const deleteAction = async (id) => {
    deleteMutation.mutateAsync(id)
  }

  return (
    <>
      <PageHeading title="Author List" />
      <div className="mt-12">
        {error && <Alert type="error" message={error.message} />}
        {isLoading && (
          <Alert
            type="info"
            message="Loading..."
            innerClass="animate animate-pulse"
          />
        )}
        {status === 'success' && (
          <AuthorList data={data} deleteAction={deleteAction} />
        )}
      </div>
    </>
  )
}
Example #3
Source File: CreateActivationKeyButton.js    From sed-frontend with Apache License 2.0 7 votes vote down vote up
CreateActivationKeyButton = (props) => {
  const { onClick } = props;
  const queryClient = useQueryClient();
  const user = queryClient.getQueryData('user');
  const isDisabled = () => {
    return !user.rbacPermissions.canWriteActivationKeys;
  };
  return (
    <Button variant="primary" onClick={onClick} isDisabled={isDisabled()}>
      Create activation key
    </Button>
  );
}
Example #4
Source File: Authentication.js    From sed-frontend with Apache License 2.0 7 votes vote down vote up
Authentication = ({ children }) => {
  const queryClient = useQueryClient();
  const location = useLocation();

  const { isLoading, isFetching, isSuccess, isError } = useUser();

  useEffect(() => {
    isSuccess && window.insights?.chrome?.hideGlobalFilter();
  }, [isSuccess]);

  useEffect(() => {
    /**
     * On every rerender, based on URL change (location.pathname),
     * reset the user's status to loading before authenticating again.
     */
    queryClient.invalidateQueries('user');
  }, [location.pathname]);

  if (isError === true) {
    return <Unavailable />;
  } else if (isLoading === true || isFetching === true) {
    return <Loading />;
  } else if (isSuccess === true) {
    return <>{children}</>;
  }
}
Example #5
Source File: List.jsx    From sitepoint-books-firebase with MIT License 6 votes vote down vote up
function ScreenBookList() {
  const { data, isLoading, error, status } = useQuery(
    'books',
    BookService.getAll
  )

  const queryClient = useQueryClient()

  const deleteMutation = useMutation((id) => BookService.remove(id), {
    onSuccess: () => {
      queryClient.invalidateQueries('books')
    },
  })

  const deleteAction = async (id) => {
    deleteMutation.mutateAsync(id)
  }

  return (
    <>
      <PageHeading title="Book List" />
      <div className="mt-12">
        {error && <Alert type="error" message={error.message} />}
        {isLoading && (
          <Alert
            type="info"
            message="Loading..."
            innerClass="animate animate-pulse"
          />
        )}
        {status === 'success' && (
          <BookList data={data} deleteAction={deleteAction} />
        )}
      </div>
    </>
  )
}
Example #6
Source File: BookItem.jsx    From react-query-3 with GNU General Public License v3.0 6 votes vote down vote up
BookItem = ({id, title, author }) => {
  const queryClient = useQueryClient()
  const { mutateAsync, isLoading } = useMutation(removeBook)

  const remove = async () => {
    await mutateAsync(id)
    queryClient.invalidateQueries('books')
  }

  return (
    <Flex key={id} p={3} width="100%" alignItems="center">
      <StyledLink as={RouterLink} to={`/update-book/${id}`} mr="auto">{title}</StyledLink>
      <Text>{author}</Text>
      <Button onClick={remove} ml="5">
        { isLoading ? <Loader type="ThreeDots" color="#fff" height={10} /> : "Remove" }
      </Button>
    </Flex>
  );
}
Example #7
Source File: useTokenPrices.js    From v3-ui with MIT License 6 votes vote down vote up
useTokenPrices = (chainId, addresses) => {
  const queryClient = useQueryClient()

  const flatAddresses = addresses ? Object.values(addresses).flat() : []
  const blockNumbers = addresses ? Object.keys(addresses) : []

  const enabled = Boolean(addresses && flatAddresses?.length > 0 && chainId)

  return useQuery(
    [QUERY_KEYS.tokenPrices, chainId, flatAddresses, blockNumbers],
    async () => await getTokenPrices(chainId, addresses),
    {
      enabled,
      onSuccess: (data) => populateCaches(chainId, queryClient, data, addresses)
    }
  )
}
Example #8
Source File: usePastPrizes.js    From v3-ui with MIT License 6 votes vote down vote up
usePastPrizes = (pool, page, pageSize = PRIZE_PAGE_SIZE) => {
  const queryClient = useQueryClient()
  const chainId = pool?.chainId
  const readProvider = useReadProvider(chainId)

  const poolAddress = pool?.prizePool?.address

  const { data: prizes, ...prizeData } = useQuery(
    [QUERY_KEYS.poolPrizesQuery, chainId, poolAddress, page, pageSize],
    async () => await getPoolPrizesData(chainId, readProvider, pool?.contract, page, pageSize),
    {
      enabled: Boolean(poolAddress && !!readProvider && chainId),
      keepPreviousData: true,
      onSuccess: (data) => populateCaches(chainId, poolAddress, queryClient, data)
    }
  )

  const addresses = getErc20AddressesByBlockNumberFromPrizes(
    prizes,
    pool?.tokens?.underlyingToken?.address
  )
  const { data: tokenPrices, ...tokenPricesData } = useTokenPrices(chainId, addresses)

  const count = pool?.prize?.currentPrizeId - 1 || 0
  const pages = Math.ceil(Number(count / PRIZE_PAGE_SIZE))
  const isFetched =
    (prizeData.isFetched && tokenPricesData.isFetched) ||
    (prizeData.isFetched && Boolean(prizes?.length === 0))

  const data = formatAndCalculatePrizeValues(prizes, tokenPrices, pool?.tokens?.underlyingToken)

  return {
    data,
    isFetched,
    page,
    pages,
    count
  }
}
Example #9
Source File: epoch-context.js    From idena-web with MIT License 6 votes vote down vote up
export function EpochProvider(props) {
  const queryClient = useQueryClient()
  const {apiKey, url} = useSettingsState()

  const timing = useNodeTiming()

  const [lastModifiedEpochTime, setLastModifiedEpochTime] = useState(0)

  const {data: epochData} = useQuery(
    ['get-epoch', apiKey, url],
    () => fetchEpoch(),
    {
      retryDelay: 5 * 1000,
      initialData: null,
    }
  )

  useInterval(() => {
    if (!epochData || !timing) return

    if (shouldRefetchEpoch(epochData, timing)) {
      const currentTime = new Date().getTime()
      if (Math.abs(currentTime - lastModifiedEpochTime > 15 * 1000)) {
        queryClient.invalidateQueries('get-epoch')
        setLastModifiedEpochTime(currentTime)
      }
    }
  }, 1000)

  return <EpochContext.Provider {...props} value={epochData ?? null} />
}
Example #10
Source File: List.jsx    From sitepoint-books-firebase with MIT License 6 votes vote down vote up
function ScreenCategoryList() {
  const { data, isLoading, error, status } = useQuery(
    'books',
    CategoryService.getAll
  )

  const queryClient = useQueryClient()

  const deleteMutation = useMutation((id) => CategoryService.remove(id), {
    onSuccess: () => {
      queryClient.invalidateQueries('categories')
    },
  })

  const deleteAction = async (id) => {
    deleteMutation.mutateAsync(id)
  }

  return (
    <>
      <PageHeading title="Category List" />
      <div className="mt-12">
        {error && <Alert type="error" message={error.message} />}
        {isLoading && (
          <Alert
            type="info"
            message="Loading..."
            innerClass="animate animate-pulse"
          />
        )}
        {status === 'success' && (
          <CategoryList data={data} deleteAction={deleteAction} />
        )}
      </div>
    </>
  )
}
Example #11
Source File: ActivationKeysTable.js    From sed-frontend with Apache License 2.0 6 votes vote down vote up
ActivationKeysTable = (props) => {
  const { actions } = props;
  const columnNames = {
    name: 'Key Name',
    role: 'Role',
    serviceLevel: 'SLA',
    usage: 'Usage',
  };
  const { isLoading, error, data } = useActivationKeys();
  const queryClient = useQueryClient();
  const user = queryClient.getQueryData('user');
  const isActionsDisabled = () => {
    return !user.rbacPermissions.canWriteActivationKeys;
  };

  const Results = () => {
    return (
      <TableComposable aria-label="ActivationKeys">
        <Thead>
          <Tr ouiaSafe={true}>
            <Th width={40}>{columnNames.name}</Th>
            <Th>{columnNames.role}</Th>
            <Th>{columnNames.serviceLevel}</Th>
            <Th>{columnNames.usage}</Th>
            <Td></Td>
          </Tr>
        </Thead>
        <Tbody>
          {data.map((datum) => {
            let rowActions = actions(datum.name);
            return (
              <Tr key={datum.name} ouiaSafe={true}>
                <Td modifier="breakWord" dataLabel={columnNames.name}>
                  {datum.name}
                </Td>
                <Td dataLabel={columnNames.role}>{datum.role}</Td>
                <Td dataLabel={columnNames.serviceLevel}>
                  {datum.serviceLevel}
                </Td>
                <Td dataLabel={columnNames.usage}>{datum.usage}</Td>
                <Td isActionCell>
                  <ActionsColumn
                    items={rowActions}
                    isDisabled={isActionsDisabled()}
                    actionsToggle={customActionsToggle}
                  />
                </Td>
              </Tr>
            );
          })}
        </Tbody>
      </TableComposable>
    );
  };

  if (isLoading && !error) {
    return <Loading />;
  } else if (!isLoading && !error) {
    return <Results />;
  } else {
    return <Unavailable />;
  }
}
Example #12
Source File: CreateActivationKeyModal.js    From sed-frontend with Apache License 2.0 5 votes vote down vote up
CreateActivationKeyModal = (props) => {
  const queryClient = useQueryClient();
  const [created, setCreated] = React.useState(false);
  const [error, setError] = React.useState(false);
  const { handleModalToggle, isOpen } = props;
  const { mutate, isLoading } = useCreateActivationKey();
  const submitForm = (name, role, serviceLevel, usage) => {
    mutate(
      { name, role, serviceLevel, usage },
      {
        onSuccess: () => {
          setError(false);
          setCreated(true);
          queryClient.invalidateQueries('activation_keys');
        },
        onError: () => {
          setError(true);
          setCreated(false);
        },
      }
    );
  };
  return (
    <Modal
      variant={ModalVariant.large}
      title="Create new activation key"
      description=""
      isOpen={isOpen}
      onClose={handleModalToggle}
    >
      {isLoading ? (
        <Loading />
      ) : (
        <ActivationKeyForm
          handleModalToggle={handleModalToggle}
          submitForm={submitForm}
          isSuccess={created}
          isError={error}
        />
      )}
    </Modal>
  );
}
Example #13
Source File: DeleteActivationKeyConfirmationModal.js    From sed-frontend with Apache License 2.0 5 votes vote down vote up
DeleteActivationKeyConfirmationModal = (props) => {
  const { isOpen, handleModalToggle, name } = props;
  const { addSuccessNotification, addErrorNotification } = useNotifications();
  const { mutate, isLoading } = useDeleteActivationKey();
  const queryClient = useQueryClient();

  const deleteActivationKey = (name) => {
    mutate(name, {
      onSuccess: (_data, name) => {
        queryClient.setQueryData('activation_keys', (oldData) =>
          oldData.filter((entry) => entry.name != name)
        );
        addSuccessNotification(`Activation Key ${name} deleted`);
        handleModalToggle();
      },
      onError: () => {
        addErrorNotification('Something went wrong. Please try again');
        handleModalToggle();
      },
    });
    mutate;
  };
  const actions = [
    <Button
      key="confirm"
      variant="danger"
      onClick={() => deleteActivationKey(name)}
      data-testid="delete-activation-key-confirmation-modal-confirm-button"
    >
      Delete
    </Button>,
    <Button key="cancel" variant="link" onClick={handleModalToggle}>
      Cancel
    </Button>,
  ];

  const title = (
    <>
      <TextContent>
        <Text component={TextVariants.h2}>
          <ExclamationTriangleIcon size="md" color="#F0AB00" /> Delete
          Activation Key?
        </Text>
      </TextContent>
    </>
  );
  const content = () => {
    if (isLoading) {
      return <Loading />;
    } else {
      return (
        <TextContent>
          <Text component={TextVariants.p}>
            <b>{name}</b> will no longer be available for use. This operation
            cannot be undone.
          </Text>
        </TextContent>
      );
    }
  };

  return (
    <Modal
      title={title}
      isOpen={isOpen}
      onClose={handleModalToggle}
      variant={ModalVariant.small}
      actions={actions}
    >
      {content()}
    </Modal>
  );
}
Example #14
Source File: EditActivationKeyModal.js    From sed-frontend with Apache License 2.0 5 votes vote down vote up
EditActivationKeyModal = (props) => {
  const { activationKeyName } = props;
  const queryClient = useQueryClient();
  const [updated, setUpdated] = React.useState(false);
  const [error, setError] = React.useState(false);
  const { handleModalToggle, isOpen } = props;
  const { mutate, isLoading } = useUpdateActivationKey();
  const {
    isLoading: isKeyLoading,
    error: keyError,
    data: activationKey,
  } = useActivationKey(activationKeyName);
  const submitForm = (name, role, serviceLevel, usage) => {
    mutate(
      { activationKeyName, role, serviceLevel, usage },
      {
        onSuccess: () => {
          setError(false);
          setUpdated(true);
          queryClient.invalidateQueries('activation_keys');
          queryClient.resetQueries(`activation_key_${activationKeyName}`);
        },
        onError: () => {
          setError(true);
          setUpdated(false);
        },
      }
    );
  };
  return (
    <Modal
      variant={ModalVariant.large}
      title="Edit activation key"
      description=""
      isOpen={isOpen}
      onClose={handleModalToggle}
    >
      {(isLoading || isKeyLoading) && !keyError ? (
        <Loading />
      ) : (
        <ActivationKeyForm
          activationKey={activationKey}
          handleModalToggle={handleModalToggle}
          submitForm={submitForm}
          isSuccess={updated}
          isError={error}
        />
      )}
    </Modal>
  );
}
Example #15
Source File: Form.jsx    From sitepoint-books-firebase with MIT License 5 votes vote down vote up
function ScreenAuthorForm() {
  const { id } = useParams()
  const { data, isLoading, error, status } = useQuery(
    ['author', { id }],
    AuthorService.getOne
  )

  const queryClient = useQueryClient()

  const saveData = (data) => {
    if (id) {
      return AuthorService.update(id, data)
    } else {
      AuthorService.create(data)
    }
  }

  const mutation = useMutation((data) => saveData(data), {
    onSuccess: () => {
      if (id) queryClient.invalidateQueries(['author', { id }])
    },
  })

  const { isSuccess } = mutation

  const onSubmit = async (submittedData) => {
    mutation.mutate(submittedData)
  }

  if (isSuccess) {
    return <Redirect to="/author" />
  }

  if (!id) {
    return (
      <>
        <PageHeading title="Create Author" />
        <div className="mt-12">
          {error && <Alert type="error" message={error.message} />}
          <AuthorForm submit={onSubmit} />
        </div>
      </>
    )
  }

  return (
    <>
      <PageHeading title="Edit Author" />
      <div className="mt-12">
        {error && <Alert type="error" message={error.message} />}
        {isLoading && (
          <Alert
            type="info"
            message="Loading..."
            innerClass="animate animate-pulse"
          />
        )}
        {status === 'success' && <AuthorForm values={data} submit={onSubmit} />}
      </div>
    </>
  )
}
Example #16
Source File: Form.jsx    From sitepoint-books-firebase with MIT License 5 votes vote down vote up
function ScreenCategoryForm() {
  let { id } = useParams()
  const { data, isLoading, error, status } = useQuery(
    ['category', { id }],
    CategoryService.getOne
  )
  const queryClient = useQueryClient()

  const saveData = (data) => {
    if (id) {
      return CategoryService.update(id, data)
    } else {
      CategoryService.create(data)
    }
  }

  const mutation = useMutation((data) => saveData(data), {
    onSuccess: () => {
      if (id) queryClient.invalidateQueries(['category', { id }])
    },
  })

  const { isSuccess } = mutation

  const onSubmit = async (submittedData) => {
    mutation.mutate(submittedData)
  }

  if (isSuccess) {
    return <Redirect to="/category" />
  }

  if (!id) {
    return (
      <>
        <PageHeading title="Create Category" />
        <div className="mt-12">
          <CategoryForm values={{ cover: 'nocover' }} action={onSubmit} />
        </div>
      </>
    )
  }

  return (
    <>
      <PageHeading title="Edit Category" />
      <div className="mt-12">
        {error && <Alert type="error" message={error.message} />}
        {isLoading && (
          <Alert
            type="info"
            message="Loading..."
            innerClass="animate animate-pulse"
          />
        )}
        {status === 'success' && (
          <CategoryForm values={data} action={onSubmit} />
        )}
      </div>
    </>
  )
}
Example #17
Source File: offers.js    From idena-web with MIT License 5 votes vote down vote up
export default function AdOfferList() {
  const {t} = useTranslation()

  const queryClient = useQueryClient()

  const {data: burntCoins, status: burntCoinsStatus} = useApprovedBurntCoins()

  const isFetched = burntCoinsStatus === 'success'

  const isEmpty = isFetched && burntCoins.length === 0

  const [selectedAd, setSelectedAd] = React.useState({})

  const burnDisclosure = useDisclosure()
  const {
    onOpen: onOpenBurnDisclosure,
    onClose: onCloseBurnDisclosure,
  } = burnDisclosure

  const handlePreviewBurn = React.useCallback(
    ad => {
      setSelectedAd(ad)
      onOpenBurnDisclosure()
    },
    [onOpenBurnDisclosure]
  )

  const handleBurn = React.useCallback(() => {
    onCloseBurnDisclosure()
    queryClient.invalidateQueries(['bcn_burntCoins', []])
  }, [onCloseBurnDisclosure, queryClient])

  return (
    <Layout skipBanner>
      <Page>
        <PageHeader>
          <PageTitle mb={4}>{t('All offers')}</PageTitle>
          <PageCloseButton href="/adn/list" />
        </PageHeader>
        <Table>
          <Thead>
            <Tr>
              <RoundedTh isLeft>{t('Banner/author')}</RoundedTh>
              <RoundedTh>{t('Website')}</RoundedTh>
              <RoundedTh>{t('Target')}</RoundedTh>
              <RoundedTh>{t('Burn')}</RoundedTh>
              <RoundedTh isRight />
            </Tr>
          </Thead>
          <Tbody>
            {isFetched &&
              burntCoins.map(burn => (
                <AdOfferListItem
                  key={burn.key}
                  burn={burn}
                  onBurn={handlePreviewBurn}
                />
              ))}
          </Tbody>
        </Table>

        {isEmpty && (
          <Center color="muted" mt="4" w="full">
            {t('No active offers')}
          </Center>
        )}

        <BurnDrawer ad={selectedAd} onBurn={handleBurn} {...burnDisclosure} />
      </Page>
    </Layout>
  )
}
Example #18
Source File: hooks.js    From idena-web with MIT License 5 votes vote down vote up
export function useApprovedBurntCoins() {
  const queryClient = useQueryClient()

  const {decodeProfile, decodeAdBurnKey} = useProtoProfileDecoder()

  const {data: burntCoins, status: burntCoinsStatus} = useBurntCoins({
    select: data =>
      data?.map(({address, key, amount}) => ({
        address,
        key,
        ...decodeAdBurnKey(key),
        amount,
      })) ?? [],
    notifyOnChangeProps: 'tracked',
  })

  const rpcFetcher = useRpcFetcher()

  return useQuery({
    queryKey: ['approvedAdOffers'],
    queryFn: () =>
      Promise.all(
        burntCoins.map(async burn => {
          const identity = await queryClient.fetchQuery({
            queryKey: ['dna_identity', [burn.address]],
            queryFn: rpcFetcher,
            staleTime: 5 * 60 * 1000,
          })

          if (identity.profileHash) {
            const profile = await queryClient.fetchQuery({
              queryKey: ['ipfs_get', [identity.profileHash]],
              queryFn: rpcFetcher,
              staleTime: Infinity,
            })

            const {ads} = decodeProfile(profile)

            const ad = ads.find(({cid}) => cid === burn.cid)

            if (ad) {
              const voting = await getAdVoting(ad.contract)
              return isApprovedVoting(voting) ? burn : null
            }
          }
        })
      ),
    enabled: burntCoinsStatus === 'success',
    select: React.useCallback(data => data.filter(Boolean), []),
    notifyOnChangeProps: 'tracked',
  })
}
Example #19
Source File: hooks.js    From idena-web with MIT License 5 votes vote down vote up
export function useProfileAds() {
  const rpcFetcher = useRpcFetcher()

  const [{profileHash}, {forceUpdate: forceIdentityUpdate}] = useIdentity()

  const {decodeProfile, decodeAd, decodeAdTarget} = useProtoProfileDecoder()

  const {data: profile, status: profileStatus} = useQuery({
    queryKey: ['ipfs_get', [profileHash]],
    queryFn: rpcFetcher,
    enabled: Boolean(profileHash),
    select: decodeProfile,
    staleTime: Infinity,
    notifyOnChangeProps: 'tracked',
  })

  const queryClient = useQueryClient()

  const decodedProfileAds = useQueries(
    profile?.ads?.map(({cid, target, ...ad}) => ({
      queryKey: ['decodedProfileAd', [cid]],
      queryFn: async () => ({
        ...decodeAd(
          await queryClient
            .fetchQuery({
              queryKey: ['ipfs_get', [cid]],
              queryFn: rpcFetcher,
              staleTime: Infinity,
            })
            .catch(() => '')
        ),
        ...decodeAdTarget(target),
        cid,
        ...ad,
      }),
      enabled: Boolean(cid),
      staleTime: Infinity,
    })) ?? []
  )

  const profileAds = useQueries(
    decodedProfileAds
      .filter(({data}) => Boolean(data?.contract))
      .map(({data}) => {
        const {cid, contract, ...ad} = data
        return {
          queryKey: ['profileAd', cid, contract],
          queryFn: async () => ({
            ...ad,
            cid,
            status: AdStatus.Published,
          }),
        }
      })
  )

  const status =
    profileStatus === 'loading' ||
    (profileHash === undefined
      ? true
      : Boolean(profileHash) && profileStatus === 'idle') ||
    decodedProfileAds.some(ad => ad.status === 'loading') ||
    profileAds.some(ad => ad.status === 'loading')
      ? 'loading'
      : 'done'

  return {
    data: profileAds.map(({data}) => data) ?? [],
    status,
    refetch: forceIdentityUpdate,
  }
}
Example #20
Source File: index.js    From flame-coach-web with MIT License 4 votes vote down vote up
Account = ({
  customerIdentifier,
  email
}) => {
  const queryClient = useQueryClient();

  const [notification, setNotification] = useState({
    enable: false,
    message: '',
    level: 'INFO'
  });

  const resetNotificationHandler = () => {
    setNotification(update(notification,
      {
        enable: { $set: false }
      }));
  };

  const updateNotificationHandler = (enable, message, level) => {
    setNotification({
      enable,
      message,
      level
    });
  };

  const updateUserDetailsHandler = (event) => {
    let newValue = event.target.value;

    queryClient.setQueryData(['getContactInformation', customerIdentifier], (oldData) => {
      if (event.target.name === 'country') {
        logDebug('AccountCoach', 'updateUserDetailsHandler', 'Event Native:', event.nativeEvent.target);
        newValue = {
          value: event.nativeEvent.target.textContent,
          code: event.target.value.length === 0 ? null : event.target.value
        };
      }

      return {
        ...oldData,
        [event.target.name]: newValue
      };
    });
  };

  const updatePhotoHandler = () => {
    logger.debug('Update photo');
  };

  const contactInformation = useQuery(['getContactInformation', customerIdentifier],
    () => getCoachContactInformation(customerIdentifier), {
      onError: async (err) => {
        logError('AccountCoach',
          'useQuery getCoachContactInformation',
          'Error:', err);
      }
    });

  const updateContactInformation = useMutation(
    ({
      // eslint-disable-next-line no-shadow
      customerIdentifier,
      newContactInformation
    }) => updateCoachContactInformation(customerIdentifier, newContactInformation),
    {
      onError: (error) => {
        logError('AccountCoach', 'updateContactInformation', 'Error Details:', error.response.data.detail);
        const errorCode = ErrorMessage.fromCode(error.response.data.code);
        updateNotificationHandler(true, errorCode.msg, errorCode.level);
      },
      onSuccess: () => {
        const infoCode = InfoMessage.CODE_2001;
        updateNotificationHandler(true, infoCode.msg, infoCode.level);
      }
    }
  );

  const saveContactInformationHandler = () => {
    logDebug('AccountCoach', 'saveContactInformationHandler', 'Contact Information:', contactInformation.data);

    const newContactInformation = {
      firstName: contactInformation.data.firstName,
      lastName: contactInformation.data.lastName,
      phoneCode: contactInformation.data.phoneCode,
      phoneNumber: contactInformation.data.phoneNumber,
      countryCode: (contactInformation.data.country) ? contactInformation.data.country.code : null
    };

    updateContactInformation.mutate({
      customerIdentifier,
      newContactInformation
    });
  };

  return (
    <Page
      title="Account"
      isError={contactInformation.isError}
      isLoading={contactInformation.isFetching}
    >
      <Grid
        container
        spacing={3}
      >
        <Grid
          item
          lg={4}
          md={6}
          xs={12}
        >
          <Profile
            user={contactInformation.data ? {
              city: '',
              country: (contactInformation.data.country)
                ? contactInformation.data.country.value : '',
              avatar: ''
            } : null}
            updatePhotoHandler={updatePhotoHandler}
          />
        </Grid>
        <Grid
          item
          lg={8}
          md={6}
          xs={12}
        >
          <ProfileDetails
            userDetails={contactInformation.data ? {
              firstName: contactInformation.data.firstName,
              lastName: contactInformation.data.lastName,
              email,
              phoneCode: contactInformation.data.phoneCode,
              phoneNumber: contactInformation.data.phoneNumber,
              country: contactInformation.data.country && contactInformation.data.country.code
                ? contactInformation.data.country.code : '',
            } : null}
            saveContactInformationHandler={saveContactInformationHandler}
            updateUserDetailsHandler={updateUserDetailsHandler}
          />
          {notification.enable
            ? (
              <Notification
                collapse
                open={notification.enable}
                openHandler={resetNotificationHandler}
                level={notification.level}
                message={notification.message}
              />
            )
            : null}
        </Grid>
      </Grid>
    </Page>
  );
}
Example #21
Source File: index.js    From flame-coach-web with MIT License 4 votes vote down vote up
Appointments = ({
  customerIdentifier
}) => {

  const queryClient = useQueryClient();
  const classes = useStyles();

  const [appointment, setAppointment] = React.useState({
    id: null,
    title: null,
    allDay: false,
    dttmStarts: new Date(),
    dttmEnds: new Date(),
    resource: {
      notes: "",
      price: null,
      clientIdentifier: null
    }
  });

  const [notification, setNotification] = useState({
    enable: false,
    message: "",
    level: "INFO"
  });

  const [openDialog, setOpenDialog] = React.useState(false);
  const [operation, setOperation] = React.useState("ADD");
  const [calendarView, setCalendarView] = React.useState(Views.DAY);

  React.useEffect(() => {
    setNotification(update(notification, {
      enable: { $set: false }
    }));
  }, [openDialog]);

  const {
    register,
    handleSubmit
  } = useForm();

  const { mutate: addAppointment } = useAddAppointmentClient();

  const editAppointment = useEditAppointmentClient();

  const { mutate: deleteAppointment } = useDeleteAppointmentClient();

  const appointments = useFetchAppointmentsCoach(customerIdentifier,
    {
      select: (data) => {
        if (data === undefined || !data.appointments) {
          return [];
        }

        return data.appointments.map((item) => {

          const startDate = moment.tz(item.dttmStarts, defaultTimezone);
          const endDate = moment.tz(item.dttmEnds, defaultTimezone);

          return {
            id: item.identifier,
            title: `${item.client.firstName} ${item.client.lastName}`,
            start: new Date(startDate.format("YYYY-MM-DDTHH:mm:ss")),
            end: new Date(endDate.format("YYYY-MM-DDTHH:mm:ss")),
            allDay: false, //TODO: Check if this is all day
            resource: {
              clientIdentifier: item.client.identifier,
              price: item.price,
              notes: item.notes
            }
          };
        }
        );
      }
    }
  );

  const clientsCoach = useFetchClientsCoach(customerIdentifier);

  const okDialogHandler = (data, event) => {
    event.preventDefault();
    logInfo("Appointments", "okDialogHandler", "value", appointment);

    if (!appointment.title || !appointment.dttmStarts ||
      !appointment.dttmEnds || !appointment.resource?.price ||
      !appointment.resource?.clientIdentifier) {
      setNotification(update(notification,
        {
          enable: { $set: true },
          message: { $set: ErrorMessage.CODE_0009.msg },
          level: { $set: ErrorMessage.CODE_0009.level }
        }));
    } else {

      if (appointment.dttmStarts.isAfter(appointment.dttmEnds)) {
        setNotification(update(notification,
          {
            enable: { $set: true },
            message: { $set: ErrorMessage.CODE_0008.msg },
            level: { $set: ErrorMessage.CODE_0008.level }
          }));
      } else {
        if (operation === "ADD") {
          addAppointment({
            appointment: appointment,
            clientIdentifier: appointment.resource?.clientIdentifier,
            coachIdentifier: customerIdentifier
          }, {
            onSuccess: () => {
              queryClient.invalidateQueries(["getCoachAppointments", customerIdentifier]);
              setOpenDialog(false);
            },
            onError: (error) => {
              logError("Appointments", "useMutation addAppointment", "Error Details:", error.response.data.detail);
              const errorCode = ErrorMessage.fromCode(error.response.data.code);

              setNotification(update(notification,
                {
                  enable: { $set: true },
                  message: { $set: errorCode.msg },
                  level: { $set: errorCode.level }
                }));
            }
          }
          );
        }

        if (operation === "EDIT/DELETE") {
          editAppointment.mutate({
            appointmentIdentifier: appointment.id,
            appointment: appointment
          }, {
            onSuccess: () => {
              queryClient.invalidateQueries(["getCoachAppointments", customerIdentifier]);
              setOpenDialog(false);
            },
            onError: (error) => {
              logError("Appointments", "useMutation editAppointment", "Error Details:", error.response.data.detail);
              const errorCode = ErrorMessage.fromCode(error.response.data.code);

              setNotification(update(notification,
                {
                  enable: { $set: true },
                  message: { $set: errorCode.msg },
                  level: { $set: errorCode.level }
                }));
            }
          }
          );
        }
      }
    }
  };

  const deleteHandler = () => {
    deleteAppointment({
      appointmentIdentifier: appointment.id
    }, {
      onSuccess: () => {
        queryClient.invalidateQueries(["getCoachAppointments", customerIdentifier]);
        setOpenDialog(false);
      },
      onError: (error) => {
        logError("Appointments", "useMutation deleteAppointment", "Error Details:", error.response.data.detail);
        const errorCode = ErrorMessage.fromCode(error.response.data.code);

        setNotification(update(notification,
          {
            enable: { $set: true },
            message: { $set: errorCode.msg },
            level: { $set: errorCode.level }
          }));
      }
    });
  };

  const doubleClickSlotHandler = (slot) => {
    setOperation("EDIT/DELETE");

    let dateTimeZoneStart;
    let dateTimeZoneEnd;
    let allDay = false;

    //Entire day event
    if (slot?.allDay) {
      dateTimeZoneStart = getTimezoneDateTime(slot.start);
      dateTimeZoneEnd = getTimezoneDateTime(slot.start)
        .add(1, "days")
        .subtract(1, "seconds");
      allDay = true;
    } else {
      //Select the first and the last date
      dateTimeZoneStart = getTimezoneDateTime(slot.start);
      dateTimeZoneEnd = getTimezoneDateTime(slot.end);
    }

    setAppointment(update(appointment, {
      id: { $set: slot.id },
      title: { $set: slot.title },
      dttmStarts: { $set: dateTimeZoneStart },
      dttmEnds: { $set: dateTimeZoneEnd },
      allDay: { $set: allDay },
      resource: {
        notes: { $set: slot.resource?.notes },
        price: { $set: slot.resource?.price },
        clientIdentifier: { $set: slot.resource?.clientIdentifier }
      }
    }));

    setOpenDialog(true);
  };

  const selectSlotHandler = (slots) => {
    setOperation("ADD");

    let dateTimeZoneStart;
    let dateTimeZoneEnd;
    let allDay = false;

    //Selected entire day
    if (slots.length === 1) {
      dateTimeZoneStart = getTimezoneDateTime(slots[0]);
      dateTimeZoneEnd = getTimezoneDateTime(slots[0])
        .add(1, "days")
        .subtract(1, "seconds");
      allDay = true;
    } else {
      //Select the first and the last date
      dateTimeZoneStart = getTimezoneDateTime(slots[0]);
      dateTimeZoneEnd = getTimezoneDateTime(slots[slots.length - 1]);
    }

    setAppointment(update(appointment, {
      id: { $set: null },
      title: { $set: "" },
      dttmStarts: { $set: dateTimeZoneStart },
      dttmEnds: { $set: dateTimeZoneEnd },
      allDay: { $set: allDay },
      resource: {
        notes: { $set: "" },
        price: { $set: 0.0 },
        clientIdentifier: { $set: null }
      }
    }));

    setOpenDialog(true);
  };

  return (
    <Page
      title={"Appointments"}
      isError={clientsCoach.isError || appointments.isError}
      isLoading={clientsCoach.isFetching || appointments.isFetching}>
      <Card className={clsx(classes.calendarCard)}>
        <CardHeader
          title="Appointments"
          className={clsx(classes.calendarCardHeader)}
        />
        <Divider />
        <CardContent className={clsx(classes.calendarCard, classes.calendarCardContent)}>
          <BigCalendar
            view={calendarView}
            events={appointments.data}
            localizer={localizer}
            doubleClickSlotHandler={doubleClickSlotHandler}
            selectSlotHandler={selectSlotHandler}
            onView={(value) => setCalendarView(value)}
          />
        </CardContent>
      </Card>
      <FormDialog
        submitHandler={handleSubmit}
        dialogTitle={operation === "ADD" ? "New Appointment" : "Edit Appointment"}
        dialogDescription="Please complete the following fields below:"
        open={openDialog}
        deleteHandler={operation === "EDIT/DELETE" ? deleteHandler : null}
        okHandler={okDialogHandler}
        closeHandler={() => setOpenDialog(false)}>
        <Grid
          container
          spacing={1}
          direction="row">
          <Grid
            item
            xs={12}
            md={12}>
            <SearchClient
              clients={!clientsCoach.isLoading ? clientsCoach?.data.clientsCoach : []}
              clientDefault={appointment.resource?.clientIdentifier}
              disabled={operation === "EDIT/DELETE"}
              searchSelectedHandler={(newValue) => {
                setAppointment(update(appointment, {
                  title: { $set: newValue ? `${newValue.firstname} ${newValue.lastname}` : "" },
                  resource: {
                    clientIdentifier: { $set: newValue.identifier }
                  }
                }));
              }}
              error={Boolean(!appointment.resource?.clientIdentifier)}
              margin="dense"
              inputRef={register("client")}
            />
          </Grid>
          <Grid
            item
            xs={12}
            md={6}
          >
            <KeyboardDateTimePicker
              autoOk
              fullWidth
              name="dttmStarts"
              label="Starts"
              inputVariant="outlined"
              inputRef={register("dttmStarts")}
              onChange={(newDate) => {
                logDebug("Appointments", "onChange KeyboardDateTimePicker", "New Date", newDate);
                setAppointment(update(appointment, {
                  dttmStarts: { $set: newDate }
                }));
              }}
              error={Boolean(!appointment.dttmStarts)}
              value={appointment.dttmStarts}
            />
          </Grid>
          <Grid
            item
            xs={12}
            md={6}
          >
            <KeyboardDateTimePicker
              autoOk
              fullWidth
              name="dttmEnds"
              label="Ends"
              inputVariant="outlined"
              inputRef={register("dttmEnds")}
              onChange={(newDate) => {
                logDebug("Appointments", "onChange KeyboardDateTimePicker", "New Date", newDate);
                setAppointment(update(appointment, {
                  dttmEnds: { $set: newDate }
                }));
              }}
              error={Boolean(!appointment.dttmEnds)}
              value={appointment.dttmEnds}
              variant="outlined"
            />
          </Grid>
          <Grid
            item
            xs={12}
            md={12}
          >
            <TextField
              fullWidth
              label="Price"
              name="price"
              type="number"
              inputProps={{
                step: 0.05,
                min: 0
              }}
              InputProps={{
                startAdornment: <InputAdornment position="start">£</InputAdornment>
              }}
              onChange={(event) => {
                setAppointment(update(appointment, {
                  resource: {
                    price: { $set: event.target.value }
                  }
                }));
              }}
              value={appointment.resource?.price}
              error={Boolean(!appointment.resource?.price)}
              margin="dense"
              inputRef={register("price")}
              variant="outlined"
            />
          </Grid>
          <Grid
            item
            xs={12}
            md={12}
          >
            <TextField
              fullWidth
              label="Notes"
              name="notes"
              margin="dense"
              inputRef={register("notes")}
              onChange={(event) => {
                setAppointment(update(appointment, {
                  resource: {
                    notes: { $set: event.target.value }
                  }
                }));
              }}
              value={appointment.resource?.notes}
              variant="outlined"
            />
          </Grid>
          {notification.enable
            ? (
              <Grid item xs={12}>
                <Notification
                  collapse
                  open={notification.enable}
                  openHandler={() => setNotification(update(notification,
                    {
                      enable: { $set: false }
                    }))
                  }
                  level={notification.level}
                  message={notification.message}
                />
              </Grid>
            )
            : null}
        </Grid>
      </FormDialog>
    </Page>
  );

}
Example #22
Source File: index.js    From flame-coach-web with MIT License 4 votes vote down vote up
CustomersView = ({ customerIdentifier }) => {
  const classes = useStyles();
  const isMobile = useIsMediumMobile();

  const options = {
    filterType: "dropdown",
    selectableRows: "none",
    tableBodyMaxHeight: "70vh",
    sortOrder: {
      name: "Name",
      direction: "asc"
    },
    print: false
  };
  const [notification, setNotification] = useState({
    enable: false,
    message: "",
    level: "INFO"
  });

  const queryClient = useQueryClient();

  const [clientLoading, setClientLoading] = useState(false);
  const [isClientLoading, setIsClientLoading] = useState(null);

  const { mutate: inviteClient } = useInviteClient();

  const resetNotificationHandler = () => {
    setNotification(update(notification,
      {
        enable: { $set: false }
      }));
  };

  const updateNotificationHandler = (enable, message, level) => {
    setNotification({
      enable,
      message,
      level
    });
  };

  const formikSendInviteClient = useFormik({
    initialValues: {
      email: ""
    },

    validationSchema,
    validateOnBlur: false,
    validateOnChange: false,
    onSubmit: (values) => {
      inviteClient({
        coachIdentifier: customerIdentifier,
        clientEmail: values.email
      }, {
        onError: (error) => {
          logError("Customer",
            "useMutation inviteClient",
            "Error:", error.response);

          logError("Customer", "useMutation inviteClient", "Error Details:", error.response.data.detail);
          const errorCode = ErrorMessage.fromCode(error.response.data.code);
          updateNotificationHandler(true, errorCode.msg, errorCode.level);
        },
        onSuccess: (data) => {
          queryClient.invalidateQueries(["getClientsCoachPlusClientsAvailableForCoaching", customerIdentifier]);

          const successMessage = data.registrationInvite ? InfoMessage.CODE_0004
            : InfoMessage.CODE_0003;

          updateNotificationHandler(true, successMessage.msg, successMessage.level);
        }
      });
    }
  });

  const {
    isError,
    isLoading,
    data
  } = useQuery(["getClientsCoachPlusClientsAvailableForCoaching", customerIdentifier],
    () => getClientsCoachPlusClientsAvailableForCoaching(customerIdentifier), {
      onError: async (err) => {
        logError("Customer",
          "useQuery getClientsCoachPlusClientsAvailableForCoaching",
          "Error:", err);
      },
      select: (data) => {
        const filteredClients = data.clientsCoach.filter((client) => client.status === "PENDING"
          || client.status === "ACCEPTED");

        return {
          identifier: data.identifier,
          clientsCoach: filteredClients
        };
      }
    });

  const unlinkClient = useMutation(
    ({
      clientIdentifier,
      // eslint-disable-next-line no-unused-vars
      coachIdentifier
    }) => enrollmentProcessBreak(clientIdentifier),
    {
      onMutate: async ({ clientIdentifier }) => {
        setClientLoading(clientIdentifier);
        setIsClientLoading(true);
        resetNotificationHandler();
      },
      onError: async (error) => {
        logError("Customer",
          "useMutation enrollmentProcessBreak",
          "Error:", error.response);
        setIsClientLoading(false);

        logError("Customer", "useMutation enrollmentProcessBreak", "Error Details:", error.response.data.detail);
        const errorCode = ErrorMessage.fromCode(error.response.data.code);
        updateNotificationHandler(true, errorCode.msg, errorCode.level);
      },
      onSuccess: async (data, variables) => {
        await queryClient.cancelQueries(["getClientsCoachPlusClientsAvailableForCoaching", variables.coachIdentifier]);

        queryClient.setQueryData(["getClientsCoachPlusClientsAvailableForCoaching", customerIdentifier], (oldData) => {
          const index = oldData.clientsCoach.findIndex(
            (customer) => customer.identifier === variables.clientIdentifier
          );

          return update(oldData, {
            clientsCoach: {
              [index]: {
                status: { $set: data.status }
              }
            }
          });
        });

        setIsClientLoading(false);
      }
    }
  );

  const unlinkClientHandler = (client) => {
    unlinkClient.mutate({
      clientIdentifier: client.identifier,
      coachIdentifier: customerIdentifier
    });
  };

  const getStatus = (status) => {
    switch (status) {
      case "AVAILABLE":
        return "Available";
      case "PENDING":
        return "Pending";
      case "ACCEPTED":
        return "My client";
      default:
        return "Unknown";
    }
  };

  const columnActions = {
    label: "Actions",
    options: {
      filter: false,
      sort: false,
      setCellHeaderProps: () => {
        return {
          className: clsx({
            [classes.rightTableHead]: true
          })
        };
      },
      // eslint-disable-next-line react/display-name
      customBodyRender: (value) => {
        const disableButtonMinus = value.client.identifier === value.clientLoading
          ? value.isClientLoading || !(value.client.status !== "AVAILABLE")
          : !(value.client.status !== "AVAILABLE");

        return (
          <Grid
            container
            justifyContent={isMobile ? "flex-start" : "flex-end"}
            spacing={1}
            className={clsx({
              [classes.actionColumnTable]: true
            })}
          >
            <Grid item>
              <Button
                className={classes.minusUserButton}
                variant="contained"
                disabled={disableButtonMinus}
                onClick={() => unlinkClientHandler(value.client)}
              >
                <SvgIcon
                  fontSize="small"
                  color="inherit"
                >
                  <UserMinusIcon />
                </SvgIcon>
              </Button>
            </Grid>
          </Grid>
        );
      }
    }
  };

  const columns = ["Name", "Email", "Registration date", "Status", columnActions];

  return (
    <Page
      title="Customers"
      isError={isError}
      isLoading={isLoading}>
      <Grid
        direction="row"
        container
      >
        <Grid item xs={12}>
          <Box marginBottom={2}>
            <form onSubmit={formikSendInviteClient.handleSubmit}>
              <Card>
                <CardContent>
                  <Typography gutterBottom variant="h5" component="h2">
                    Send Invite
                  </Typography>
                  <Box className={classes.sendCard}>
                    <TextField
                      error={Boolean(formikSendInviteClient.errors.email)}
                      fullWidth
                      helperText={formikSendInviteClient.errors.email}
                      label="Email"
                      margin="normal"
                      name="email"
                      onBlur={formikSendInviteClient.handleBlur}
                      onChange={formikSendInviteClient.handleChange}
                      value={formikSendInviteClient.values.email}
                      variant="outlined"
                    />
                    <Button
                      className={classes.sendInviteButton}
                      variant="contained"
                      type="submit"
                    >
                      <SvgIcon
                        fontSize="small"
                        color="inherit"
                      >
                        <SendIcon />
                      </SvgIcon>
                    </Button>
                  </Box>
                </CardContent>
              </Card>
            </form>
          </Box>
        </Grid>
        <Grid item xs={12}>
          <Table
            title="Clients List"
            data={data ? data.clientsCoach.map((client) => ([
              `${client.firstname} ${client.lastname}`,
              client.email,
              client.registrationDate,
              getStatus(client.status),
              {
                client,
                clientLoading,
                isClientLoading
              }
            ])) : null}
            columns={columns}
            options={options}
            themeTable={outerTheme => outerTheme}
          />
        </Grid>
        {notification.enable
          ? (
            <Grid item xs={12}>
              <Notification
                collapse
                open={notification.enable}
                openHandler={resetNotificationHandler}
                level={notification.level}
                message={notification.message}
              />
            </Grid>
          )
          : null}
      </Grid>
    </Page>
  );
}
Example #23
Source File: index.js    From flame-coach-web with MIT License 4 votes vote down vote up
Dashboard = ({
  customerIdentifier
}) => {
  const queryClient = useQueryClient();

  const classes = useStyles();

  // region Native reactj state
  const [enrollment, setEnrollment] = useState({});
  const [activeCoachStep, setActiveCoachStep] = React.useState(0);

  const [startDate, setStartDate] = useState(new Date());
  const [endDate, setEndDate] = useState(new Date());
  const [taskPeriod, setTaskPeriod] = useState("today");
  const [tasksProgress, setTaskProgress] = useState(0);

  const [notification, setNotification] = useState({
    enable: false,
    message: "",
    level: "INFO"
  });

  // endregion

  // region General methods

  const updateNotificationHandler = (enable, message, level) => {
    setNotification({
      enable,
      message,
      level
    });
  };

  const notificationHandler = () => {
    setNotification(update(notification,
      {
        enable: { $set: false }
      }));
  };

  const progressPercentage = (data) => {
    if (!data || !data.dailyTasks) {
      return 0.0;
    }
    const listOfDailyTasks = data.dailyTasks;
    const numberOfDailyTasks = listOfDailyTasks.length;

    logDebug("DashboardClient", "progressPercentage", "numberOfDailyTasks", numberOfDailyTasks);

    const numberOfDailyTasksTicked = listOfDailyTasks
      .filter((dailyTask) => dailyTask.ticked).length;

    if (numberOfDailyTasksTicked === 0) {
      return 0.0;
    }

    logDebug("DashboardClient", "progressPercentage", "numberOfDailyTasksTicked", numberOfDailyTasksTicked);

    const result = (numberOfDailyTasksTicked * 100) / numberOfDailyTasks;

    logDebug("DashboardClient", "progressPercentage", "total", result);

    return result % 1 === 0 ? result : result.toFixed(1);
  };

  const progressLabel = () => {
    switch (taskPeriod) {
      case "today":
        return "TODAY";
      case "thisWeek":
        return "THIS WEEK";
      case "lastWeek":
        return "LAST WEEK";
      case "nextWeek":
        return "NEXT WEEK";
      default:
        return "TODAY";
    }
  };

  const updateTaskHandler = (task, event) => {
    const newDailyTask = {
      name: task.taskName,
      description: task.taskDescription,
      date: task.date,
      ticked: event.target.checked
    };

    updateDailyTask.mutate({
      taskIdentifier: task.identifier,
      newTask: newDailyTask
    });
  };

  const taskPeriodRefreshHandler = () => {
    clientTasks.refetch();
  };

  const taskPeriodHandler = (event) => {
    logDebug("Dashboard", "taskPeriodHandler", "value", event.target.value);
    setTaskPeriod(event.target.value);
    const now = moment();
    let initDate;
    let finalDate;

    switch (event.target.value) {
      case "today": {
        const today = now.toDate();
        initDate = today;
        finalDate = today;
        break;
      }
      case "thisWeek": {
        initDate = now.startOf("week")
          .toDate();
        finalDate = now.endOf("week")
          .toDate();
        break;
      }
      case "lastWeek": {
        const firstDayOfWeek = now.startOf("week");
        initDate = moment(firstDayOfWeek)
          .subtract(7, "days")
          .toDate();
        finalDate = moment(firstDayOfWeek)
          .subtract(1, "days")
          .toDate();
        break;
      }
      case "nextWeek": {
        const firstDayOfWeek = now.startOf("week");
        initDate = moment(firstDayOfWeek)
          .add(7, "days")
          .toDate();
        finalDate = moment(firstDayOfWeek)
          .add(13, "days")
          .toDate();
        break;
      }
      default: {
        const today = now.toDate();
        initDate = today;
        finalDate = today;
        break;
      }
    }

    logDebug("Dashboard", "taskPeriodHandler", "date range", initDate, finalDate);
    setStartDate(initDate);
    setEndDate(finalDate);
  };

  // endregion

  // region Code with queries to API

  const clientTasks = useQuery(["getDailyTasksByClientAndDay", customerIdentifier, startDate, endDate],
    () => getDailyTasksByClientAndDay(customerIdentifier, startDate, endDate), {
      onError: async (err) => {
        logError("DashboardClient",
          "useQuery getDailyTasksByClientAndDay",
          "Error:", err);
      },
      onSettled: (data) => {
        setTaskProgress(progressPercentage(data));
      }
    });

  const enrollmentStatus = useQuery(["getEnrollmentStatus", customerIdentifier],
    () => enrollmentProcessStatus(customerIdentifier), {
      onError: async (err) => {
        logError("DashboardClient",
          "useQuery getEnrollmentStatus",
          "Error:", err);
      },
      onSuccess: (data) => {
        const coach = data.coach ? {
          name: `${data.coach.firstName} ${data.coach.lastName}`
        } : null;

        setEnrollment({
          coach,
          status: data.status
        });

        if (data.status === "ACCEPTED") {
          setActiveCoachStep(3);
        } else if (data.status === "PENDING") {
          setActiveCoachStep(1);
        }
      }
    });

  const enrollmentFinish = useMutation(
    ({
      // eslint-disable-next-line no-shadow
      customerIdentifier,
      flag
    }) => enrollmentProcessFinish(customerIdentifier, flag),
    {
      onError: async (error) => {
        logError("DashboardClient",
          "useMutation enrollmentProcessBreak",
          "Error:", error.response);

        logError("DashboardClient", "useMutation enrollmentProcessBreak", "Error Details:", error.response.data.detail);
        const errorCode = ErrorMessage.fromCode(error.response.data.code);
        updateNotificationHandler(true, errorCode.msg, errorCode.level);
      },
      // eslint-disable-next-line no-unused-vars
      onSuccess: async (data, variables) => {
        if (data.status === "AVAILABLE") {
          setEnrollment(update(enrollment, {
            coach: { $set: null },
            status: { $set: "AVAILABLE" }
          }));
          setActiveCoachStep((prevState) => prevState - 1);
          notificationHandler();
        } else if (data.status === "ACCEPTED") {
          setEnrollment(update(enrollment, {
            status: { $set: "ACCEPTED" }
          }));
          setActiveCoachStep((prevState) => prevState + 1);
          notificationHandler();
        } else {
          const errorCode = ErrorMessage.CODE_9999;
          updateNotificationHandler(true, errorCode.msg, errorCode.level);
        }
      }
    }
  );

  const nextAppointment = useFetchNextClientAppointment(customerIdentifier);

  const updateDailyTask = useMutation(
    ({
      taskIdentifier,
      newTask
    }) => updateDailyTaskByUUID(taskIdentifier, newTask),
    {
      onError: async (error) => {
        logError("DashboardClient", "updateDailyTaskMutation", "Error Details:", error.response.data.detail);
        const errorCode = ErrorMessage.fromCode(error.response.data.code);
        updateNotificationHandler(true, errorCode.msg, errorCode.level);
      },
      onSuccess: async (data, variables) => {
        await queryClient.cancelQueries(["getDailyTasksByClientAndDay", customerIdentifier, startDate, endDate]);

        logDebug("Planner",
          "DashboardClient",
          "Response:", data);

        queryClient.setQueryData(["getDailyTasksByClientAndDay", customerIdentifier, startDate, endDate], (oldData) => {
          logDebug("Planner",
            "DashboardClient",
            "Old Data:", oldData);
          const index = oldData.dailyTasks.findIndex(
            (dailyTask) => dailyTask.identifier === variables.taskIdentifier
          );

          const newData = update(oldData, {
            dailyTasks: {
              [index]: { $set: data.dailyTasks[0] }
            }
          });

          logDebug("Planner",
            "DashboardClient",
            "New Data:", newData);

          return newData;
        });

        setTaskProgress(progressPercentage(data));
        notificationHandler();
      }
    }
  );

  // endregion

  // region Native reactj code

  useEffect(() => {
    if (startDate && endDate) {
      clientTasks.refetch();
    }
  }, [startDate, endDate]);

  // endregion

  return (
    <Page
      title="Dashboard"
      isError={clientTasks.isError || enrollmentStatus.isError}
      isLoading={enrollmentStatus.isLoading}
    >
      <>
        <Grid
          container
          spacing={3}
        >
          <Grid
            item
            lg={3}
            sm={6}
            xs={12}
          >
            <TasksProgress
              isLoading={clientTasks.isFetching}
              type={progressLabel(taskPeriod)}
              progress={tasksProgress}
            />
          </Grid>
          <Grid
            item
            lg={3}
            sm={6}
            xs={12}
          >
            <MyCoach
              isLoading={enrollmentStatus.isFetching}
              coachName={enrollment.coach ? enrollment.coach.name : null}
            />
          </Grid>
          <Grid
            item
            lg={4}
            sm={6}
            xs={12}
          >
            <NextAppointment isLoading={nextAppointment.isFetching}
                             date={nextAppointment.isError ? null : getTimezoneDateTime(nextAppointment.data?.appointments[0].dttmStarts)}
            />
          </Grid>
        </Grid>
        <Grid
          container
          spacing={3}
        >
          <Grid
            item
            xs={12}
          >
            <EnrollmentCard
              isLoading={enrollmentStatus.isFetching}
              activeCoachStep={activeCoachStep}
              setActiveCoachStep={setActiveCoachStep}
              customerIdentifier={customerIdentifier}
              enrollmentFinish={enrollmentFinish}
              enrollmentStatus={enrollment?.status}
            />
          </Grid>
          {notification.enable
            ? (
              <Grid
                item
                xs={12}
              >
                <Notification
                  className={classes.notification}
                  collapse
                  open={notification.enable}
                  openHandler={notificationHandler}
                  level={notification.level}
                  message={notification.message}
                />
              </Grid>
            )
            : null}
          <Grid
            item
            lg={8}
            md={12}
            xl={9}
            xs={12}
          >
            <Tasks
              tasks={clientTasks.data ? clientTasks.data.dailyTasks : []}
              taskPeriod={taskPeriod}
              taskPeriodHandler={taskPeriodHandler}
              taskPeriodRefreshHandler={taskPeriodRefreshHandler}
              updateTaskHandler={updateTaskHandler}
            />
          </Grid>
        </Grid>
      </>
    </Page>
  );
}
Example #24
Source File: GameListItem.jsx    From airboardgame with MIT License 4 votes vote down vote up
GameListItem = ({
  game: {
    owner,
    id,
    board: {
      minAge,
      materialLanguage,
      duration,
      playerCount,
      published,
      imageUrl,
      keepTitle,
    },
  },
  game,
  userId,
  onClick: propOnClick,
  onDelete,
  isAdmin = false,
  studio = false,
}) => {
  const { t, i18n } = useTranslation();
  const history = useHistory();
  const queryClient = useQueryClient();
  const deleteMutation = useMutation((gameId) => deleteGame(gameId), {
    onSuccess: () => {
      queryClient.invalidateQueries("ownGames");
      queryClient.invalidateQueries("games");
    },
  });

  const realImageUrl = media2Url(imageUrl);

  const [showImage, setShowImage] = React.useState(Boolean(realImageUrl));

  const translation = React.useMemo(
    () => getBestTranslationFromConfig(game.board, i18n.languages),
    [game, i18n.languages]
  );

  const onClick = React.useCallback(
    (e) => {
      e.stopPropagation();
      e.preventDefault();
      if (propOnClick) {
        return propOnClick(id);
      } else {
        history.push(`/playgame/${id}`);
      }
    },
    [history, id, propOnClick]
  );

  const onShare = React.useCallback(
    async (e) => {
      e.stopPropagation();
      e.preventDefault();

      await navigator.clipboard.writeText(getGameUrl(id));
      toast.info(t("Url copied to clipboard!"), { autoClose: 1000 });
    },
    [id, t]
  );

  const deleteGameHandler = async () => {
    confirmAlert({
      title: t("Confirmation"),
      message: t("Do you really want to remove this game?"),
      buttons: [
        {
          label: t("Yes"),
          onClick: async () => {
            try {
              deleteMutation.mutate(id);
              if (onDelete) onDelete(id);
              toast.success(t("Game deleted"), { autoClose: 1500 });
            } catch (e) {
              if (e.message === "Forbidden") {
                toast.error(t("Action forbidden. Try logging in again."));
              } else {
                console.log(e);
                toast.error(t("Error while deleting game. Try again later..."));
              }
            }
          },
        },
        {
          label: t("No"),
          onClick: () => {},
        },
      ],
    });
  };

  let playerCountDisplay = undefined;
  if (playerCount && playerCount.length) {
    const [min, max] = playerCount;
    if (min === max) {
      if (max === 9) {
        playerCountDisplay = ["9+"];
      } else {
        playerCountDisplay = [max];
      }
    } else {
      if (max === 9) {
        playerCountDisplay = [min, "9+"];
      } else {
        playerCountDisplay = [min, max];
      }
    }
  }

  let durationDisplay = undefined;
  if (duration && duration.length) {
    const [min, max] = duration;
    if (min === max) {
      if (max === 90) {
        durationDisplay = "90+";
      } else {
        durationDisplay = `~${max}`;
      }
    } else {
      if (max === 90) {
        durationDisplay = `${min}~90+`;
      } else {
        durationDisplay = `${min}~${max}`;
      }
    }
  }

  let materialLanguageDisplay = t(materialLanguage);

  const owned = userId && (userId === owner || !owner);

  return (
    <Game other={!owned && studio}>
      <a href={`/playgame/${id}`} className="img-wrapper button">
        <span onClick={onClick}>
          {showImage && (
            <>
              <span
                className="back"
                style={{ backgroundImage: `url(${realImageUrl})` }}
              />
              <img
                className="img"
                src={realImageUrl}
                alt={translation.name}
                onError={() => setShowImage(false)}
              />
            </>
          )}
          {(!showImage || keepTitle) && <h2>{translation.name}</h2>}
        </span>
      </a>
      <span className="extra-actions">
        <a
          href={getGameUrl(id)}
          className="button edit icon-only success"
          onClick={onShare}
        >
          <img
            src="https://icongr.am/feather/share-2.svg?size=16&color=ffffff"
            alt={t("Share game link")}
            title={t("Share game link")}
          />
        </a>
        {(owned || isAdmin) && (
          <>
            <button
              onClick={deleteGameHandler}
              className="button edit icon-only error"
            >
              <img
                src="https://icongr.am/feather/trash.svg?size=16&color=ffffff"
                alt={t("Delete")}
                title={t("Delete")}
              />
            </button>
            <Link to={`/game/${id}/edit`} className="button edit icon-only ">
              <img
                src="https://icongr.am/feather/edit.svg?size=16&color=ffffff"
                alt={t("Edit")}
                title={t("Edit")}
              />
            </Link>
          </>
        )}
      </span>
      {!published && (
        <img
          className="unpublished"
          src="https://icongr.am/entypo/eye-with-line.svg?size=32&color=888886"
          alt={t("Unpublished")}
        />
      )}
      <div className="details">
        {playerCountDisplay && (
          <span>
            {playerCountDisplay.length === 2 &&
              t("{{min}} - {{max}} players", {
                min: playerCountDisplay[0],
                max: playerCountDisplay[1],
              })}
            {playerCountDisplay.length === 1 &&
              t("{{count}} player", {
                count: playerCountDisplay[0],
              })}
          </span>
        )}
        {durationDisplay && <span>{durationDisplay} mins</span>}
        {minAge && <span>age {minAge}+</span>}
        {materialLanguageDisplay && <span>{materialLanguageDisplay}</span>}
      </div>

      <h2 className="game-name">{translation.name}</h2>

      <p className="baseline">{translation.baseline}</p>
    </Game>
  );
}
Example #25
Source File: MediaLibraryModal.jsx    From airboardgame with MIT License 4 votes vote down vote up
MediaLibraryModal = ({ show, setShow, onSelect }) => {
  const { t } = useTranslation();

  const {
    getLibraryMedia,
    addMedia,
    removeMedia,
    libraries,
  } = useMediaLibrary();

  const queryClient = useQueryClient();
  const [tab, setTab] = React.useState(libraries[0].id);

  const currentLibrary = libraries.find(({ id }) => id === tab);

  const { isLoading, data = [] } = useQuery(
    `media__${tab}`,
    () => getLibraryMedia(currentLibrary),
    {
      enabled: show,
    }
  );

  const handleSelect = React.useCallback(
    (media) => {
      onSelect(media);
      setShow(false);
    },
    [onSelect, setShow]
  );

  const uploadMediaMutation = useMutation(
    async (files) => {
      if (files.length === 1) {
        return [await addMedia(currentLibrary, files[0])];
      } else {
        return Promise.all(files.map((file) => addMedia(currentLibrary, file)));
      }
    },
    {
      onSuccess: (result) => {
        if (result.length === 1) {
          // If only one file is processed
          handleSelect(result[0].content);
        }
        queryClient.invalidateQueries(`media__${tab}`);
      },
    }
  );

  const onRemove = React.useCallback(
    (key) => {
      confirmAlert({
        title: t("Confirmation"),
        message: t("Do you really want to remove this media?"),
        buttons: [
          {
            label: t("Yes"),
            onClick: async () => {
              try {
                await removeMedia(key);
                toast.success(t("Media deleted"), { autoClose: 1500 });
              } catch (e) {
                if (e.message === "Forbidden") {
                  toast.error(t("Action forbidden. Try logging in again."));
                } else {
                  console.log(e);
                  toast.error(
                    t("Error while deleting media. Try again later...")
                  );
                }
              }
            },
          },
          {
            label: t("No"),
            onClick: () => {},
          },
        ],
      });
    },
    [removeMedia, t]
  );

  const { getRootProps, getInputProps } = useDropzone({
    onDrop: uploadMediaMutation.mutate,
  });

  return (
    <Modal
      title={t("Media library")}
      show={show}
      setShow={setShow}
      position="left"
    >
      <nav className="tabs">
        {libraries.map(({ id, name }) => (
          <a
            onClick={() => setTab(id)}
            className={tab === id ? "active" : ""}
            style={{ cursor: "pointer" }}
            key={id}
          >
            {name}
          </a>
        ))}
      </nav>

      <section>
        {libraries.map(({ id, name }, index) => {
          if (tab === id) {
            return (
              <div key={id}>
                {index === 0 && (
                  <>
                    <h3>{t("Add file")}</h3>
                    <div
                      {...getRootProps()}
                      style={{
                        border: "3px dashed white",
                        margin: "0.5em",
                        padding: "0.5em",
                        textAlign: "center",
                      }}
                    >
                      <input {...getInputProps()} />
                      <p>{t("Click or drag'n'drop file here")}</p>
                    </div>
                  </>
                )}
                <h3>{name}</h3>
                {!isLoading && (
                  <ImageGrid>
                    {data.map((key) => (
                      <div key={key}>
                        <img
                          src={`${API_BASE}/${key}`}
                          onClick={() => handleSelect(key)}
                        />
                        <button
                          onClick={() => onRemove(key)}
                          className="button icon-only remove"
                          title={t("Remove")}
                        >
                          X
                        </button>
                      </div>
                    ))}
                  </ImageGrid>
                )}
              </div>
            );
          } else {
            return null;
          }
        })}
      </section>
    </Modal>
  );
}
Example #26
Source File: index.js    From flame-coach-web with MIT License 4 votes vote down vote up
MeasuresView = ({
  customerIdentifier
}) => {
  const classes = useStyles();

  const queryClient = useQueryClient();

  const isMobile = useIsMobile();

  const [timeFrameWeight, setTimeFrameWeight] = useState("1_MONTH");
  const [dateWeightAdding, setDateWeightAdding] = useState(moment()
    .utc());
  const [weightAdding, setWeightAdding] = useState(NaN);

  const [notification, setNotification] = useState({
    enable: false,
    message: "",
    level: "INFO"
  });

  const updateNotificationHandler = (enable, message, level) => {
    setNotification({
      enable,
      message,
      level
    });
  };

  const notificationHandler = () => {
    setNotification(update(notification,
      {
        enable: { $set: false }
      }));
  };

  const personalData = useFetchClientPersonalInformation(customerIdentifier);
  const {
    isFetching,
    isError,
    data
  } = useFetchWeightClient(customerIdentifier);
  const { mutate: mutateAddWeight } = useAddWeightClient();
  const { mutate: mutateDeleteWeight } = useDeleteWeightClient();

  const filteredData = filterWeightsPerTimeRange(data, moment()
    .utc(), timeFrameWeight);

  const addWeightHandler = (weight, date) => {
    if (Number.isNaN(weight)) {
      const errorMessage = ErrorMessage.CODE_0006;
      updateNotificationHandler(true, errorMessage.msg, errorMessage.level);
    } else if (date === null) {
      const errorMessage = ErrorMessage.CODE_0007;
      updateNotificationHandler(true, errorMessage.msg, errorMessage.level);
    } else {
      mutateAddWeight({
        clientIdentifier: customerIdentifier,
        weight,
        utcDate: date
      }, {
        onError: (error) => {
          logError("Measures",
            "useMutation addNewWeight",
            "Error:", error.response);

          logError("Measures", "useMutation addNewWeight", "Error Details:", error.response.data.detail);
          const errorCode = ErrorMessage.fromCode(error.response.data.code);
          updateNotificationHandler(true, errorCode.msg, errorCode.level);
        },
        onSuccess: () => {
          queryClient.invalidateQueries(["getWeightClient", customerIdentifier]);

          const successMessage = InfoMessage.CODE_0002;
          updateNotificationHandler(true, successMessage.msg, successMessage.level);
        }
      });
    }
  };

  const deleteWeightHandler = (event) => {
    mutateDeleteWeight({
      clientIdentifier: customerIdentifier,
      identifier: event.identifier
    }, {
      onError: (error) => {
        logError("Measures",
          "useMutation deleteHandler",
          "Error:", error.response);

        logError("Measures", "useMutation deleteHandler", "Error Details:", error.response.data.detail);
        const errorCode = ErrorMessage.fromCode(error.response.data.code);
        updateNotificationHandler(true, errorCode.msg, errorCode.level);
      },
      onSuccess: () => {
        queryClient.invalidateQueries(["getWeightClient", customerIdentifier]);
      }
    });
  };

  return (
    <Page
      title="Measures"
      isError={isError || personalData.isError}
      isLoading={isFetching || personalData.isFetching}
    >
      <Grid
        container
        spacing={1}
        direction="row"
      >
        <Grid
          item
          xs={12}
          md={9}
        >
          <Grid
            container
            spacing={1}
            direction="column"
          >
            <Grid
              item
              xs={12}
            >
              <Grid container spacing={1}>
                <Grid item md={9} xs={12}>
                  <WeightChart
                    className={isMobile ? classes.weightGraphicMobileCardContent
                      : classes.weightGraphicCardContent}
                    isMobile={isMobile}
                    timeFrame={timeFrameWeight}
                    dataChart={filteredData}
                    measureUnit={extractWeightType(personalData.data?.measureType.value)}
                  />
                </Grid>
                <Grid item md={3} xs={12}>
                  <Events
                    className={classes.eventsCardContent}
                    dataEvents={filteredData}
                    onDeleteHandle={deleteWeightHandler}
                    measureUnit={extractWeightType(personalData.data?.measureType.value)}
                  />
                </Grid>
              </Grid>
            </Grid>
          </Grid>
        </Grid>

        <Grid
          item
          md={3}
          xs={12}
        >
          <Filters
            className={isMobile ? null : classes.filtersCardContent}
            enableAddingWeight
            timeFrame={timeFrameWeight}
            onChangeTimeFrameHandler={(newTimeFrame) => setTimeFrameWeight(newTimeFrame)}
            date={dateWeightAdding}
            onChangeDateHandler={(newDate) => setDateWeightAdding(newDate)}
            weight={weightAdding}
            onChangeWeightHandler={(newWeight) => setWeightAdding(newWeight)}
            onAddWeightHandler={addWeightHandler}
          />
        </Grid>
        {notification.enable
          ? (
            <Grid
              item
              xs={12}
              md={9}
            >
              <Notification
                collapse
                open={notification.enable}
                openHandler={notificationHandler}
                level={notification.level}
                message={notification.message}
              />
            </Grid>
          )
          : null}
      </Grid>
    </Page>
  );
}
Example #27
Source File: hooks.js    From idena-web with MIT License 4 votes vote down vote up
export function useDeferredVotes() {
  const queryClient = useQueryClient()
  const {coinbase, privateKey} = useAuthState()
  const failToast = useFailToast()
  const {t} = useTranslation()
  const router = useRouter()

  const {data: deferredVotes, isFetched} = useQuery(
    'useDeferredVotes',
    () => getDeferredVotes(coinbase),
    {
      enabled: !!coinbase,
      initialData: [],
    }
  )

  const {
    data: {currentBlock},
    isFetched: isBlockFetched,
  } = useSyncing({
    refetchIntervalInBackground: true,
    refetchInterval: REFETCH_INTERVAL,
    enabled: deferredVotes.length > 0,
  })

  const addVote = async vote => {
    await addDeferredVote({coinbase, ...vote})
    queryClient.invalidateQueries('useDeferredVotes')
  }

  const deleteVote = async id => {
    await deleteDeferredVote(id)
    queryClient.invalidateQueries('useDeferredVotes')
  }

  const estimateSendVote = async vote => {
    const voteData = {
      method: 'sendVote',
      contractHash: vote.contractHash,
      amount: vote.amount,
      args: vote.args,
    }

    return estimateCallContract(privateKey, voteData)
  }

  const estimateProlong = async contractHash => {
    try {
      await estimateCallContract(privateKey, {
        method: 'prolongVoting',
        contractHash,
      })
      return true
    } catch (e) {
      return false
    }
  }

  const sendVote = async (vote, skipToast) => {
    function showError(message) {
      failToast(
        `${t('Can not send scheduled transaction:', {
          nsSeparator: '|',
        })} ${message}`
      )
    }

    try {
      const voteData = {
        method: 'sendVote',
        contractHash: vote.contractHash,
        amount: vote.amount,
        args: vote.args,
      }

      console.log(`sending deferred vote, contract: ${vote.contractHash}`)

      const {
        receipt: {gasCost, txFee},
      } = await estimateCallContract(privateKey, voteData)

      const voteResponse = await callContract(privateKey, {
        ...voteData,
        gasCost: Number(gasCost),
        txFee: Number(txFee),
      })

      await updateDeferredVote(vote.id, {
        type: DeferredVoteType.Success,
        txHash: voteResponse,
      })
      queryClient.invalidateQueries('useDeferredVotes')
    } catch (e) {
      switch (e.message) {
        case 'too early to accept open vote': {
          try {
            const readContractData = createContractDataReader(vote.contractHash)

            const startBlock = await readContractData('startBlock', 'uint64')
            const votingDuration = await readContractData(
              'votingDuration',
              'uint64'
            )

            const nextVoteBlock = startBlock + votingDuration

            if (nextVoteBlock > vote.block) {
              await updateDeferredVote(vote.id, {
                block: nextVoteBlock,
              })
              queryClient.invalidateQueries('useDeferredVotes')
            }
          } catch (err) {
            console.error(err)
          } finally {
            if (!skipToast) showError(e.message)
          }
          break
        }
        case 'too late to accept open vote':
        case 'quorum is not reachable': {
          if (await estimateProlong(vote.contractHash)) {
            if (!skipToast)
              failToast({
                title: t('Can not cast public vote. Please, prolong voting'),
                onAction: () => {
                  router.push(`/oracles/view?id=${vote.contractHash}`)
                },
                actionContent: t('Open voting'),
              })

            const readContractData = createContractDataReader(vote.contractHash)

            const votingDuration = await readContractData(
              'votingDuration',
              'uint64'
            )

            await updateDeferredVote(vote.id, {
              block: vote.block + votingDuration,
            })
            queryClient.invalidateQueries('useDeferredVotes')
          } else {
            if (!skipToast) showError(e.message)
            deleteVote(vote.id)
          }
          break
        }
        case 'insufficient funds': {
          showError(e.message)
          break
        }
        default: {
          showError(e.message)
          deleteVote(vote.id)
        }
      }
    }
  }

  const available = deferredVotes.filter(x => x.block < currentBlock)

  return [
    {
      votes: available,
      all: deferredVotes,
      isReady: isFetched && isBlockFetched,
    },
    {addVote, sendVote, estimateSendVote, estimateProlong, deleteVote},
  ]
}
Example #28
Source File: hooks.js    From idena-web with MIT License 4 votes vote down vote up
export function useRotatingAds(limit = 3) {
  const rpcFetcher = useRpcFetcher()

  const burntCoins = useTargetedAds()

  const addresses = [...new Set(burntCoins?.map(({address}) => address))]

  const profileHashes = useQueries(
    addresses.map(address => ({
      queryKey: ['dna_identity', [address]],
      queryFn: rpcFetcher,
      staleTime: 5 * 60 * 1000,
      notifyOnChangeProps: ['data', 'error'],
      select: selectProfileHash,
    }))
  )

  const {decodeAd, decodeProfile} = useProtoProfileDecoder()

  const profiles = useQueries(
    profileHashes.map(({data: cid}) => ({
      queryKey: ['ipfs_get', [cid]],
      queryFn: rpcFetcher,
      enabled: Boolean(cid),
      select: decodeProfile,
      staleTime: Infinity,
      notifyOnChangeProps: ['data'],
    }))
  )

  const profileAdVotings = useQueries(
    profiles
      .map(({data}) => data?.ads)
      .flat()
      .map(ad => ({
        queryKey: ['profileAdVoting', ad?.contract],
        queryFn: () => getAdVoting(ad?.contract),
        enabled: Boolean(ad?.contract),
        staleTime: 5 * 60 * 1000,
        select: data => ({...data, cid: ad?.cid}),
      }))
  )

  const approvedProfileAdVotings = profileAdVotings?.filter(({data}) =>
    isApprovedVoting(data)
  )

  const queryClient = useQueryClient()

  const decodedProfileAds = useQueries(
    burntCoins
      ?.filter(({key}) =>
        approvedProfileAdVotings.some(
          ({data}) => data?.cid === AdBurnKey.fromHex(key).cid
        )
      )
      .slice(0, limit)
      .map(({key, address, amount}) => {
        const {cid} = AdBurnKey.fromHex(key)
        return {
          queryKey: ['decodedRotatingAd', [cid]],
          queryFn: async () => ({
            ...decodeAd(
              await queryClient
                .fetchQuery({
                  queryKey: ['ipfs_get', [cid]],
                  queryFn: rpcFetcher,
                  staleTime: Infinity,
                })
                .catch(() => '')
            ),
            cid,
            author: address,
            amount: Number(amount),
          }),
          enabled: Boolean(cid),
          staleTime: Infinity,
        }
      }) ?? []
  )

  return decodedProfileAds?.map(x => x.data).filter(Boolean) ?? []
}
Example #29
Source File: index.js    From idena-web with MIT License 4 votes vote down vote up
export default function HomePage() {
  const queryClient = useQueryClient()

  const {
    t,
    i18n: {language},
  } = useTranslation()

  const [identity] = useIdentity()

  const {
    address,
    state,
    online,
    delegatee,
    delegationEpoch,
    pendingUndelegation,
    canMine,
    canInvite,
    canTerminate,
    canActivateInvite,
  } = identity

  const router = useRouter()

  const epoch = useEpoch()
  const {privateKey} = useAuthState()
  const userStatAddress = useBreakpointValue([
    address ? `${address.substr(0, 3)}...${address.substr(-4, 4)}` : '',
    address,
  ])

  const [showValidationResults, setShowValidationResults] = React.useState()

  const {onCopy} = useClipboard(address)
  const successToast = useSuccessToast()

  const {
    isOpen: isOpenKillForm,
    onOpen: onOpenKillForm,
    onClose: onCloseKillForm,
  } = useDisclosure()

  const {
    data: {balance, stake, replenishedStake},
  } = useQuery(['get-balance', address], () => fetchBalance(address), {
    initialData: {balance: 0, stake: 0, replenishedStake: 0},
    enabled: !!address,
    refetchInterval: 30 * 1000,
  })

  const [validationResultSeen, setValidationResultSeen] = useValidationResults()

  useEffect(() => {
    if (epoch) {
      const {epoch: epochNumber} = epoch
      if (epochNumber) {
        queryClient.invalidateQueries('get-balance')
        setShowValidationResults(!validationResultSeen)
      }
    }
  }, [epoch, queryClient, validationResultSeen])

  const [dnaUrl] = React.useState(() =>
    typeof window !== 'undefined'
      ? JSON.parse(sessionStorage.getItem('dnaUrl'))
      : null
  )

  React.useEffect(() => {
    if (dnaUrl) {
      if (isValidDnaUrl(dnaUrl.route))
        router.push({pathname: dnaUrl.route, query: dnaUrl.query})
      sessionStorage.removeItem('dnaUrl')
    }
  }, [dnaUrl, router])

  const toDna = toLocaleDna(language, {maximumFractionDigits: 4})

  const [
    currentOnboarding,
    {dismissCurrentTask, next: nextOnboardingTask},
  ] = useOnboarding()

  const eitherOnboardingState = (...states) =>
    eitherState(currentOnboarding, ...states)

  const {
    isOpen: isOpenActivateInvitePopover,
    onOpen: onOpenActivateInvitePopover,
    onClose: onCloseActivateInvitePopover,
  } = useDisclosure()

  const activateInviteDisclosure = useDisclosure()

  const activateInviteRef = React.useRef()

  const {scrollTo: scrollToActivateInvite} = useScroll(activateInviteRef)

  React.useEffect(() => {
    if (
      isOpenActivateInvitePopover ||
      eitherState(
        currentOnboarding,
        onboardingShowingStep(OnboardingStep.StartTraining),
        onboardingShowingStep(OnboardingStep.ActivateInvite)
      )
    ) {
      scrollToActivateInvite()
      onOpenActivateInvitePopover()
    } else onCloseActivateInvitePopover()
  }, [
    currentOnboarding,
    isOpenActivateInvitePopover,
    onCloseActivateInvitePopover,
    onOpenActivateInvitePopover,
    scrollToActivateInvite,
  ])

  const canSubmitFlip = [
    IdentityStatus.Verified,
    IdentityStatus.Human,
    IdentityStatus.Newbie,
  ].includes(state)

  const [{idenaBotConnected}, {persistIdenaBot, skipIdenaBot}] = useAppContext()

  const shouldStartIdenaJourney = currentOnboarding.matches(
    OnboardingStep.StartTraining
  )
  const onboardingPopoverPlacement = useBreakpointValue(['top', 'bottom'])

  const replenishStakeDisclosure = useDisclosure()

  const {
    onOpen: onOpenReplenishStakeDisclosure,
    onClose: onCloseReplenishStakeDisclosure,
  } = replenishStakeDisclosure

  React.useEffect(() => {
    if (Object.keys(router.query).find(q => q === 'replenishStake')) {
      onOpenReplenishStakeDisclosure()
      router.push('/home')
    }
  }, [onOpenReplenishStakeDisclosure, router])

  const failToast = useFailToast()

  const toast = useSuccessToast()

  const stakingApy = useStakingApy()

  const ads = useRotatingAds()

  const isDesktop = useIsDesktop()

  const spoilInviteDisclosure = useDisclosure()

  return (
    <Layout canRedirect={!dnaUrl} didConnectIdenaBot={idenaBotConnected}>
      {!idenaBotConnected && (
        <MyIdenaBotAlert onConnect={persistIdenaBot} onSkip={skipIdenaBot} />
      )}

      <Page pt="10" position="relative">
        <MobileApiStatus top={idenaBotConnected ? 4 : 5 / 2} left={4} />
        <Stack
          w={['100%', '480px']}
          direction={['column', 'row']}
          spacing={['6', 10]}
        >
          <Box>
            <Stack
              spacing={[1, 8]}
              w={['100%', '480px']}
              align={['center', 'initial']}
              ref={activateInviteRef}
            >
              <UserProfileCard
                identity={identity}
                my={[4, 0]}
              ></UserProfileCard>

              {canActivateInvite && (
                <Box w={['100%', 'initial']} pb={[8, 0]}>
                  <OnboardingPopover
                    isOpen={isOpenActivateInvitePopover}
                    placement={onboardingPopoverPlacement}
                  >
                    <PopoverTrigger>
                      {shouldStartIdenaJourney ? (
                        <StartIdenaJourneyPanel
                          onHasActivationCode={activateInviteDisclosure.onOpen}
                        />
                      ) : state === IdentityStatus.Invite ? (
                        <AcceptInvitationPanel />
                      ) : (
                        <ActivateInvitationPanel />
                      )}
                    </PopoverTrigger>
                    {shouldStartIdenaJourney ? (
                      <StartIdenaJourneyOnboardingContent
                        onDismiss={() => {
                          dismissCurrentTask()
                          onCloseActivateInvitePopover()
                        }}
                      />
                    ) : state === IdentityStatus.Invite ? (
                      <AcceptInviteOnboardingContent
                        onDismiss={() => {
                          dismissCurrentTask()
                          onCloseActivateInvitePopover()
                        }}
                      />
                    ) : (
                      <ActivateInviteOnboardingContent
                        onDismiss={() => {
                          dismissCurrentTask()
                          onCloseActivateInvitePopover()
                        }}
                      />
                    )}
                  </OnboardingPopover>
                </Box>
              )}

              {showValidationResults && (
                <ValidationReportSummary
                  onClose={() => setValidationResultSeen()}
                />
              )}

              <UserStatList title={t('My Wallet')}>
                <UserStatistics label={t('Address')} value={userStatAddress}>
                  <ExternalLink
                    display={['none', 'initial']}
                    href={`https://scan.idena.io/address/${address}`}
                  >
                    {t('Open in blockchain explorer')}
                  </ExternalLink>
                  <CopyIcon
                    display={['inline', 'none']}
                    mt="3px"
                    ml="4px"
                    boxSize={4}
                    fill="#96999e"
                    onClick={() => {
                      onCopy()
                      successToast({
                        title: 'Address copied!',
                        duration: '5000',
                      })
                    }}
                  />
                </UserStatistics>

                <UserStatistics label={t('Balance')} value={toDna(balance)}>
                  <TextLink display={['none', 'initial']} href="/wallets">
                    <Stack isInline spacing={0} align="center" fontWeight={500}>
                      <Text as="span">{t('Send')}</Text>
                      <ChevronRightIcon boxSize={4} />
                    </Stack>
                  </TextLink>
                </UserStatistics>

                <Button
                  display={['initial', 'none']}
                  onClick={() => {
                    router.push('/wallets')
                  }}
                  w="100%"
                  h={10}
                  fontSize="15px"
                  variant="outline"
                  color="blue.500"
                  border="none"
                  borderColor="transparent"
                >
                  {t('Send iDNA')}
                </Button>
              </UserStatList>

              <Stack spacing="2" w="full">
                {Boolean(state) && state !== IdentityStatus.Undefined && (
                  <UserStatList title={t('Stake')}>
                    <Stack direction={['column', 'row']} spacing={['5', 0]}>
                      <Stack spacing={['5', '3']} flex={1}>
                        <Stack spacing="5px">
                          <UserStat>
                            <Flex
                              direction={['row', 'column']}
                              justify={['space-between', 'flex-start']}
                            >
                              <UserStatLabel
                                color={[null, 'muted']}
                                fontSize={['mdx', 'md']}
                                fontWeight={[400, 500]}
                                lineHeight="4"
                              >
                                {t('Balance')}
                              </UserStatLabel>
                              <UserStatValue
                                fontSize={['mdx', 'md']}
                                lineHeight="4"
                                mt={[null, '3px']}
                              >
                                {toDna(
                                  state === IdentityStatus.Newbie
                                    ? (stake - (replenishedStake ?? 0)) * 0.25
                                    : stake
                                )}
                              </UserStatValue>
                            </Flex>
                          </UserStat>
                          <Button
                            display={['none', 'inline-flex']}
                            variant="link"
                            color="blue.500"
                            fontWeight={500}
                            lineHeight="4"
                            w="fit-content"
                            _hover={{
                              background: 'transparent',
                              textDecoration: 'underline',
                            }}
                            _focus={{
                              outline: 'none',
                            }}
                            onClick={replenishStakeDisclosure.onOpen}
                          >
                            {t('Add stake')}
                            <ChevronRightIcon boxSize="4" />
                          </Button>
                        </Stack>
                        {stake > 0 && state === IdentityStatus.Newbie && (
                          <AnnotatedUserStatistics
                            annotation={t(
                              'You need to get Verified status to get the locked funds into the normal wallet'
                            )}
                            label={t('Locked')}
                            value={toDna(
                              (stake - (replenishedStake ?? 0)) * 0.75
                            )}
                          />
                        )}
                      </Stack>
                      <Stack spacing="5px" flex={1}>
                        <UserStat flex={0}>
                          <Flex
                            direction={['row', 'column']}
                            justify={['space-between', 'flex-start']}
                          >
                            <UserStatLabel
                              color={[null, 'muted']}
                              fontSize={['mdx', 'md']}
                              fontWeight={[400, 500]}
                              lineHeight="4"
                            >
                              {t('APY')}
                            </UserStatLabel>
                            <UserStatValue
                              fontSize={['mdx', 'md']}
                              lineHeight="4"
                              mt={[null, '3px']}
                            >
                              {stakingApy > 0 ? toPercent(stakingApy) : '--'}
                            </UserStatValue>
                          </Flex>
                        </UserStat>
                        <ExternalLink
                          href={`https://idena.io/staking?amount=${Math.floor(
                            state === IdentityStatus.Newbie
                              ? (stake - (replenishedStake ?? 0)) * 0.25
                              : stake
                          )}`}
                          display={['none', 'inline-flex']}
                        >
                          {t('Staking calculator')}
                        </ExternalLink>
                      </Stack>
                    </Stack>

                    <Stack display={['inline-flex', 'none']}>
                      <Button
                        onClick={replenishStakeDisclosure.onOpen}
                        w="100%"
                        h={10}
                        fontSize="15px"
                        variant="outline"
                        color="blue.500"
                        border="none"
                        borderColor="transparent"
                      >
                        {t('Add stake')}
                      </Button>

                      <Button
                        onClick={() => {
                          openExternalUrl(
                            `https://idena.io/staking?amount=${Math.floor(
                              state === IdentityStatus.Newbie
                                ? (stake - (replenishedStake ?? 0)) * 0.25
                                : stake
                            )}`
                          )
                        }}
                        w="100%"
                        h={10}
                        fontSize="15px"
                        variant="outline"
                        color="blue.500"
                        border="none"
                        borderColor="transparent"
                      >
                        {t('Staking calculator')}
                      </Button>
                    </Stack>
                  </UserStatList>
                )}
                <StakingAlert />
              </Stack>
            </Stack>
            {ads?.length > 0 && !isDesktop && (
              <Box display={['block', 'none']} mt="6">
                <AdCarousel ads={ads} />
              </Box>
            )}
          </Box>

          <Stack spacing={[0, 10]} flexShrink={0} w={['100%', 200]}>
            {address && privateKey && canMine && (
              <Box minH={62} mt={[1, 6]}>
                <OnboardingPopover
                  isOpen={eitherOnboardingState(
                    onboardingShowingStep(OnboardingStep.ActivateMining)
                  )}
                >
                  <PopoverTrigger>
                    <Box
                      bg="white"
                      position={
                        eitherOnboardingState(
                          onboardingShowingStep(OnboardingStep.ActivateMining)
                        )
                          ? 'relative'
                          : 'initial'
                      }
                      borderRadius={['mdx', 'md']}
                      p={[0, 2]}
                      m={[0, -2]}
                      zIndex={2}
                    >
                      <ActivateMiningForm
                        privateKey={privateKey}
                        isOnline={online}
                        delegatee={delegatee}
                        delegationEpoch={delegationEpoch}
                        pendingUndelegation={pendingUndelegation}
                        onShow={nextOnboardingTask}
                      />
                    </Box>
                  </PopoverTrigger>
                  <OnboardingPopoverContent
                    title={t('Activate mining status')}
                    onDismiss={nextOnboardingTask}
                  >
                    <Text>
                      {t(
                        `To become a validator of Idena blockchain you can activate your mining status. Keep your node online to mine iDNA coins.`
                      )}
                    </Text>
                  </OnboardingPopoverContent>
                </OnboardingPopover>
              </Box>
            )}
            <Stack spacing={[0, 1]} align="flex-start">
              <WideLink
                display={['initial', 'none']}
                label="Open in blockchain explorer"
                href={`https://scan.idena.io/address/${address}`}
                isNewTab
              >
                <Box
                  boxSize={8}
                  backgroundColor="brandBlue.10"
                  borderRadius="10px"
                >
                  <OpenExplorerIcon boxSize={5} mt="6px" ml="6px" />
                </Box>
              </WideLink>
              <WideLink
                mt={[0, '2px']}
                label={t('Training validation')}
                onClick={() => router.push('/try')}
              >
                <Box
                  boxSize={[8, 5]}
                  backgroundColor={['brandBlue.10', 'initial']}
                  borderRadius="10px"
                >
                  <TestValidationIcon
                    color="blue.500"
                    boxSize={5}
                    mt={['6px', 0]}
                    ml={['6px', 0]}
                  />
                </Box>
              </WideLink>
              <WideLink
                label={t('New voting')}
                onClick={() => router.push('/oracles/new')}
              >
                <Box
                  boxSize={[8, 5]}
                  backgroundColor={['brandBlue.10', 'initial']}
                  borderRadius="10px"
                >
                  <OracleIcon
                    color="blue.500"
                    boxSize={5}
                    mt={['6px', 0]}
                    ml={['6px', 0]}
                  />
                </Box>
              </WideLink>
              <WideLink
                label={t('New ad')}
                onClick={() => router.push('/adn/new')}
              >
                <Box
                  boxSize={[8, 5]}
                  backgroundColor={['brandBlue.10', 'initial']}
                  borderRadius="10px"
                >
                  <AdsIcon
                    color="blue.500"
                    boxSize={5}
                    mt={['6px', 0]}
                    ml={['6px', 0]}
                  />
                </Box>
              </WideLink>
              <WideLink
                label={t('New flip')}
                isDisabled={!canSubmitFlip}
                onClick={() => router.push('/flips/new')}
              >
                <Box
                  boxSize={[8, 5]}
                  backgroundColor={['brandBlue.10', 'initial']}
                  borderRadius="10px"
                >
                  <PhotoIcon
                    color="blue.500"
                    boxSize={5}
                    mt={['6px', 0]}
                    ml={['6px', 0]}
                  />
                </Box>
              </WideLink>
              <WideLink
                label={t('Invite')}
                onClick={() => router.push('/contacts?new')}
                isDisabled={!canInvite}
              >
                <Box
                  boxSize={[8, 5]}
                  backgroundColor={['brandBlue.10', 'initial']}
                  borderRadius="10px"
                >
                  <AddUserIcon
                    color="blue.500"
                    boxSize={5}
                    mt={['6px', 0]}
                    ml={['6px', 0]}
                  />
                </Box>
              </WideLink>
              <WideLink
                label={t('Spoil invite')}
                onClick={spoilInviteDisclosure.onOpen}
              >
                <Box
                  boxSize={[8, 5]}
                  backgroundColor={['brandBlue.10', 'initial']}
                  borderRadius="10px"
                >
                  <PooIcon
                    color="blue.500"
                    boxSize="5"
                    mt={['6px', 0]}
                    ml={['6px', 0]}
                  />
                </Box>
              </WideLink>
              <WideLink
                label={t('Terminate')}
                onClick={onOpenKillForm}
                isDisabled={!canTerminate}
              >
                <Box
                  boxSize={[8, 5]}
                  backgroundColor={['brandBlue.10', 'initial']}
                  borderRadius="10px"
                >
                  <DeleteIcon
                    color="blue.500"
                    boxSize={5}
                    mt={['6px', 0]}
                    ml={['6px', 0]}
                  />
                </Box>
              </WideLink>
            </Stack>
          </Stack>
        </Stack>

        <KillForm isOpen={isOpenKillForm} onClose={onCloseKillForm}></KillForm>

        <ActivateInvitationDialog {...activateInviteDisclosure} />

        <ReplenishStakeDrawer
          {...replenishStakeDisclosure}
          onSuccess={React.useCallback(
            hash => {
              toast({
                title: t('Transaction sent'),
                description: hash,
              })
              onCloseReplenishStakeDisclosure()
            },
            [onCloseReplenishStakeDisclosure, t, toast]
          )}
          onError={failToast}
        />

        <SpoilInviteDrawer
          {...spoilInviteDisclosure}
          onSuccess={() => {
            successToast(t('Invitation is successfully spoiled'))
            spoilInviteDisclosure.onClose()
          }}
          onFail={failToast}
        />
      </Page>
    </Layout>
  )
}