Repository: usememos/memos
File: web/src/pages/Attachments.tsx (TypeScript)

Code before:
    } catch (error) {
      console.error("Failed to load more attachments:", error);
      toast.error("Failed to load more attachments. Please try again.");
    } finally {
      setIsLoadingMore(false);
    }
  };

  return (
    <section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
      {!md && <MobileHeader />}
      <div className="w-full px-4 sm:px-6">
        <div className="w-full border border-border flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-background text-foreground">
          <div className="relative w-full flex flex-row justify-between items-center">
            <p className="py-1 flex flex-row justify-start items-center select-none opacity-80">
              <PaperclipIcon className="w-6 h-auto mr-1 opacity-80" />
              <span className="text-lg">{t("common.attachments")}</span>
            </p>
            <div>
              <div className="relative max-w-32">
                <SearchIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
                <Input
                  className="pl-9"
                  placeholder={t("common.search")}
                  value={state.searchQuery}
                  onChange={(e) => setState({ ...state, searchQuery: e.target.value })}
                />
              </div>
            </div>
          </div>
          <div className="w-full flex flex-col justify-start items-start mt-4 mb-6">
            {loadingState.isLoading ? (
              <div className="w-full h-32 flex flex-col justify-center items-center">
                <p className="w-full text-center text-base my-6 mt-8">{t("resource.fetching-data")}</p>
              </div>
            ) : (
              <>
                {filteredAttachments.length === 0 ? (
                  <div className="w-full mt-8 mb-8 flex flex-col justify-center items-center italic">
                    <Empty />
                    <p className="mt-4 text-muted-foreground">{t("message.no-data")}</p>
                  </div>
                ) : (
                  <>
                    <div className={"w-full h-auto px-2 flex flex-col justify-start items-start gap-y-8"}>
                      {Array.from(groupedAttachments.entries()).map(([monthStr, attachments]) => {
                        return (
                          <div key={monthStr} className="w-full flex flex-row justify-start items-start">
                            <div className="w-16 sm:w-24 pt-4 sm:pl-4 flex flex-col justify-start items-start">
                              <span className="text-sm opacity-60">{dayjs(monthStr).year()}</span>
                              <span className="font-medium text-xl">

Review comment (refactor):
Unfortunately attachments are not fetched and stored in `mobx`, but rather local state, so I've had to manually refetch the data on change.

Long-term ideally this whole file is refactored to use the respective `mobx` state, so that manual/local state management is not needed.

Code after:
        pageToken: nextPageToken,
      });
      setAttachments((prevAttachments) => [...prevAttachments, ...fetchedAttachments]);
      setNextPageToken(newPageToken ?? "");
    } catch (error) {
      console.error("Failed to load more attachments:", error);
      toast.error("Failed to load more attachments. Please try again.");
    } finally {
      setIsLoadingMore(false);
    }
  };

  const handleRefetch = async () => {
    try {
      loadingState.setLoading();
      const { attachments: fetchedAttachments, nextPageToken } = await attachmentServiceClient.listAttachments({
        pageSize: 50,
      });
      setAttachments(fetchedAttachments);
      setNextPageToken(nextPageToken ?? "");
      loadingState.setFinish();
    } catch (error) {
      console.error(error);
      loadingState.setError();
    }
  };

  const handleDeleteUnusedAttachments = async () => {
    try {
      await Promise.all(
        unusedAttachments.map((attachment) => {
          return attachmentStore.deleteAttachment(attachment.name);
        }),
      );
      toast.success(t("resource.delete-all-unused-success"));
    } catch (error) {
      console.error(error);
      toast.error(t("resource.delete-all-unused-error"));
    } finally {
      void handleRefetch();
    }
  };

  return (
    <section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
      {!md && <MobileHeader />}
      <div className="w-full px-4 sm:px-6">
        <div className="w-full border border-border flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-background text-foreground">
          <div className="relative w-full flex flex-row justify-between items-center">
            <p className="py-1 flex flex-row justify-start items-center select-none opacity-80">
              <PaperclipIcon className="w-6 h-auto mr-1 opacity-80" />