Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions components/admin/requests/Header.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new backlink logic doesn't work as intended when the request page is loaded without tab / page in the URL. this can happen after doing something like creating a replacement request. If you pay close attention to the url you'll notice it says /admin?tab=undefined&page=undefined for a half second and then defaults tab to PENDING and page to an empty string, but the link itself is still incorrect.

To see this pay close attention to the URL in the following video:

Screen.Recording.2025-11-24.at.10.48.47.PM.mov

To fix this I think you should build the link from the current query with fallbacks, without relying on stale state or you could rerun when the query changes. I have an example (that I think works) that you can just drop in for the Header.tsx file if you think it looks fine:

import { ChevronDownIcon, ChevronLeftIcon } from '@chakra-ui/icons'; // Chakra UI icon
import {
  Alert,
  AlertIcon,
  Box,
  Button,
  Flex,
  HStack,
  Link,
  Menu,
  MenuButton,
  MenuItem,
  MenuList,
  Text,
  useDisclosure,
  VStack,
} from '@chakra-ui/react'; // Chakra UI
import PermitTypeBadge from '@components/admin/PermitTypeBadge';
import RequestStatusBadge from '@components/admin/RequestStatusBadge'; // Request status badge
import ShopifyBadge from '@components/admin/ShopifyBadge';
import { ApplicationStatus, ApplicationType, PermitType } from '@lib/graphql/types';
import { formatDateYYYYMMDD, formatDateYYYYMMDDLocal } from '@lib/utils/date';
import { getPermanentPermitExpiryDate } from '@lib/utils/permit-expiry';
import { CurrentApplication } from '@tools/admin/permit-holders/current-application';
import { titlecase } from '@tools/string';
import NextLink from 'next/link';
import { useRouter } from 'next/router';
import ConfirmDeleteRequestModal from './delete/ConfirmDeleteRequestModal';

type RequestHeaderProps = {
  readonly id: number;
  readonly applicationType: ApplicationType;
  readonly permitType: PermitType;
  readonly createdAt: Date;
  readonly applicationStatus?: ApplicationStatus;
  readonly paidThroughShopify?: boolean;
  readonly shopifyOrderID?: string;
  readonly shopifyOrderNumber?: string;
  readonly permitExpiry: Date | null;
  readonly temporaryPermitExpiry: Date | null;
  readonly reasonForRejection?: string;
  readonly mostRecentApplication: CurrentApplication | null;
};

/**
 * Header of View Request page
 * @param id Application id
 * @param applicationType Type of application
 * @param permitType Type of permit
 * @param createdAt Date permit was created at
 * @param applicationStatus Status of application
 * @param paidThroughShopify If the permit fee was paid through Shopify
 * @param shopifyOrderID Order ID of Shopify payment if paid through Shopify
 * @param shopifyOrderNumber Order number of Shopify payment if paid through Shopify
 * @param permitExpiry Permit expiry if application is complete
 * @param temporaryPermitExpiry Permit expiry if application is for a temporary permit
 * @param reasonForRejection Reason for rejecting application
 */
export default function RequestHeader({
  id,
  applicationType,
  permitType,
  createdAt,
  applicationStatus,
  paidThroughShopify,
  shopifyOrderID,
  shopifyOrderNumber,
  permitExpiry,
  temporaryPermitExpiry,
  reasonForRejection,
  mostRecentApplication,
}: RequestHeaderProps) {
  const displayShopifyUrl = paidThroughShopify && shopifyOrderID && shopifyOrderNumber;
  const shopifyOrderUrl = `https://${process.env.NEXT_PUBLIC_SHOPIFY_DOMAIN}/admin/orders/${shopifyOrderID}`;

  let expiryDateText: string | null;
  if (applicationStatus === 'COMPLETED' && !!permitExpiry) {
    expiryDateText = `Expiry date: ${formatDateYYYYMMDD(permitExpiry)}`;
  } else if (permitType === 'TEMPORARY' && !!temporaryPermitExpiry) {
    expiryDateText = `This permit will expire: ${formatDateYYYYMMDD(temporaryPermitExpiry)}`;
  } else if (applicationType === 'REPLACEMENT' && !!permitExpiry) {
    expiryDateText = `This permit will expire: ${formatDateYYYYMMDD(permitExpiry)}`;
  } else if (permitType === 'PERMANENT') {
    expiryDateText = `This permit will expire: ${formatDateYYYYMMDD(
      getPermanentPermitExpiryDate()
    )} (expected)`;
  } else {
    expiryDateText = null;
  }

  const router = useRouter();

  const routerStatus =
    typeof router.query.tab === 'string' ? router.query.tab : applicationStatus || 'PENDING';
  const routerPage = typeof router.query.page === 'string' ? router.query.page : '0';
  const backLink = `/admin?tab=${routerStatus}&page=${routerPage}`;

  const formatStatus = (status: string | string[] | undefined) => {
    if (typeof status != 'string') {
      return '';
    }
    if (status === 'ALL') {
      return '';
    }
    return status.toLowerCase().replace('_', ' ');
  };

  // Delete application modal state
  const {
    isOpen: isDeleteApplicationModalOpen,
    onOpen: onOpenDeleteApplicationModal,
    onClose: onCloseDeleteApplicationModal,
  } = useDisclosure();

  return (
    <Box textAlign="left">
      <NextLink href={backLink} passHref>
        <Text textStyle="button-semibold" textColor="primary" as="a">
          <ChevronLeftIcon />
          All {formatStatus(routerStatus)} requests
        </Text>
      </NextLink>

      <VStack alignItems="stretch">
        <Flex marginTop={5} alignItems="baseline" justifyContent="space-between">
          <Box>
            <Flex alignItems="center">
              <Text textStyle="display-large" as="h1" marginRight={3} textTransform="capitalize">
                {`${titlecase(applicationType)} Request`}
              </Text>
              <HStack spacing={3}>
                {applicationStatus && <RequestStatusBadge variant={applicationStatus} />}
                {paidThroughShopify && <ShopifyBadge />}
              </HStack>
            </Flex>
            <HStack spacing={3} marginTop={3}>
              <Text textStyle="caption" as="p">
                Received on {formatDateYYYYMMDDLocal(createdAt)} at{' '}
                {createdAt.toLocaleTimeString('en-CA')}
              </Text>
              <Menu>
                <MenuButton
                  as={Button}
                  rightIcon={<ChevronDownIcon />}
                  height="30px"
                  bg="background.gray"
                  _hover={{ bg: 'background.grayHover' }}
                  color="black"
                >
                  <Text textStyle="caption">More Actions</Text>
                </MenuButton>
                <MenuList>
                  {mostRecentApplication?.processing?.status == 'COMPLETED' ? null : (
                    <MenuItem
                      color="text.critical"
                      textStyle="button-regular"
                      onClick={onOpenDeleteApplicationModal}
                    >
                      {'Delete Request'}
                    </MenuItem>
                  )}
                </MenuList>
              </Menu>
            </HStack>
            {displayShopifyUrl && (
              <Text textStyle="caption" as="p">
                Paid with Shopify: Order{' '}
                <Link
                  href={shopifyOrderUrl}
                  isExternal={true}
                  textStyle="caption-bold"
                  textDecoration="underline"
                  color="primary"
                >
                  {`#${shopifyOrderNumber}`}
                </Link>
              </Text>
            )}
          </Box>
          <VStack alignItems="flex-end" spacing="0">
            <Flex alignItems="center">
              <Text textStyle="heading" as="h3" marginRight={3} textTransform="capitalize">
                Permit Type:
              </Text>
              <PermitTypeBadge variant={permitType} />
            </Flex>
            <HStack justifyContent="flex-end">
              <Text textStyle="caption" as="p" mt="12px">
                {expiryDateText}
              </Text>
            </HStack>
          </VStack>
        </Flex>
        {applicationStatus === 'REJECTED' && (
          <Alert status="error">
            <AlertIcon />
            <Text as="p" textStyle="caption">
              <b>Reason for Rejection: </b>
              {reasonForRejection || ''}
            </Text>
          </Alert>
        )}
        <ConfirmDeleteRequestModal
          isOpen={isDeleteApplicationModalOpen}
          applicationId={id}
          refetch={() => {
            /* Do not refetch, redirect to main page */
          }}
          onClose={() => {
            onCloseDeleteApplicationModal();
            router.push('/admin');
          }}
        />
      </VStack>
    </Box>
  );
}

Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,26 @@ export default function RequestHeader({
const router = useRouter();

const [backLink, setBackLink] = useState('/admin');
const [status, setStatus] = useState(router.query.tab);
const [page, setPage] = useState(router.query.page);

const generateBackLink = () => {
const routerQuery = router.query;
const status = typeof routerQuery.tab === 'string' ? routerQuery.tab : applicationStatus;
const page = typeof routerQuery.page === 'string' ? routerQuery.page : '0';
typeof routerQuery.tab === 'string' ? setStatus(routerQuery.tab) : setStatus(applicationStatus);
typeof routerQuery.page === 'string' ? setPage(routerQuery.page) : setPage('0');
setBackLink(`/admin?tab=${status}&page=${page}`);
};

const formatStatus = (status: string | string[] | undefined) => {
if (typeof status != 'string') {
return '';
}
if (status === 'ALL') {
return '';
}
return status.toLowerCase().replace('_', ' ');
};

useEffect(() => {
generateBackLink();
}, []);
Expand All @@ -115,7 +128,7 @@ export default function RequestHeader({
<NextLink href={backLink} passHref>
<Text textStyle="button-semibold" textColor="primary" as="a">
<ChevronLeftIcon />
All requests
All {formatStatus(status)} requests
</Text>
</NextLink>

Expand Down