AppsCleanup.tsx raw

   1  import { SkipForwardIcon, Trash2Icon, TriangleAlertIcon } from "lucide-react";
   2  import React from "react";
   3  import AppHeader from "src/components/AppHeader";
   4  import AppCard from "src/components/connections/AppCard";
   5  import Loading from "src/components/Loading";
   6  import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert";
   7  import { Button } from "src/components/ui/button";
   8  import {
   9    Card,
  10    CardDescription,
  11    CardFooter,
  12    CardHeader,
  13    CardTitle,
  14  } from "src/components/ui/card";
  15  import { LinkButton } from "src/components/ui/custom/link-button";
  16  import { Progress } from "src/components/ui/progress";
  17  import { useDeleteApp } from "src/hooks/useDeleteApp";
  18  import { useUnusedApps } from "src/hooks/useUnusedApps";
  19  import { App } from "src/types";
  20  
  21  export function AppsCleanup() {
  22    const unusedApps = useUnusedApps(100_000); // assume never more than 100k apps
  23    const [appIndex, setAppIndex] = React.useState<number>();
  24    const [skippedCount, setSkippedCount] = React.useState<number>(0);
  25    const [deletedCount, setDeletedCount] = React.useState<number>(0);
  26    const [appsToReview, setAppsToReview] = React.useState<App[]>();
  27    React.useEffect(() => {
  28      if (!unusedApps) {
  29        return;
  30      }
  31      // only allow initializing once
  32      if (appIndex === undefined) {
  33        setAppIndex(0);
  34  
  35        const _appsToReview = [...unusedApps];
  36        _appsToReview.sort((a, b) => {
  37          return (
  38            (a.lastUsedAt ? new Date(a.lastUsedAt).getTime() : 0) -
  39            (b.lastUsedAt ? new Date(b.lastUsedAt).getTime() : 0)
  40          );
  41        });
  42        setAppsToReview(_appsToReview);
  43      }
  44    }, [appIndex, unusedApps]);
  45  
  46    if (!appsToReview || appIndex === undefined) {
  47      return <Loading />;
  48    }
  49  
  50    const currentApp = appsToReview[appIndex];
  51  
  52    return (
  53      <>
  54        <AppHeader
  55          pageTitle="Cleanup Unused Apps"
  56          title="Cleanup Unused Apps"
  57          description="Review apps that haven't been used for 2 months or longer"
  58        />
  59        {currentApp && (
  60          <Alert variant="destructive">
  61            <AlertTitle className="flex gap-2">
  62              <TriangleAlertIcon className="h-4 w-4" />
  63              Warning
  64            </AlertTitle>
  65            <AlertDescription>
  66              Review the app carefully before deleting it, deleted apps cannot be
  67              recovered.
  68            </AlertDescription>
  69          </Alert>
  70        )}
  71        <div className="grid grid-cols-1 lg:grid-cols-5 gap-3">
  72          <div className="lg:col-span-3 flex flex-col gap-3">
  73            {currentApp && (
  74              <>
  75                <div className="w-full h-full">
  76                  <AppCard
  77                    app={currentApp}
  78                    actions={
  79                      <>
  80                        <Button
  81                          onClick={() => {
  82                            setAppIndex(appIndex + 1);
  83                            setSkippedCount((current) => current + 1);
  84                          }}
  85                        >
  86                          <SkipForwardIcon />
  87                          Skip
  88                        </Button>
  89                        <DeleteAppButton
  90                          app={currentApp}
  91                          onDelete={() => {
  92                            setAppIndex(appIndex + 1);
  93                            setDeletedCount((current) => current + 1);
  94                          }}
  95                        />
  96                      </>
  97                    }
  98                    readonly
  99                  />
 100                </div>
 101              </>
 102            )}
 103            {!currentApp && (
 104              <>
 105                <div>No more unused apps to review.</div>
 106                <div>
 107                  <LinkButton to="/apps?tab=connected-apps">
 108                    Back to overview
 109                  </LinkButton>
 110                </div>
 111              </>
 112            )}
 113          </div>
 114          <div className="lg:col-span-2">
 115            <Card className="w-full">
 116              <CardHeader>
 117                <CardTitle>Progress</CardTitle>
 118                <CardDescription>
 119                  {appIndex} of {appsToReview.length} unused apps reviewed (
 120                  {skippedCount} skipped, {deletedCount} deleted)
 121                </CardDescription>
 122              </CardHeader>
 123              <CardFooter>
 124                <Progress value={(appIndex / appsToReview.length) * 100} />
 125              </CardFooter>
 126            </Card>
 127          </div>
 128        </div>
 129      </>
 130    );
 131  }
 132  
 133  function DeleteAppButton({
 134    app,
 135    onDelete,
 136  }: {
 137    app: App;
 138    onDelete: () => void;
 139  }) {
 140    const { deleteApp } = useDeleteApp(app);
 141    return (
 142      <Button
 143        variant="destructive"
 144        onClick={() => {
 145          deleteApp();
 146          onDelete();
 147        }}
 148      >
 149        <Trash2Icon />
 150        Delete
 151      </Button>
 152    );
 153  }
 154