(bugfix) Fix proof of payment download URL to use shareID with checksum
Fixed bug where proof of payment download links used raw locationID instead of shareID (locationID + checksum), causing link validation to fail. Added AsyncLink component to handle async shareID generation gracefully. Changes: - BillEditForm: Generate shareID using generateShareId server action - BillEditForm: Use AsyncLink to prevent broken links during async load - AsyncLink: New reusable component for links that need async data - Updated download URL from locationID-billID to shareID-billID format Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
13
web-app/app/ui/AsyncLink.tsx
Normal file
13
web-app/app/ui/AsyncLink.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
/** Link component that can be disabled */
|
||||
export const AsyncLink: React.FC<{ href: string; children: React.ReactNode, target?: string, className?: string, disabled?: boolean }> = ({ href, children, target, className, disabled }) =>
|
||||
disabled ? <span className={className}>{children}</span> :
|
||||
<Link
|
||||
href={href}
|
||||
target={target}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { DocumentIcon, TicketIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||
import { Bill, BilledTo, BillingLocation } from "../lib/db-types";
|
||||
import React, { FC, useEffect } from "react";
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import { useFormState } from "react-dom";
|
||||
import { updateOrAddBill } from "../lib/actions/billActions";
|
||||
import Link from "next/link";
|
||||
@@ -11,6 +11,8 @@ import { DecodeResult, findDecodePdf417 } from "../lib/pdf/barcodeDecoderWasm";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import { InfoBox } from "./InfoBox";
|
||||
import { Pdf417Barcode } from "./Pdf417Barcode";
|
||||
import { generateShareId } from "../lib/actions/locationActions";
|
||||
import { AsyncLink } from "./AsyncLink";
|
||||
|
||||
// Next.js does not encode an utf-8 file name correctly when sending a form with a file attachment
|
||||
// This is a workaround for that
|
||||
@@ -35,6 +37,20 @@ export const BillEditForm: FC<BillEditFormProps> = ({ location, bill }) => {
|
||||
|
||||
const { yearMonth: { year: billYear, month: billMonth }, _id: locationID, proofOfPaymentType } = location;
|
||||
|
||||
/**
|
||||
* Share ID for viewing-only links (locationID + checksum)
|
||||
* Note: This is different from the share button which calls `generateShareLink`
|
||||
* to activate sharing and set TTL in the database
|
||||
*/
|
||||
const [shareID, setShareID] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// share ID can be generated server-side since it requires a secret key
|
||||
// which we don't want to expose to the client
|
||||
(async () => setShareID(await generateShareId(locationID)))();
|
||||
}, [locationID]);
|
||||
|
||||
|
||||
const initialState = { message: null, errors: {} };
|
||||
|
||||
const handleAction = updateOrAddBillMiddleware.bind(null, locationID, billID, billYear, billMonth);
|
||||
@@ -238,14 +254,15 @@ export const BillEditForm: FC<BillEditFormProps> = ({ location, bill }) => {
|
||||
// -> don't show anything
|
||||
proofOfPayment.fileName ? (
|
||||
<div className="mt-3 ml-[-.5rem]">
|
||||
<Link
|
||||
href={`/share/proof-of-payment/per-bill/${locationID}-${billID}/`}
|
||||
<AsyncLink
|
||||
disabled={!shareID}
|
||||
href={`/share/proof-of-payment/per-bill/${shareID}-${billID}/`}
|
||||
target="_blank"
|
||||
className='text-center w-full max-w-[20rem] text-nowrap truncate inline-block'
|
||||
>
|
||||
<TicketIcon className="h-[1em] w-[1em] text-2xl inline-block mr-1 mt-[-.2em] text-teal-500" />
|
||||
{decodeURIComponent(proofOfPayment.fileName)}
|
||||
</Link>
|
||||
</AsyncLink>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user