react-query#useInfiniteQuery TypeScript Examples

The following examples show how to use react-query#useInfiniteQuery. 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: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteAllHustlersQuery = <
      TData = AllHustlersQuery,
      TError = unknown
    >(
      variables?: AllHustlersQueryVariables,
      options?: UseInfiniteQueryOptions<AllHustlersQuery, TError, TData>
    ) =>{
    const query = useFetchData<AllHustlersQuery, AllHustlersQueryVariables>(AllHustlersDocument)
    return useInfiniteQuery<AllHustlersQuery, TError, TData>(
      variables === undefined ? ['AllHustlers.infinite'] : ['AllHustlers.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #2
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteSearchDopeQuery = <
      TData = SearchDopeQuery,
      TError = unknown
    >(
      variables: SearchDopeQueryVariables,
      options?: UseInfiniteQueryOptions<SearchDopeQuery, TError, TData>
    ) =>{
    const query = useFetchData<SearchDopeQuery, SearchDopeQueryVariables>(SearchDopeDocument)
    return useInfiniteQuery<SearchDopeQuery, TError, TData>(
      ['SearchDope.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #3
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteRenderDopeQuery = <
      TData = RenderDopeQuery,
      TError = unknown
    >(
      variables?: RenderDopeQueryVariables,
      options?: UseInfiniteQueryOptions<RenderDopeQuery, TError, TData>
    ) =>{
    const query = useFetchData<RenderDopeQuery, RenderDopeQueryVariables>(RenderDopeDocument)
    return useInfiniteQuery<RenderDopeQuery, TError, TData>(
      variables === undefined ? ['RenderDope.infinite'] : ['RenderDope.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #4
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteProfileGearQuery = <
      TData = ProfileGearQuery,
      TError = unknown
    >(
      variables?: ProfileGearQueryVariables,
      options?: UseInfiniteQueryOptions<ProfileGearQuery, TError, TData>
    ) =>{
    const query = useFetchData<ProfileGearQuery, ProfileGearQueryVariables>(ProfileGearDocument)
    return useInfiniteQuery<ProfileGearQuery, TError, TData>(
      variables === undefined ? ['ProfileGear.infinite'] : ['ProfileGear.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #5
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteProfileHustlersQuery = <
      TData = ProfileHustlersQuery,
      TError = unknown
    >(
      variables?: ProfileHustlersQueryVariables,
      options?: UseInfiniteQueryOptions<ProfileHustlersQuery, TError, TData>
    ) =>{
    const query = useFetchData<ProfileHustlersQuery, ProfileHustlersQueryVariables>(ProfileHustlersDocument)
    return useInfiniteQuery<ProfileHustlersQuery, TError, TData>(
      variables === undefined ? ['ProfileHustlers.infinite'] : ['ProfileHustlers.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #6
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteProfileDopesQuery = <
      TData = ProfileDopesQuery,
      TError = unknown
    >(
      variables?: ProfileDopesQueryVariables,
      options?: UseInfiniteQueryOptions<ProfileDopesQuery, TError, TData>
    ) =>{
    const query = useFetchData<ProfileDopesQuery, ProfileDopesQueryVariables>(ProfileDopesDocument)
    return useInfiniteQuery<ProfileDopesQuery, TError, TData>(
      variables === undefined ? ['ProfileDopes.infinite'] : ['ProfileDopes.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #7
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteItemQuery = <
      TData = ItemQuery,
      TError = unknown
    >(
      variables?: ItemQueryVariables,
      options?: UseInfiniteQueryOptions<ItemQuery, TError, TData>
    ) =>{
    const query = useFetchData<ItemQuery, ItemQueryVariables>(ItemDocument)
    return useInfiniteQuery<ItemQuery, TError, TData>(
      variables === undefined ? ['Item.infinite'] : ['Item.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #8
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteHustlersWalletQuery = <
      TData = HustlersWalletQuery,
      TError = unknown
    >(
      variables?: HustlersWalletQueryVariables,
      options?: UseInfiniteQueryOptions<HustlersWalletQuery, TError, TData>
    ) =>{
    const query = useFetchData<HustlersWalletQuery, HustlersWalletQueryVariables>(HustlersWalletDocument)
    return useInfiniteQuery<HustlersWalletQuery, TError, TData>(
      variables === undefined ? ['HustlersWallet.infinite'] : ['HustlersWallet.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #9
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteHustlerQuery = <
      TData = HustlerQuery,
      TError = unknown
    >(
      variables?: HustlerQueryVariables,
      options?: UseInfiniteQueryOptions<HustlerQuery, TError, TData>
    ) =>{
    const query = useFetchData<HustlerQuery, HustlerQueryVariables>(HustlerDocument)
    return useInfiniteQuery<HustlerQuery, TError, TData>(
      variables === undefined ? ['Hustler.infinite'] : ['Hustler.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #10
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteDopesQuery = <
      TData = DopesQuery,
      TError = unknown
    >(
      variables?: DopesQueryVariables,
      options?: UseInfiniteQueryOptions<DopesQuery, TError, TData>
    ) =>{
    const query = useFetchData<DopesQuery, DopesQueryVariables>(DopesDocument)
    return useInfiniteQuery<DopesQuery, TError, TData>(
      variables === undefined ? ['Dopes.infinite'] : ['Dopes.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #11
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteDopeListingQuery = <
      TData = DopeListingQuery,
      TError = unknown
    >(
      variables?: DopeListingQueryVariables,
      options?: UseInfiniteQueryOptions<DopeListingQuery, TError, TData>
    ) =>{
    const query = useFetchData<DopeListingQuery, DopeListingQueryVariables>(DopeListingDocument)
    return useInfiniteQuery<DopeListingQuery, TError, TData>(
      variables === undefined ? ['DopeListing.infinite'] : ['DopeListing.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #12
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteAllItemsQuery = <
      TData = AllItemsQuery,
      TError = unknown
    >(
      variables?: AllItemsQueryVariables,
      options?: UseInfiniteQueryOptions<AllItemsQuery, TError, TData>
    ) =>{
    const query = useFetchData<AllItemsQuery, AllItemsQueryVariables>(AllItemsDocument)
    return useInfiniteQuery<AllItemsQuery, TError, TData>(
      variables === undefined ? ['AllItems.infinite'] : ['AllItems.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #13
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteDrugsQuery = <
      TData = DrugsQuery,
      TError = unknown
    >(
      variables?: DrugsQueryVariables,
      options?: UseInfiniteQueryOptions<DrugsQuery, TError, TData>
    ) =>{
    const query = useFetchData<DrugsQuery, DrugsQueryVariables>(DrugsDocument)
    return useInfiniteQuery<DrugsQuery, TError, TData>(
      variables === undefined ? ['Drugs.infinite'] : ['Drugs.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #14
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteDrugQuery = <
      TData = DrugQuery,
      TError = unknown
    >(
      variables?: DrugQueryVariables,
      options?: UseInfiniteQueryOptions<DrugQuery, TError, TData>
    ) =>{
    const query = useFetchData<DrugQuery, DrugQueryVariables>(DrugDocument)
    return useInfiniteQuery<DrugQuery, TError, TData>(
      variables === undefined ? ['Drug.infinite'] : ['Drug.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #15
Source File: graphql.ts    From dope-monorepo with GNU General Public License v3.0 6 votes vote down vote up
useInfiniteWalletQuery = <
      TData = WalletQuery,
      TError = unknown
    >(
      variables?: WalletQueryVariables,
      options?: UseInfiniteQueryOptions<WalletQuery, TError, TData>
    ) =>{
    const query = useFetchData<WalletQuery, WalletQueryVariables>(WalletDocument)
    return useInfiniteQuery<WalletQuery, TError, TData>(
      variables === undefined ? ['Wallet.infinite'] : ['Wallet.infinite', variables],
      (metaData) => query({...variables, ...(metaData.pageParam ?? {})}),
      options
    )}
Example #16
Source File: useApiInfinite.ts    From kinopub.webos with MIT License 6 votes vote down vote up
function useApiInfinite<T extends Method>(
  method: T,
  params: Parameters<ApiClient[T]> = [] as Parameters<ApiClient[T]>,
  options?: UseInfiniteQueryOptions<Methods[T]>,
) {
  const client = useMemo(() => new ApiClient(), []);
  const query = useInfiniteQuery<Methods[T], string, Methods[T]>(
    [method, ...params],
    ({ pageParam }) => {
      // @ts-expect-error
      return client[method](...params, pageParam) as Methods[T];
    },
    {
      // @ts-expect-error
      getNextPageParam: (lastPage: PaginationResponse) => {
        return lastPage?.pagination?.current + 1 || 1;
      },
      ...options,
    },
  );

  return query;
}
Example #17
Source File: useInfiniteReadingHistory.ts    From apps with GNU Affero General Public License v3.0 5 votes vote down vote up
function useInfiniteReadingHistory({
  key,
  query,
  variables,
}: UseInifiniteReadingHistoryProps): UseInfiniteReadingHistory {
  const { isLoading, isFetchingNextPage, hasNextPage, data, fetchNextPage } =
    useInfiniteQuery<ReadHistoryData>(
      key,
      ({ pageParam }) =>
        request(`${apiUrl}/graphql`, query, {
          ...variables,
          after: pageParam,
        }),
      {
        getNextPageParam: (lastPage) =>
          lastPage?.readHistory?.pageInfo.hasNextPage &&
          lastPage?.readHistory?.pageInfo.endCursor,
      },
    );

  const canFetchMore =
    !isLoading && !isFetchingNextPage && hasNextPage && data.pages.length > 0;

  const infiniteScrollRef = useFeedInfiniteScroll({
    fetchPage: fetchNextPage,
    canFetchMore,
  });

  const hasData = data?.pages?.some(
    (page) => page.readHistory.edges.length > 0,
  );

  return useMemo(
    () => ({
      hasData,
      isLoading,
      data,
      isInitialLoading: !hasData && isLoading,
      infiniteScrollRef,
    }),
    [hasData, isLoading, data, infiniteScrollRef],
  );
}
Example #18
Source File: CommentsSection.tsx    From apps with GNU Affero General Public License v3.0 5 votes vote down vote up
export default function CommentsSection({
  userId,
  tokenRefreshed,
  isSameUser,
  numComments,
}: CommentsSectionProps): ReactElement {
  const comments = useInfiniteQuery<UserCommentsData>(
    ['user_comments', userId],
    ({ pageParam }) =>
      request(`${apiUrl}/graphql`, USER_COMMENTS_QUERY, {
        userId,
        first: 3,
        after: pageParam,
      }),
    {
      enabled: !!userId && tokenRefreshed,
      getNextPageParam: (lastPage) =>
        lastPage.page.pageInfo.hasNextPage && lastPage.page.pageInfo.endCursor,
    },
  );

  return (
    <ActivitySection
      title={`${isSameUser ? 'Your ' : ''}Comments`}
      query={comments}
      count={numComments}
      emptyScreen={
        <EmptyMessage data-testid="emptyComments">
          {isSameUser ? `You didn't comment yet.` : 'No comments yet.'}
        </EmptyMessage>
      }
      elementToNode={(comment) => (
        <CommentContainer key={comment.id}>
          <div className="flex flex-col tablet:flex-row tablet:justify-center items-center py-2 w-12 tablet:w-20 font-bold rounded-xl bg-theme-bg-secondary typo-callout">
            <UpvoteIcon className="tablet:mr-1 mb-1 tablet:mb-0 text-2xl icon" />
            {largeNumberFormat(comment.numUpvotes)}
          </div>
          <Link href={comment.permalink} passHref prefetch={false}>
            <a className={commentInfoClass} aria-label={comment.content}>
              <CommentContent>{comment.content}</CommentContent>
              <CommentTime dateTime={comment.createdAt}>
                {format(new Date(comment.createdAt), 'MMM d, y')}
              </CommentTime>
            </a>
          </Link>
        </CommentContainer>
      )}
    />
  );
}
Example #19
Source File: UpvotedPopupModal.tsx    From apps with GNU Affero General Public License v3.0 5 votes vote down vote up
export function UpvotedPopupModal({
  listPlaceholderProps,
  onRequestClose,
  requestQuery: { queryKey, query, params, options = {} },
  children,
  ...modalProps
}: UpvotedPopupModalProps): ReactElement {
  const { requestMethod } = useRequestProtocol();
  const queryResult = useInfiniteQuery<UpvotesData>(
    queryKey,
    ({ pageParam }) =>
      requestMethod(
        `${apiUrl}/graphql`,
        query,
        { ...params, after: pageParam },
        { requestKey: JSON.stringify(queryKey) },
      ),
    {
      ...options,
      getNextPageParam: (lastPage) =>
        lastPage?.upvotes?.pageInfo?.hasNextPage &&
        lastPage?.upvotes?.pageInfo?.endCursor,
    },
  );

  const [page] = queryResult?.data?.pages || [];
  const container = useRef<HTMLElement>();
  const [modalRef, setModalRef] = useState<HTMLElement>();

  return (
    <ResponsiveModal
      {...modalProps}
      contentRef={(e) => setModalRef(e)}
      onRequestClose={onRequestClose}
      padding={false}
      style={{
        content: {
          maxHeight: '40rem',
          overflow: 'initial',
        },
      }}
    >
      <header className="flex items-center py-4 px-6 w-full border-b border-theme-divider-tertiary">
        <h3 className="font-bold typo-title3">Upvoted by</h3>
        <ModalCloseButton onClick={onRequestClose} />
      </header>
      <section
        className="overflow-auto relative w-full h-full shrink max-h-full"
        data-testid={`List of ${queryKey[0]} with ID ${queryKey[1]}`}
        ref={container}
      >
        {page && page.upvotes.edges.length > 0 ? (
          <UpvoterList
            queryResult={queryResult}
            scrollingContainer={container.current}
            appendTooltipTo={modalRef}
          />
        ) : (
          <UpvoterListPlaceholder {...listPlaceholderProps} />
        )}
      </section>
    </ResponsiveModal>
  );
}
Example #20
Source File: index.tsx    From next-crud with MIT License 4 votes vote down vote up
Users = () => {
  const { push } = useRouter()
  const { data, fetchNextPage, isFetching, hasNextPage, refetch } =
    useInfiniteQuery<TPaginationResult<User>>(
      'users',
      async ({ pageParam = 1 }) => {
        const data: TPaginationResult<User> = await fetch(
          `/api/users?page=${pageParam}`
        ).then((res) => res.json())

        return data
      },
      {
        getNextPageParam: (lastPage) => {
          const pagination = lastPage.pagination as TPaginationDataPageBased
          return pagination.page === pagination.pageCount
            ? undefined
            : pagination.page + 1
        },
      }
    )

  const allData = useMemo(() => {
    return data?.pages.flatMap((page) => page.data)
  }, [data])

  const onEditUser = (id: User['id']) => {
    push(`/users/${id}`)
  }

  const onDeleteUser = async (id: User['id']) => {
    await fetch(`/api/users/${id}`, {
      method: 'DELETE',
    })
    refetch()
  }

  return (
    <Layout title="Users" backRoute="/">
      <VStack spacing={6} width="100%">
        <Heading>Users</Heading>
        <Flex direction="row" justify="flex-end" width="100%">
          <Button
            colorScheme="green"
            leftIcon={<AddIcon color="white" />}
            onClick={() => push('/users/create')}
          >
            Create user
          </Button>
        </Flex>
        <VStack
          boxShadow="0px 2px 8px #ccc"
          p={4}
          borderRadius={6}
          width="100%"
          align="flex-start"
        >
          {!data && (
            <Stack width="100%">
              <Skeleton height="20px" />
              <Skeleton height="20px" />
              <Skeleton height="20px" />
            </Stack>
          )}
          {allData?.map((user) => (
            <UserListItem
              key={user.id}
              {...user}
              onEdit={onEditUser}
              onDelete={onDeleteUser}
            />
          ))}
        </VStack>
        <Button
          colorScheme="blue"
          onClick={() => fetchNextPage()}
          disabled={isFetching || !hasNextPage}
        >
          Load more
        </Button>
      </VStack>
    </Layout>
  )
}
Example #21
Source File: DashboardsPage.tsx    From kubenav with MIT License 4 votes vote down vote up
DashboardsPage: React.FunctionComponent = () => {
  const context = useContext<IContext>(AppContext);
  const cluster = context.currentCluster();

  const [searchText, setSearchText] = useState<string>('');

  const fetchItems = async (cursor) =>
    await kubernetesRequest(
      'GET',
      `${
        context.settings.prometheusDashboardsNamespace
          ? `/api/v1/namespaces/${context.settings.prometheusDashboardsNamespace}/configmaps`
          : `/api/v1/configmaps`
      }?labelSelector=kubenav.io/prometheus-dashboard=true&limit=50${
        cursor.pageParam ? `&continue=${cursor.pageParam}` : ''
      }`,
      '',
      context.settings,
      await context.kubernetesAuthWrapper(''),
    );

  const { isError, isFetching, isFetchingNextPage, hasNextPage, data, error, fetchNextPage, refetch } =
    useInfiniteQuery(
      `PrometheusDashboardsPage_${cluster ? cluster.id : ''}_${cluster ? cluster.namespace : ''}`,
      fetchItems,
      {
        refetchInterval: context.settings.queryRefetchInterval,
        getNextPageParam: (lastGroup) =>
          lastGroup.metadata && lastGroup.metadata.continue ? lastGroup.metadata.continue : false,
      },
    );

  const doRefresh = async (event: CustomEvent<RefresherEventDetail>) => {
    event.detail.complete();
    refetch();
  };

  const loadMore = async (event: CustomEvent<void>) => {
    await fetchNextPage();
    (event.target as HTMLIonInfiniteScrollElement).complete();
  };

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonButtons slot="start">
            <IonMenuButton />
          </IonButtons>
          <IonTitle>Prometheus</IonTitle>
          <IonButtons slot="primary">
            <Details refresh={refetch} />
          </IonButtons>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        {isFetching ? <IonProgressBar slot="fixed" type="indeterminate" color="primary" /> : null}
        <IonRefresher slot="fixed" onIonRefresh={doRefresh} />

        {!isError && cluster ? (
          <React.Fragment>
            <IonSearchbar
              inputmode="search"
              value={searchText}
              onIonChange={(e) => setSearchText(e.detail.value ? e.detail.value : '')}
            />
            {data && data.pages ? (
              <IonList>
                {data.pages.map((group, i) => (
                  <React.Fragment key={i}>
                    {group && group.items
                      ? group.items
                          .filter((item) => {
                            const regex = new RegExp(searchText, 'gi');
                            return item.metadata && item.metadata.name && item.metadata.name.match(regex);
                          })
                          .map((item: V1ConfigMap, j) => {
                            return (
                              <IonItem
                                key={`${i}_${j}`}
                                routerLink={`/plugins/prometheus/${item.metadata ? item.metadata.namespace : ''}/${
                                  item.metadata ? item.metadata.name : ''
                                }`}
                                routerDirection="forward"
                              >
                                <IonLabel>
                                  <h2>{item.data && item.data['title'] ? item.data['title'] : ''}</h2>
                                  <p>{item.data && item.data['description'] ? item.data['description'] : ''}</p>
                                </IonLabel>
                              </IonItem>
                            );
                          })
                      : null}
                  </React.Fragment>
                ))}
                <IonInfiniteScroll
                  threshold="25%"
                  disabled={!hasNextPage || (isFetchingNextPage as boolean)}
                  onIonInfinite={loadMore}
                >
                  {(!isFetchingNextPage as boolean) ? (
                    <IonButton size="small" expand="block" fill="clear" onClick={() => fetchNextPage()}>
                      Load more
                    </IonButton>
                  ) : null}
                  <IonInfiniteScrollContent
                    loadingText={`Loading more Prometheus Dashboards...`}
                  ></IonInfiniteScrollContent>
                </IonInfiniteScroll>
              </IonList>
            ) : null}
          </React.Fragment>
        ) : isFetching ? null : (
          <LoadingErrorCard
            cluster={context.cluster}
            clusters={context.clusters}
            error={error as Error}
            icon="/assets/icons/kubernetes/prometheus.png"
            text="Could not get Prometheus Dashboards"
          />
        )}
      </IonContent>
    </IonPage>
  );
}
Example #22
Source File: ListPage.tsx    From kubenav with MIT License 4 votes vote down vote up
ListPage: React.FunctionComponent<IListPageProps> = ({ match }: IListPageProps) => {
  const context = useContext<IContext>(AppContext);
  const cluster = context.currentCluster();

  // Determine one which page we are currently (which items for a resource do we want to show) by the section and type
  // parameter. Get the component 'ResourceItem' we want to render.
  const page = resources[match.params.section].pages[match.params.type];
  const Component = page.listItemComponent;

  // namespace and showNamespace is used to group all items by namespace and to only show the namespace once via the
  // IonItemDivider component.
  let namespace = '';
  let showNamespace = false;

  // searchText is used to search and filter the list of items.
  const [searchText, setSearchText] = useState<string>('');

  const fetchItems = async (cursor) =>
    await kubernetesRequest(
      'GET',
      `${page.listURL(cluster ? cluster.namespace : '')}?limit=50${
        cursor.pageParam ? `&continue=${cursor.pageParam}` : ''
      }`,
      '',
      context.settings,
      await context.kubernetesAuthWrapper(''),
    );

  const { isError, isFetching, isFetchingNextPage, hasNextPage, data, error, fetchNextPage, refetch } =
    useInfiniteQuery(
      // NOTE: Array keys (https://react-query.tanstack.com/docs/guides/queries#array-keys) do not work with
      // useInfiniteQuery, therefore we are creating a string only query key with the values, which normaly are used as
      // query key.
      // ['ListPage', cluster ? cluster.id : '', cluster ? cluster.namespace : '', match.params.section, match.params.type],
      `ListPage_${cluster ? cluster.id : ''}_${cluster ? cluster.namespace : ''}_${match.params.section}_${
        match.params.type
      }`,
      fetchItems,
      {
        refetchInterval: context.settings.queryRefetchInterval,
        getNextPageParam: (lastGroup) =>
          lastGroup.metadata && lastGroup.metadata.continue ? lastGroup.metadata.continue : false,
      },
    );

  // The doRefresh method is used for a manual reload of the items for the corresponding resource. The
  // event.detail.complete() call is required to finish the animation of the IonRefresher component.
  const doRefresh = async (event: CustomEvent<RefresherEventDetail>) => {
    event.detail.complete();
    refetch();
  };

  // allGroups is used to fetch additional items from the Kubernetes API. When the fetchNextPage funtion is finished we
  // have to call the complete() method on the infinite scroll instance.
  const loadMore = async (event: CustomEvent<void>) => {
    await fetchNextPage();
    (event.target as HTMLIonInfiniteScrollElement).complete();
  };

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonButtons slot="start">
            <IonMenuButton />
          </IonButtons>
          <IonTitle>{page.pluralText}</IonTitle>
          <IonButtons slot="primary">
            {isNamespaced(match.params.type) ? <Namespaces /> : null}
            <Details
              refresh={refetch}
              bookmark={{
                title: page.pluralText,
                url: match.url,
                namespace: isNamespaced(match.params.type) ? (cluster ? cluster.namespace : '') : '',
              }}
              type={match.params.type}
              page={page}
            />
          </IonButtons>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        {isFetching ? <IonProgressBar slot="fixed" type="indeterminate" color="primary" /> : null}
        <IonRefresher slot="fixed" onIonRefresh={doRefresh} />

        {!isError && cluster ? (
          <React.Fragment>
            <IonSearchbar
              inputmode="search"
              value={searchText}
              onIonChange={(e) => setSearchText(e.detail.value ? e.detail.value : '')}
            />

            <IonList>
              {data && data.pages
                ? data.pages.map((group, i) => (
                    <React.Fragment key={i}>
                      {group && group.items
                        ? group.items
                            .filter((item) => {
                              if (searchText.startsWith('$')) {
                                const parts = searchText.split(':');
                                if (
                                  parts.length !== 2 ||
                                  (parts.length === 2 && parts[1] === '') ||
                                  getProperty(item, parts[0].substring(2)) === undefined
                                ) {
                                  return false;
                                }

                                const regex = new RegExp(parts[1].trim(), 'gi');
                                return getProperty(item, parts[0].substring(2).trim()).toString().match(regex);
                              } else {
                                const regex = new RegExp(searchText, 'gi');
                                if (match.params.type === 'events') {
                                  return (
                                    item.metadata &&
                                    item.metadata.name &&
                                    (item.metadata.name.match(regex) ||
                                      item.message.match(regex) ||
                                      item.reason.match(regex))
                                  );
                                } else {
                                  return item.metadata && item.metadata.name && item.metadata.name.match(regex);
                                }
                              }
                            })
                            .map((item, j) => {
                              if (
                                isNamespaced(match.params.type) &&
                                item.metadata &&
                                item.metadata.namespace &&
                                item.metadata.namespace !== namespace
                              ) {
                                namespace = item.metadata.namespace;
                                showNamespace = true;
                              } else {
                                showNamespace = false;
                              }

                              return (
                                <IonItemGroup key={j}>
                                  {showNamespace ? (
                                    <IonItemDivider>
                                      <IonLabel>{namespace}</IonLabel>
                                    </IonItemDivider>
                                  ) : null}
                                  <ItemOptions
                                    item={item}
                                    url={page.detailsURL(
                                      item.metadata ? item.metadata.namespace : '',
                                      item.metadata ? item.metadata.name : '',
                                    )}
                                  >
                                    <Component item={item} section={match.params.section} type={match.params.type} />
                                  </ItemOptions>
                                </IonItemGroup>
                              );
                            })
                        : null}
                    </React.Fragment>
                  ))
                : null}
              <IonInfiniteScroll
                threshold="25%"
                disabled={!hasNextPage || (isFetchingNextPage as boolean)}
                onIonInfinite={loadMore}
              >
                {(!isFetchingNextPage as boolean) ? (
                  <IonButton size="small" expand="block" fill="clear" onClick={() => fetchNextPage()}>
                    Load more
                  </IonButton>
                ) : null}
                <IonInfiniteScrollContent loadingText={`Loading more ${page.pluralText}...`}></IonInfiniteScrollContent>
              </IonInfiniteScroll>
            </IonList>
          </React.Fragment>
        ) : isFetching ? null : (
          <LoadingErrorCard
            cluster={context.cluster}
            clusters={context.clusters}
            error={error as Error}
            icon={page.icon}
            text={`Could not get ${page.pluralText}`}
          />
        )}
      </IonContent>
    </IonPage>
  );
}
Example #23
Source File: CustomResourcesListPage.tsx    From kubenav with MIT License 4 votes vote down vote up
CustomResourcesListPage: React.FunctionComponent<ICustomResourcesListPageProps> = ({
  location,
  match,
}: ICustomResourcesListPageProps) => {
  const context = useContext<IContext>(AppContext);
  const cluster = context.currentCluster();

  // scope can be "Cluster" or "Namespaced", for a cluster scoped CRD we have to set the namespace to "" to retrive all
  // CRs.
  const scope = new URLSearchParams(location.search).get('scope');

  // namespace and showNamespace is used to group all items by namespace and to only show the namespace once via the
  // IonItemDivider component.
  let namespace = '';
  let showNamespace = false;

  // searchText is used to search and filter the list of items.
  const [searchText, setSearchText] = useState<string>('');

  const fetchItems = async (cursor) =>
    await kubernetesRequest(
      'GET',
      `${getURL(
        scope === 'Cluster' ? '' : cluster ? cluster.namespace : '',
        match.params.group,
        match.params.version,
        match.params.name,
      )}?limit=50${cursor.pageParam ? `&continue=${cursor.pageParam}` : ''}`,
      '',
      context.settings,
      await context.kubernetesAuthWrapper(''),
    );

  const { isError, isFetching, isFetchingNextPage, hasNextPage, data, error, fetchNextPage, refetch } =
    useInfiniteQuery(
      // NOTE: Array keys (https://react-query.tanstack.com/docs/guides/queries#array-keys) do not work with
      // useInfiniteQuery, therefore we are creating a string only query key with the values, which normaly are used as
      // query key.
      // ['CustomResourcesListPage', cluster ? cluster.id : '', cluster ? cluster.namespace : '', match.params.group, match.params.version, match.params.name],
      `CustomResourcesListPage_${cluster ? cluster.id : ''}_${cluster ? cluster.namespace : ''}_${match.params.group}_${
        match.params.version
      }_${match.params.name}`,
      fetchItems,
      {
        refetchInterval: context.settings.queryRefetchInterval,
        getNextPageParam: (lastGroup) =>
          lastGroup.metadata && lastGroup.metadata.continue ? lastGroup.metadata.continue : false,
      },
    );

  // The doRefresh method is used for a manual reload of the items for the corresponding resource. The
  // event.detail.complete() call is required to finish the animation of the IonRefresher component.
  const doRefresh = async (event: CustomEvent<RefresherEventDetail>) => {
    event.detail.complete();
    refetch();
  };

  // allGroups is used to fetch additional items from the Kubernetes API. When the fetchNextPage funtion is finished we
  // have to call the complete() method on the infinite scroll instance.
  const loadMore = async (event: CustomEvent<void>) => {
    await fetchNextPage();
    (event.target as HTMLIonInfiniteScrollElement).complete();
  };

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonButtons slot="start">
            <IonBackButton defaultHref={`/resources/cluster/customresourcedefinitions`} />
          </IonButtons>
          <IonTitle>{match.params.name}</IonTitle>
          <IonButtons slot="primary">
            <Namespaces />
            <Details
              refresh={refetch}
              bookmark={{
                title: match.params.name,
                url: match.url,
                namespace: cluster ? cluster.namespace : '',
              }}
              type="customresourcedefinitions"
            />
          </IonButtons>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        {isFetching ? <IonProgressBar slot="fixed" type="indeterminate" color="primary" /> : null}
        <IonRefresher slot="fixed" onIonRefresh={doRefresh} />

        {!isError && cluster ? (
          <React.Fragment>
            <IonSearchbar
              inputmode="search"
              value={searchText}
              onIonChange={(e) => setSearchText(e.detail.value ? e.detail.value : '')}
            />

            <IonList>
              {data && data.pages
                ? data.pages.map((group, i) => (
                    <React.Fragment key={i}>
                      {group && group.items
                        ? group.items
                            .filter((item) => {
                              const regex = new RegExp(searchText, 'gi');
                              return item.metadata && item.metadata.name && item.metadata.name.match(regex);
                            })
                            .map((item, j) => {
                              if (item.metadata && item.metadata.namespace && item.metadata.namespace !== namespace) {
                                namespace = item.metadata.namespace;
                                showNamespace = true;
                              } else {
                                showNamespace = false;
                              }

                              return (
                                <IonItemGroup key={j}>
                                  {showNamespace ? (
                                    <IonItemDivider>
                                      <IonLabel>{namespace}</IonLabel>
                                    </IonItemDivider>
                                  ) : null}
                                  <ItemOptions
                                    item={item}
                                    url={`${getURL(
                                      item.metadata ? item.metadata.namespace : '',
                                      match.params.group,
                                      match.params.version,
                                      match.params.name,
                                    )}/${item.metadata ? item.metadata.name : ''}`}
                                  >
                                    <CustomResourceItem
                                      group={match.params.group}
                                      version={match.params.version}
                                      name={match.params.name}
                                      item={item}
                                    />
                                  </ItemOptions>
                                </IonItemGroup>
                              );
                            })
                        : null}
                    </React.Fragment>
                  ))
                : null}
              <IonInfiniteScroll
                threshold="25%"
                disabled={!hasNextPage || (isFetchingNextPage as boolean)}
                onIonInfinite={loadMore}
              >
                <IonInfiniteScrollContent
                  loadingText={`Loading more ${match.params.name}...`}
                ></IonInfiniteScrollContent>
              </IonInfiniteScroll>
            </IonList>
          </React.Fragment>
        ) : isFetching ? null : (
          <LoadingErrorCard
            cluster={context.cluster}
            clusters={context.clusters}
            error={error as Error}
            icon="/assets/icons/kubernetes/crd.png"
            text={`Could not get Custom Resources "${match.params.name}"`}
          />
        )}
      </IonContent>
    </IonPage>
  );
}
Example #24
Source File: KeywordManagement.tsx    From apps with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function KeywordManagement({
  keyword,
  subtitle,
  onOperationCompleted,
}: KeywordManagerProps): ReactElement {
  const { windowLoaded } = useContext(ProgressiveEnhancementContext);
  const [currentAction, setCurrentAction] = useState<string | null>(null);

  const nextKeyword = async () => {
    if (onOperationCompleted) {
      await onOperationCompleted();
    }
    setCurrentAction(null);
  };

  const { mutateAsync: allowKeyword } = useMutation(
    () =>
      request(`${apiUrl}/graphql`, ALLOW_KEYWORD_MUTATION, {
        keyword: keyword.value,
      }),
    {
      onSuccess: () => nextKeyword(),
    },
  );

  const { mutateAsync: denyKeyword } = useMutation(
    () =>
      request(`${apiUrl}/graphql`, DENY_KEYWORD_MUTATION, {
        keyword: keyword.value,
      }),
    {
      onSuccess: () => nextKeyword(),
    },
  );

  const posts = useInfiniteQuery<FeedData>(
    ['keyword_post', keyword.value],
    ({ pageParam }) =>
      request(`${apiUrl}/graphql`, KEYWORD_FEED_QUERY, {
        keyword: keyword.value,
        first: 4,
        after: pageParam,
      }),
    {
      getNextPageParam: (lastPage) =>
        lastPage.page.pageInfo.hasNextPage && lastPage.page.pageInfo.endCursor,
    },
  );

  const onAllow = () => {
    setCurrentAction('allow');
    return allowKeyword();
  };

  const onSynonym = () => setCurrentAction('synonym');

  const onDeny = () => {
    setCurrentAction('deny');
    return denyKeyword();
  };

  const disableActions = !!currentAction;

  return (
    <ResponsivePageContainer style={{ paddingBottom: sizeN(23) }}>
      <NextSeo title="Pending Keywords" nofollow noindex />
      <h1 className="m-0 font-bold typo-title2">{keyword.value}</h1>
      <div className="flex justify-between items-center my-1 text-theme-label-tertiary typo-callout">
        <span>Occurrences: {keyword.occurrences}</span>
        <span>{subtitle}</span>
      </div>
      <ActivitySection
        title="Keyword Posts"
        query={posts}
        emptyScreen={
          <div className="font-bold typo-title3" data-testid="emptyPosts">
            No posts
          </div>
        }
        elementToNode={(post) => (
          <Link
            href={post.commentsPermalink}
            passHref
            key={post.id}
            prefetch={false}
          >
            <a
              target="_blank"
              rel="noopener noreferrer"
              aria-label={post.title}
              className="flex items-start tablet:items-center py-3 no-underline"
            >
              <LazyImage
                imgSrc={smallPostImage(post.image)}
                imgAlt="Post cover image"
                className="w-16 h-16 rounded-2xl"
              />
              <p
                className="flex-1 self-center p-0 tablet:mr-6 ml-4 whitespace-pre-wrap break-words-overflow text-theme-label-primary typo-callout multi-truncate"
                style={{
                  maxHeight: '3.75rem',
                  maxWidth: '19.25rem',
                  lineClamp: 3,
                }}
              >
                {post.title}
              </p>
            </a>
          </Link>
        )}
      />
      <div
        className={classNames(
          'fixed flex left-0 right-0 bottom-0 w-full items-center justify-between mx-auto py-6 px-4 bg-theme-bg-primary',
          styles.buttons,
        )}
      >
        <Button
          loading={currentAction === 'allow'}
          onClick={onAllow}
          disabled={disableActions}
          className="btn-primary"
        >
          Allow
        </Button>
        <Button
          disabled={disableActions}
          onClick={onSynonym}
          className="btn-secondary"
        >
          Synonym
        </Button>
        <Button
          loading={currentAction === 'deny'}
          onClick={onDeny}
          disabled={disableActions}
          className="btn-primary-ketchup"
        >
          Deny
        </Button>
      </div>
      {(windowLoaded || currentAction === 'synonym') && (
        <KeywordSynonymModal
          isOpen={currentAction === 'synonym'}
          selectedKeyword={keyword.value}
          onRequestClose={nextKeyword}
        />
      )}
    </ResponsivePageContainer>
  );
}
Example #25
Source File: useFeed.ts    From apps with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function useFeed<T>(
  feedQueryKey: unknown[],
  pageSize: number,
  adSpot: number,
  placeholdersPerPage: number,
  showOnlyUnreadPosts: boolean,
  query?: string,
  variables?: T,
): FeedReturnType {
  const { user, tokenRefreshed } = useContext(AuthContext);
  const queryClient = useQueryClient();

  const feedQuery = useInfiniteQuery<FeedData>(
    feedQueryKey,
    ({ pageParam }) =>
      request(`${apiUrl}/graphql`, query, {
        ...variables,
        first: pageSize,
        after: pageParam,
        loggedIn: !!user,
        unreadOnly: showOnlyUnreadPosts,
      }),
    {
      enabled: query && tokenRefreshed,
      refetchOnMount: false,
      refetchOnReconnect: false,
      refetchOnWindowFocus: false,
      getNextPageParam: (lastPage) =>
        lastPage.page.pageInfo.hasNextPage && lastPage.page.pageInfo.endCursor,
    },
  );

  const adsQuery = useInfiniteQuery<Ad>(
    ['ads', ...feedQueryKey],
    async () => {
      const res = await fetch(`${apiUrl}/v1/a`);
      const ads: Ad[] = await res.json();
      return ads[0];
    },
    {
      getNextPageParam: () => Date.now(),
      enabled: query && tokenRefreshed,
      refetchOnMount: false,
      refetchOnReconnect: false,
      refetchOnWindowFocus: false,
    },
  );

  useEffect(() => {
    if (
      !adsQuery.isFetching &&
      adsQuery.data?.pages?.length < feedQuery.data?.pages?.length
    ) {
      adsQuery.fetchNextPage();
    }
  }, [adsQuery.data, feedQuery.data, adsQuery.isFetching]);

  const items = useMemo(() => {
    let newItems: FeedItem[] = [];
    if (feedQuery.data) {
      newItems = feedQuery.data.pages.flatMap(
        ({ page }, pageIndex): FeedItem[] => {
          const posts: FeedItem[] = page.edges.map(({ node }, index) => ({
            type: 'post',
            post: node,
            page: pageIndex,
            index,
          }));
          if (adsQuery.data?.pages[pageIndex]) {
            posts.splice(adSpot, 0, {
              type: 'ad',
              ad: adsQuery.data?.pages[pageIndex],
            });
          } else {
            posts.splice(adSpot, 0, {
              type: 'placeholder',
            });
          }
          return posts;
        },
      );
    }
    if (feedQuery.isFetching) {
      newItems.push(
        ...Array(placeholdersPerPage).fill({ type: 'placeholder' }),
      );
    }
    return newItems;
  }, [
    feedQuery.data,
    feedQuery.isFetching,
    adsQuery.data,
    adsQuery.isFetching,
  ]);

  const updatePost = updateCachedPost(feedQueryKey, queryClient);

  useSubscription(
    () => ({
      query: POSTS_ENGAGED_SUBSCRIPTION,
    }),
    {
      next: (data: PostsEngaged) => {
        const { pageIndex, index } = findIndexOfPostInData(
          feedQuery.data,
          data.postsEngaged.id,
        );
        if (index > -1) {
          updatePost(pageIndex, index, {
            ...feedQuery.data.pages[pageIndex].page.edges[index].node,
            ...data.postsEngaged,
          });
        }
      },
    },
  );

  return {
    items,
    fetchPage: async () => {
      const adPromise = adsQuery.fetchNextPage();
      await feedQuery.fetchNextPage();
      await adPromise;
    },
    updatePost,
    removePost: removeCachedPost(feedQueryKey, queryClient),
    canFetchMore: feedQuery.hasNextPage,
    emptyFeed:
      !feedQuery?.data?.pages[0]?.page.edges.length && !feedQuery.isFetching,
  };
}
Example #26
Source File: PostsSection.tsx    From apps with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function PostsSection({
  userId,
  isSameUser,
  numPosts,
}: PostsSectionProps): ReactElement {
  const { windowLoaded } = useContext(ProgressiveEnhancementContext);
  const { user, updateUser, tokenRefreshed } = useContext(AuthContext);

  const formRef = useRef<HTMLFormElement>(null);
  const [disableSubmit, setDisableSubmit] = useState<boolean>(true);
  const [twitterHint, setTwitterHint] = useState<string>();
  const [showAccountDetails, setShowAccountDetails] = useState(false);

  const updateDisableSubmit = () => {
    if (formRef.current) {
      setDisableSubmit(!formRef.current.checkValidity());
    }
  };

  const onSubmit = async (event: FormEvent): Promise<void> => {
    event.preventDefault();
    setDisableSubmit(true);
    const data = formToJson<UserProfile>(
      formRef.current,
      loggedUserToProfile(user),
    );

    const res = await updateProfile(data);
    if ('error' in res) {
      if ('code' in res && res.code === 1) {
        if (res.field === 'twitter') {
          setTwitterHint('This Twitter handle is already used');
        } else {
          setTwitterHint('Please contact us [email protected]');
        }
      }
    } else {
      await updateUser({ ...user, ...res });
      setDisableSubmit(false);
    }
  };

  const posts = useInfiniteQuery<FeedData>(
    ['user_posts', userId],
    ({ pageParam }) =>
      request(`${apiUrl}/graphql`, AUTHOR_FEED_QUERY, {
        userId,
        first: 3,
        after: pageParam,
      }),
    {
      enabled: !!userId && tokenRefreshed,
      getNextPageParam: (lastPage) =>
        lastPage.page.pageInfo.hasNextPage && lastPage.page.pageInfo.endCursor,
    },
  );

  let postsEmptyScreen: ReactNode;
  if (!isSameUser) {
    postsEmptyScreen = (
      <EmptyMessage data-testid="emptyPosts">No articles yet.</EmptyMessage>
    );
  } else if (user.twitter) {
    postsEmptyScreen = (
      <p
        className="typo-callout text-theme-label-tertiary"
        data-testid="emptyPosts"
      >
        No articles yet.
        <br />
        <br />
        <a
          className="no-underline text-theme-label-link"
          href={ownershipGuide}
          target="_blank"
          rel="noopener"
        >
          How daily.dev picks up new articles
        </a>
        <br />
        <br />
        Do you have articles you wrote that got picked up by daily.dev in the
        past?
        <br />
        <br />
        <a
          className="no-underline text-theme-label-link"
          href="mailto:[email protected]?subject=Add my articles retroactively&body=README: To add your articles retroactively, please reply with your username or a link to your profile on daily.dev. Keep in mind that we can only add articles that we're already picked up by daily.dev. Not sure if your article appeared in our feed? Try searching its headline here: https://app.daily.dev/search"
          target="_blank"
          rel="noopener"
        >
          Email us to add your articles retroactively
        </a>
      </p>
    );
  } else {
    postsEmptyScreen = (
      <>
        <EmptyMessage data-testid="emptyPosts">
          {`Track when articles you published are getting picked by
          daily.dev. Set up your Twitter handle and we'll do the rest ?`}
        </EmptyMessage>
        {user.email && user.username ? (
          <form
            className="flex flex-col items-start mt-6"
            ref={formRef}
            onSubmit={onSubmit}
          >
            <TextField
              inputId="twitter"
              name="twitter"
              label="Twitter"
              value={user.twitter}
              hint={twitterHint}
              valid={!twitterHint}
              placeholder="handle"
              pattern="(\w){1,15}"
              maxLength={15}
              validityChanged={updateDisableSubmit}
              valueChanged={() => twitterHint && setTwitterHint(null)}
              className="self-stretch mb-4"
              style={{ maxWidth: sizeN(78) }}
            />
            <Button
              className="btn-primary"
              type="submit"
              disabled={disableSubmit}
              style={{ width: sizeN(30) }}
            >
              Save
            </Button>
          </form>
        ) : (
          <>
            <button
              type="button"
              className="self-start mt-4 btn-primary"
              onClick={() => setShowAccountDetails(true)}
            >
              Complete your profile
            </button>
            {(windowLoaded || showAccountDetails) && (
              <AccountDetailsModal
                isOpen={showAccountDetails}
                onRequestClose={() => setShowAccountDetails(false)}
              />
            )}
          </>
        )}
      </>
    );
  }

  return (
    <ActivitySection
      title={`${isSameUser ? 'Your ' : ''}Articles`}
      query={posts}
      count={numPosts}
      emptyScreen={postsEmptyScreen}
      elementToNode={(post) => (
        <Link
          href={post.commentsPermalink}
          passHref
          key={post.id}
          prefetch={false}
        >
          <a
            className={`${commentContainerClass} pl-3 no-underline`}
            aria-label={post.title}
          >
            <div className="relative">
              <LazyImage
                imgSrc={smallPostImage(post.image)}
                imgAlt="Post cover image"
                className={`rounded-2xl ${styles.postImage}`}
              />
              <LazyImage
                imgSrc={post.source.image}
                imgAlt={post.source.name}
                className={`top-1/2 left-0 w-8 h-8 bg-theme-bg-primary rounded-full -translate-x-1/2 -translate-y-1/2 ${styles.sourceImage}`}
                absolute
              />
            </div>
            <div className={commentInfoClass}>
              <CommentContent className={styles.postContent}>
                {post.title}
              </CommentContent>
              <div className="grid grid-flow-col auto-cols-max gap-x-4 mt-3 tablet:mt-0 tablet:ml-auto">
                {post.views !== null && (
                  <PostStat>
                    <EyeIcon className={postStatIconClass} />
                    {largeNumberFormat(post.views)}
                  </PostStat>
                )}
                <PostStat>
                  <UpvoteIcon className={postStatIconClass} />
                  {largeNumberFormat(post.numUpvotes)}
                </PostStat>
                <PostStat>
                  <CommentIcon className={postStatIconClass} />
                  {largeNumberFormat(post.numComments)}
                </PostStat>
              </div>
            </div>
          </a>
        </Link>
      )}
    />
  );
}
Example #27
Source File: MessagesScreen.tsx    From vsinder with Apache License 2.0 4 votes vote down vote up
MessagesScreen: React.FC<MatchesStackNav<"messages">> = ({
  route: { params },
  navigation,
}) => {
  const qKey = `/messages/${params.id}`;
  const {
    data,
    isLoading,
    isFetchingMore,
    fetchMore,
  } = useInfiniteQuery<IMessageResponse>(
    qKey,
    (key, cursor = "") =>
      defaultQueryFn(`${key}/${cursor}`).then((x) => ({
        hasMore: x.hasMore,
        messages: x.messages.map((m: Message) =>
          messageToGiftedMessage(m, { me: meData!.user!, them: params })
        ),
      })),
    {
      staleTime: 0,
      getFetchMore: ({ messages, hasMore }) =>
        hasMore && messages.length
          ? messages[messages.length - 1].createdAt
          : "",
    }
  );
  const { data: meData } = useQuery<MeResponse>("/me", defaultQueryFn);
  const cache = useQueryCache();
  const [mutate] = useMutation(defaultMutationFn);
  const {
    inputForeground,
    inputBackground,
    buttonBackground,
    buttonForeground,
    buttonSecondaryBackground,
    buttonSecondaryForeground,
    buttonForegroundDarker,
    buttonSecondaryForegroundDarker,
  } = useTheme();

  useOnWebSocket((e) => {
    if (e.type === "new-message" && e.message.senderId === params.id) {
      cache.setQueryData<IMessageResponse[]>(qKey, (d) => {
        return produce(d!, (x) => {
          x[0].messages = GiftedChat.append(x[0].messages, [
            messageToGiftedMessage(e.message, {
              me: meData!.user!,
              them: params,
            }),
          ]);
        });
      });
    } else if (e.type === "unmatch") {
      if (e.userId === params.id) {
        navigation.goBack();
      }
    }
  });

  useEffect(() => {
    getSocket().send(
      JSON.stringify({ type: "message-open", userId: params.id })
    );
    const d = cache.getQueryData<MatchesResponse>("/matches/0");
    if (d && d.matches.find((x) => x.userId === params.id && !x.read)) {
      cache.setQueryData<MatchesResponse>("/matches/0", {
        matches: d.matches.map((m) =>
          m.userId === params.id ? { ...m, read: true } : m
        ),
      });
    }

    return () => {
      getSocket().send(JSON.stringify({ type: "message-open", userId: null }));
    };
  }, []);

  if (isLoading) {
    return <FullscreenLoading />;
  }

  if (!meData?.user) {
    return null;
  }

  const messages = data ? data.map((x) => x.messages).flat() : [];

  return (
    <ScreenWrapper noPadding>
      <GiftedChat
        alignTop
        loadEarlier={data?.[data?.length - 1]?.hasMore}
        onPressAvatar={(u) =>
          navigation.navigate("viewCard", { id: u._id.toString() })
        }
        isLoadingEarlier={!!isFetchingMore}
        renderLoadEarlier={({ isLoadingEarlier }) =>
          isLoadingEarlier ? (
            <Loading />
          ) : (
            <MyButton onPress={() => fetchMore()}>load more</MyButton>
          )
        }
        listViewProps={{
          showsVerticalScrollIndicator: false,
        }}
        timeTextStyle={{
          left: { color: buttonSecondaryForegroundDarker },
          right: { color: buttonForegroundDarker },
        }}
        renderBubble={(props) => {
          return (
            <Bubble
              {...props}
              textStyle={{
                right: {
                  color: buttonForeground,
                },
                left: {
                  color: buttonSecondaryForeground,
                },
              }}
              wrapperStyle={{
                left: {
                  backgroundColor: buttonSecondaryBackground,
                },
                right: {
                  backgroundColor: buttonBackground,
                },
              }}
            />
          );
        }}
        renderSend={(props) => (
          <Send
            {...props}
            containerStyle={{
              justifyContent: "center",
              alignItems: "center",
              alignSelf: "center",
              marginRight: 15,
            }}
          >
            <MaterialIcons name="send" size={24} color={buttonBackground} />
          </Send>
        )}
        // @ts-ignore
        containerStyle={{
          backgroundColor: inputBackground,
        }}
        textInputStyle={{
          color: inputForeground,
          backgroundColor: inputBackground,
        }}
        // @ts-ignore
        renderTicks={() => null}
        messages={messages}
        onSend={(messages) => {
          messages.forEach((m) => {
            mutate([
              "/message",
              { recipientId: params.id, text: m.text, matchId: params.matchId },
              "POST",
            ]).then(({ message: newMessage }) => {
              cache.setQueryData<IMessageResponse[]>(qKey, (d) => {
                return produce(d!, (x) => {
                  x[0].messages = GiftedChat.append(x[0].messages, [
                    messageToGiftedMessage(newMessage, {
                      me: meData.user!,
                      them: params,
                    }),
                  ]);
                });
              });
              const d = cache.getQueryData<MatchesResponse>("/matches/0");
              if (d) {
                cache.setQueryData<MatchesResponse>("/matches/0", {
                  matches: d.matches.map((m) =>
                    m.userId === params.id
                      ? {
                          ...m,
                          message: {
                            text: newMessage.text,
                            createdAt: newMessage.createdAt,
                          },
                        }
                      : m
                  ),
                });
              }
            });
          });
        }}
        user={{
          _id: meData.user.id,
        }}
      />
    </ScreenWrapper>
  );
}
Example #28
Source File: index.tsx    From basement-grotesque with SIL Open Font License 1.1 4 votes vote down vote up
DataColumns = ({ tweets: initialTweets, releases }: DataColumnsProps) => {
  const {
    data: queriedTweets,
    fetchNextPage,
    hasNextPage,
    isFetching
  } = useInfiniteQuery(['tweets'], ({ pageParam }) => getTweets(pageParam), {
    enabled: !!initialTweets,
    refetchOnWindowFocus: false,
    initialData: {
      pageParams: [undefined],
      pages: [initialTweets]
    },
    getNextPageParam: (lastPage) => (lastPage ? lastPage.meta.next_token : null)
  })
  const [activeSection, setActiveSection] = useState<Sections>('releases')

  return (
    <Section background="black" noMargin>
      <Container maxWidth>
        <SectionInner
          css={{
            display: 'none',

            '@bp3': {
              display: 'grid'
            }
          }}
        >
          <Column>
            <Text css={{ fontSize: '$3' }} uppercase>
              CHANGELOG
            </Text>
            <Text css={{ fontSize: '$7' }} heading uppercase>
              Version History
            </Text>
            <div>
              {releases.map(({ date, text, version }, idx) => (
                <Release version={version} date={date} text={text} key={idx} />
              ))}
            </div>
          </Column>
          <Column>
            <Text css={{ fontSize: '$3' }} uppercase>
              Stats
            </Text>
            <Text css={{ fontSize: '$7' }} heading uppercase>
              Features status
            </Text>
            <div>
              <Feature title="Family styles" score={1} />
              <Feature title="Character set" score={4} />
              <Feature title="Spacing &amp; Kerning" score={3} />
              <Feature title="Hinting" score={4} />
            </div>
          </Column>
          <Column>
            <Text css={{ fontSize: '$3' }} uppercase>
              #BASEMENTGROTESQUE
            </Text>
            <Text css={{ fontSize: '$7' }} heading uppercase>
              Tweets
            </Text>
            <div>
              {queriedTweets?.pages?.map((page, pageIdx) => {
                if (!page) return null
                return (
                  <Fragment key={pageIdx}>
                    {page.data.map((tweet) => (
                      <Tweet tweet={tweet} key={tweet.id} />
                    ))}
                  </Fragment>
                )
              })}
              {hasNextPage && (
                <LoadMore onClick={() => fetchNextPage()} disabled={isFetching}>
                  <Text uppercase heading>
                    Load more
                  </Text>
                  <ArrowDown
                    css={{
                      marginLeft: 8,
                      color: 'white',
                      path: { fill: 'white' },
                      $$size: '16px'
                    }}
                  />
                </LoadMore>
              )}
            </div>
          </Column>
        </SectionInner>
      </Container>
      <SectionInner
        css={{
          '@bp3': {
            display: 'none'
          }
        }}
      >
        <Box>
          <Container css={{ borderBottom: '1px solid $white' }}>
            <SectionPicker>
              <select
                onChange={(e) => {
                  setActiveSection(e.target.value as Sections)
                }}
              >
                {[
                  { key: 'releases', label: 'Version History' },
                  { key: 'features', label: 'Feature Status' },
                  { key: 'tweets', label: 'Tweets' }
                ].map(({ key, label }) => (
                  <option value={key} key={key}>
                    {label}
                  </option>
                ))}
              </select>
              <svg
                width="20"
                height="13"
                viewBox="0 0 20 13"
                fill="none"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  d="M1 1.5L10 10.5L19 1.5"
                  stroke="white"
                  strokeWidth="2.5"
                />
              </svg>
            </SectionPicker>
          </Container>
        </Box>
        <Container>
          <Column
            css={{ display: activeSection === 'releases' ? 'block' : 'none' }}
          >
            {releases.map(({ date, text, version }, idx) => (
              <Release version={version} date={date} text={text} key={idx} />
            ))}
          </Column>
          <Column
            css={{ display: activeSection === 'features' ? 'block' : 'none' }}
          >
            <Feature title="Family styles" score={1} />
            <Feature title="Character set" score={4} />
            <Feature title="Spacing &amp; Kerning" score={3} />
            <Feature title="Hinting" score={4} />
          </Column>
          <Column
            css={{ display: activeSection === 'tweets' ? 'block' : 'none' }}
          >
            {queriedTweets?.pages?.map((page, pageIdx) => {
              if (!page) return null
              return (
                <Fragment key={pageIdx}>
                  {page.data.map((tweet) => (
                    <Tweet tweet={tweet} key={tweet.id} />
                  ))}
                </Fragment>
              )
            })}
            {hasNextPage && (
              <LoadMore onClick={() => fetchNextPage()} disabled={isFetching}>
                <Text uppercase heading>
                  Load more
                </Text>
                <ArrowDown
                  css={{
                    marginLeft: 8,
                    color: 'white',
                    path: { fill: 'white' },
                    $$size: '16px'
                  }}
                />
              </LoadMore>
            )}
          </Column>
        </Container>
      </SectionInner>
    </Section>
  )
}