All files / react-app/src/components/Banner banner.tsx

96.66% Statements 29/30
85.71% Branches 12/14
90% Functions 9/10
96.66% Lines 29/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99                                  78x 23x               78x   78x 12x     78x 26x 26x 26x   26x   26x 3x     26x 8x 8x 18x     8x 8x 8x         26x 18x 11x       26x         19x 1x 1x         19x     26x                                     11x    
import { Check, X } from "lucide-react";
import { useEffect, useRef } from "react";
import { useLocation } from "react-router";
 
import { useLocalStorage } from "@/hooks/useLocalStorage";
import { Observer } from "@/utils/basic-observable";
 
import { Alert, AlertVariant } from "../Alert";
 
export type Banner = {
  header: string;
  body: string;
  variant?: AlertVariant;
  pathnameToDisplayOn: string;
};
 
class BannerObserver extends Observer<Banner> {
  create = (data: Banner) => {
    this.publish(data);
  };
 
  dismiss = () => {
    this.publish(null);
  };
}
 
const bannerState = new BannerObserver();
 
export const banner = (newBanner: Banner) => {
  return bannerState.create(newBanner);
};
 
export const Banner = () => {
  const bannerObserverRef = useRef<(() => void) | null>(null);
  const { pathname } = useLocation();
  const previousPathRef = useRef(pathname);
 
  const [activeBanner, setActiveBanner] = useLocalStorage<Banner | null>("banner", null);
 
  const onClose = () => {
    setActiveBanner(null);
  };
 
  useEffect(() => {
    Eif (bannerObserverRef.current === null) {
      bannerObserverRef.current = bannerState.subscribe((banner) => {
        setActiveBanner(banner);
      });
 
      return () => {
        bannerObserverRef.current?.();
        bannerObserverRef.current = null;
      };
    }
  }, [setActiveBanner]);
 
  useEffect(() => {
    if (activeBanner) {
      bannerState.create(activeBanner);
    }
  }, [activeBanner]);
 
  useEffect(() => {
    // only run cleanup if:
    // 1. we've actually navigated (pathname changed from previous render)
    // 2. there's an active banner to clean up
    // 3. the banner's target pathname doesn't match where we navigated to
    if (pathname !== previousPathRef.current) {
      Eif (activeBanner && activeBanner.pathnameToDisplayOn !== pathname) {
        onClose();
      }
    }
 
    // store current pathname for next render's comparison
    previousPathRef.current = pathname;
  }, [pathname, activeBanner]);
 
  if (activeBanner && activeBanner.pathnameToDisplayOn === pathname) {
    return (
      <Alert variant={activeBanner.variant} className="mt-4 mb-8 flex-row text-sm">
        <div className="flex items-start justify-between">
          <Check />
          <div className="ml-2 w-full">
            <h3 className="text-lg font-bold" data-testid="banner-header">
              {activeBanner.header}
            </h3>
            <p data-testid="banner-body">{activeBanner.body}</p>
          </div>
          <button onClick={onClose} data-testid="banner-close">
            <X size={20} />
          </button>
        </div>
      </Alert>
    );
  }
 
  return null;
};