From a3ec20544c41a250ac3810f3c203ad9728de90d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Dere=C5=BEi=C4=87?= Date: Fri, 9 Jan 2026 18:18:17 +0100 Subject: [PATCH] (refactor) Move generateShareId to locationActions and apply to LocationCard - Moved generateShareId from shareChecksum.ts to locationActions.ts as a server action - Updated LocationCard to use shareID with checksum for proof of payment download link - Replaced Link with AsyncLink to handle async shareID generation - Commented out debug console.log in Pdf417Barcode Co-Authored-By: Claude Sonnet 4.5 --- web-app/app/lib/actions/locationActions.ts | 16 +++++++++++-- web-app/app/lib/shareChecksum.ts | 10 -------- web-app/app/ui/LocationCard.tsx | 28 ++++++++++++++++------ web-app/app/ui/Pdf417Barcode.tsx | 2 +- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/web-app/app/lib/actions/locationActions.ts b/web-app/app/lib/actions/locationActions.ts index d548d2f..5d124ab 100644 --- a/web-app/app/lib/actions/locationActions.ts +++ b/web-app/app/lib/actions/locationActions.ts @@ -10,7 +10,7 @@ import { gotoHomeWithMessage } from './navigationActions'; import { unstable_noStore, revalidatePath } from 'next/cache'; import { IntlTemplateFn } from '@/app/i18n'; import { getTranslations, getLocale } from "next-intl/server"; -import { generateShareId, extractShareId, validateShareChecksum } from '../shareChecksum'; +import { extractShareId, validateShareChecksum, generateShareChecksum } from '../shareChecksum'; import { validatePdfFile } from '../validators/pdfValidator'; import { checkUploadRateLimit } from '../uploadRateLimiter'; @@ -765,6 +765,18 @@ export const uploadUtilBillsProofOfPayment = async ( } } + +/** + * Generate combined location ID with checksum appended + * @param locationId - The MongoDB location ID (24 chars) + * @returns Combined ID: locationId + checksum (40 chars total) + */ +export async function generateShareId(locationId: string): Promise { + const checksum = generateShareChecksum(locationId); + return locationId + checksum; +} + + /** * Generate/activate share link for location * Called when owner clicks "Share" button @@ -800,7 +812,7 @@ export const generateShareLink = withUser( ); // Generate combined share ID (locationId + checksum) - const shareId = generateShareId(locationId); + const shareId = await generateShareId(locationId); // Build share URL const baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000'; diff --git a/web-app/app/lib/shareChecksum.ts b/web-app/app/lib/shareChecksum.ts index aef9874..0e5c2cc 100644 --- a/web-app/app/lib/shareChecksum.ts +++ b/web-app/app/lib/shareChecksum.ts @@ -56,16 +56,6 @@ export function validateShareChecksum( } } -/** - * Generate combined location ID with checksum appended - * @param locationId - The MongoDB location ID (24 chars) - * @returns Combined ID: locationId + checksum (40 chars total) - */ -export function generateShareId(locationId: string): string { - const checksum = generateShareChecksum(locationId); - return locationId + checksum; -} - /** * Extract location ID and checksum from combined share ID * @param shareId - Combined ID (locationId + checksum) diff --git a/web-app/app/ui/LocationCard.tsx b/web-app/app/ui/LocationCard.tsx index d7a34bb..914db54 100644 --- a/web-app/app/ui/LocationCard.tsx +++ b/web-app/app/ui/LocationCard.tsx @@ -1,15 +1,16 @@ 'use client'; import { CheckCircleIcon, Cog8ToothIcon, PlusCircleIcon, ShareIcon, BanknotesIcon, EyeIcon, TicketIcon, ShoppingCartIcon, EnvelopeIcon, ExclamationTriangleIcon, ClockIcon } from "@heroicons/react/24/outline"; -import { FC } from "react"; +import { FC, useEffect, useState } from "react"; import { BillBadge } from "./BillBadge"; import { BillingLocation, EmailStatus } from "../lib/db-types"; import { formatYearMonth } from "../lib/format"; import { formatCurrency } from "../lib/formatStrings"; import Link from "next/link"; -import { useLocale, useTranslations } from "next-intl"; +import { useTranslations } from "next-intl"; import { toast } from "react-toastify"; -import { generateShareLink } from "../lib/actions/locationActions"; +import { generateShareId, generateShareLink } from "../lib/actions/locationActions"; +import { AsyncLink } from "./AsyncLink"; export interface LocationCardProps { location: BillingLocation; @@ -35,6 +36,18 @@ export const LocationCard: FC = ({ location, currency }) => { const totalUnpaid = bills.reduce((acc, bill) => !bill.paid ? acc + (bill.payedAmount ?? 0) : acc, 0); const totalPayed = bills.reduce((acc, bill) => bill.paid ? acc + (bill.payedAmount ?? 0) : acc, 0); + /** + * Share ID which can be used in shareable links + * Note: not to be used in share button directly since `generateShareLink` sets sharing TTL in the DB + * */ + const [shareID, setShareID] = useState("not-yet-generated"); + + 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(_id)))(); + }, [_id]); + const handleCopyLinkClick = async () => { // copy URL to clipboard const shareLink = await generateShareLink(_id); @@ -124,16 +137,17 @@ export const LocationCard: FC = ({ location, currency }) => { )} {utilBillsProofOfPayment?.uploadedAt && ( - + className="flex mt-1 ml-1" + disabled={!shareID} > {t("download-proof-of-payment-label")} - + )} diff --git a/web-app/app/ui/Pdf417Barcode.tsx b/web-app/app/ui/Pdf417Barcode.tsx index 45daac8..10cd929 100644 --- a/web-app/app/ui/Pdf417Barcode.tsx +++ b/web-app/app/ui/Pdf417Barcode.tsx @@ -7,7 +7,7 @@ import { renderBarcode } from '../lib/pdf/renderBarcode'; export const Pdf417Barcode:FC<{hub3aText:string, className?: string }> = ({ hub3aText: hub3a_text, className }) => { const [bitmapData, setBitmapData] = useState(undefined); - console.log("Rendering Pdf417Barcode with hub3a_text:", hub3a_text); + // console.log("Rendering Pdf417Barcode with hub3a_text:", hub3a_text); useEffect(() => { const aspectRatio = 3;