Fix client-side cache staleness after proof of payment upload
Added cache revalidation to ensure ViewLocationCard reflects uploaded proof of payment when navigating back from ViewBillCard: - Server-side: Added revalidatePath() to upload actions in billActions and locationActions to invalidate Next.js server cache - Client-side: Added router.refresh() calls in ViewBillCard and ViewLocationCard to refresh client router cache after successful upload This maintains the current UX (no redirect on upload) while ensuring fresh data is displayed on navigation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { getDbClient } from '../dbClient';
|
import { getDbClient } from '../dbClient';
|
||||||
import { Bill, BilledTo, FileAttachment, BillingLocation, YearMonth } from '../db-types';
|
import { Bill, BilledTo, FileAttachment, BillingLocation } from '../db-types';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { withUser } from '@/app/lib/auth';
|
import { withUser } from '@/app/lib/auth';
|
||||||
import { AuthenticatedUser } from '../types/next-auth';
|
import { AuthenticatedUser } from '../types/next-auth';
|
||||||
import { gotoHome, gotoHomeWithMessage } from './navigationActions';
|
import { gotoHomeWithMessage } from './navigationActions';
|
||||||
import { getTranslations, getLocale } from "next-intl/server";
|
import { getTranslations, getLocale } from "next-intl/server";
|
||||||
import { IntlTemplateFn } from '@/app/i18n';
|
import { IntlTemplateFn } from '@/app/i18n';
|
||||||
import { unstable_noStore } from 'next/cache';
|
import { unstable_noStore, revalidatePath } from 'next/cache';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
errors?: {
|
errors?: {
|
||||||
@@ -116,6 +116,8 @@ const serializeAttachment = async (billAttachment: File | null): Promise<FileAtt
|
|||||||
*/
|
*/
|
||||||
export const updateOrAddBill = withUser(async (user: AuthenticatedUser, locationId: string, billId: string | undefined, billYear: number | undefined, billMonth: number | undefined, prevState: State, formData: FormData) => {
|
export const updateOrAddBill = withUser(async (user: AuthenticatedUser, locationId: string, billId: string | undefined, billYear: number | undefined, billMonth: number | undefined, prevState: State, formData: FormData) => {
|
||||||
|
|
||||||
|
unstable_noStore();
|
||||||
|
|
||||||
const { id: userId } = user;
|
const { id: userId } = user;
|
||||||
|
|
||||||
const t = await getTranslations("bill-edit-form.validation");
|
const t = await getTranslations("bill-edit-form.validation");
|
||||||
@@ -351,6 +353,7 @@ export const fetchBillByUserAndId = withUser(async (user:AuthenticatedUser, loca
|
|||||||
|
|
||||||
export const fetchBillById = async (locationID: string, billID: string, includeAttachmentBinary: boolean = false) => {
|
export const fetchBillById = async (locationID: string, billID: string, includeAttachmentBinary: boolean = false) => {
|
||||||
|
|
||||||
|
unstable_noStore();
|
||||||
|
|
||||||
const dbClient = await getDbClient();
|
const dbClient = await getDbClient();
|
||||||
|
|
||||||
@@ -387,6 +390,8 @@ export const fetchBillById = async (locationID: string, billID: string, includeA
|
|||||||
|
|
||||||
export const deleteBillById = withUser(async (user: AuthenticatedUser, locationID: string, billID: string, year: number, month: number, _prevState: any, formData?: FormData) => {
|
export const deleteBillById = withUser(async (user: AuthenticatedUser, locationID: string, billID: string, year: number, month: number, _prevState: any, formData?: FormData) => {
|
||||||
|
|
||||||
|
unstable_noStore();
|
||||||
|
|
||||||
const { id: userId } = user;
|
const { id: userId } = user;
|
||||||
|
|
||||||
const dbClient = await getDbClient();
|
const dbClient = await getDbClient();
|
||||||
@@ -488,6 +493,7 @@ export const deleteBillById = withUser(async (user: AuthenticatedUser, locationI
|
|||||||
* @returns Promise with success status
|
* @returns Promise with success status
|
||||||
*/
|
*/
|
||||||
export const uploadProofOfPayment = async (locationID: string, billID: string, formData: FormData): Promise<{ success: boolean; error?: string }> => {
|
export const uploadProofOfPayment = async (locationID: string, billID: string, formData: FormData): Promise<{ success: boolean; error?: string }> => {
|
||||||
|
|
||||||
unstable_noStore();
|
unstable_noStore();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -503,7 +509,7 @@ export const uploadProofOfPayment = async (locationID: string, billID: string, f
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate file type
|
// Validate file type
|
||||||
if (attachmentFile && attachmentFile.size > 0 && attachmentFile.type !== 'application/pdf') {
|
if (file && file.size > 0 && file.type !== 'application/pdf') {
|
||||||
return { success: false, error: 'Only PDF files are accepted' };
|
return { success: false, error: 'Only PDF files are accepted' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,6 +570,9 @@ export const uploadProofOfPayment = async (locationID: string, billID: string, f
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Invalidate the location view cache
|
||||||
|
revalidatePath(`/share/location/${locationID}`, 'page');
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error uploading proof of payment for a bill:', error);
|
console.error('Error uploading proof of payment for a bill:', error);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ObjectId } from 'mongodb';
|
|||||||
import { withUser } from '@/app/lib/auth';
|
import { withUser } from '@/app/lib/auth';
|
||||||
import { AuthenticatedUser } from '../types/next-auth';
|
import { AuthenticatedUser } from '../types/next-auth';
|
||||||
import { gotoHomeWithMessage } from './navigationActions';
|
import { gotoHomeWithMessage } from './navigationActions';
|
||||||
import { unstable_noStore as noStore } from 'next/cache';
|
import { unstable_noStore, revalidatePath } from 'next/cache';
|
||||||
import { IntlTemplateFn } from '@/app/i18n';
|
import { IntlTemplateFn } from '@/app/i18n';
|
||||||
import { getTranslations, getLocale } from "next-intl/server";
|
import { getTranslations, getLocale } from "next-intl/server";
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ const FormSchema = (t:IntlTemplateFn) => z.object({
|
|||||||
*/
|
*/
|
||||||
export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locationId: string | undefined, yearMonth: YearMonth | undefined, prevState:State, formData: FormData) => {
|
export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locationId: string | undefined, yearMonth: YearMonth | undefined, prevState:State, formData: FormData) => {
|
||||||
|
|
||||||
noStore();
|
unstable_noStore();
|
||||||
|
|
||||||
const t = await getTranslations("location-edit-form.validation");
|
const t = await getTranslations("location-edit-form.validation");
|
||||||
|
|
||||||
@@ -373,7 +373,7 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
|
|||||||
|
|
||||||
export const fetchAllLocations = withUser(async (user:AuthenticatedUser, year:number) => {
|
export const fetchAllLocations = withUser(async (user:AuthenticatedUser, year:number) => {
|
||||||
|
|
||||||
noStore();
|
unstable_noStore();
|
||||||
|
|
||||||
const dbClient = await getDbClient();
|
const dbClient = await getDbClient();
|
||||||
|
|
||||||
@@ -470,7 +470,7 @@ ova metoda je zamijenjena sa jednostavnijom `fetchLocationById`, koja brže radi
|
|||||||
|
|
||||||
export const fetchLocationByUserAndId = withUser(async (user:AuthenticatedUser, locationID:string) => {
|
export const fetchLocationByUserAndId = withUser(async (user:AuthenticatedUser, locationID:string) => {
|
||||||
|
|
||||||
noStore();
|
unstable_noStore();
|
||||||
|
|
||||||
const dbClient = await getDbClient();
|
const dbClient = await getDbClient();
|
||||||
|
|
||||||
@@ -499,7 +499,7 @@ export const fetchLocationByUserAndId = withUser(async (user:AuthenticatedUser,
|
|||||||
|
|
||||||
export const fetchLocationById = async (locationID:string) => {
|
export const fetchLocationById = async (locationID:string) => {
|
||||||
|
|
||||||
noStore();
|
unstable_noStore();
|
||||||
|
|
||||||
const dbClient = await getDbClient();
|
const dbClient = await getDbClient();
|
||||||
|
|
||||||
@@ -526,7 +526,7 @@ export const fetchLocationById = async (locationID:string) => {
|
|||||||
|
|
||||||
export const deleteLocationById = withUser(async (user:AuthenticatedUser, locationID:string, yearMonth:YearMonth, _prevState:any, formData: FormData) => {
|
export const deleteLocationById = withUser(async (user:AuthenticatedUser, locationID:string, yearMonth:YearMonth, _prevState:any, formData: FormData) => {
|
||||||
|
|
||||||
noStore();
|
unstable_noStore();
|
||||||
|
|
||||||
const dbClient = await getDbClient();
|
const dbClient = await getDbClient();
|
||||||
|
|
||||||
@@ -642,7 +642,8 @@ const serializeAttachment = async (file: File | null):Promise<FileAttachment | n
|
|||||||
* @returns Promise with success status
|
* @returns Promise with success status
|
||||||
*/
|
*/
|
||||||
export const uploadUtilBillsProofOfPayment = async (locationID: string, formData: FormData): Promise<{ success: boolean; error?: string }> => {
|
export const uploadUtilBillsProofOfPayment = async (locationID: string, formData: FormData): Promise<{ success: boolean; error?: string }> => {
|
||||||
noStore();
|
|
||||||
|
unstable_noStore();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@@ -689,6 +690,9 @@ export const uploadUtilBillsProofOfPayment = async (locationID: string, formData
|
|||||||
} }
|
} }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Invalidate the location view cache
|
||||||
|
revalidatePath(`/share/location/${locationID}`, 'page');
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error uploading util bills proof of payment:', error);
|
console.error('Error uploading util bills proof of payment:', error);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { DocumentIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/outline";
|
import { TicketIcon, CheckCircleIcon, XCircleIcon, DocumentIcon } from "@heroicons/react/24/outline";
|
||||||
import { Bill, BillingLocation } from "../lib/db-types";
|
import { Bill, BillingLocation } from "../lib/db-types";
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import { formatYearMonth } from "../lib/format";
|
import { formatYearMonth } from "../lib/format";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { Pdf417Barcode } from "./Pdf417Barcode";
|
import { Pdf417Barcode } from "./Pdf417Barcode";
|
||||||
@@ -16,6 +17,7 @@ export interface ViewBillCardProps {
|
|||||||
|
|
||||||
export const ViewBillCard: FC<ViewBillCardProps> = ({ location, bill }) => {
|
export const ViewBillCard: FC<ViewBillCardProps> = ({ location, bill }) => {
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
const t = useTranslations("bill-edit-form");
|
const t = useTranslations("bill-edit-form");
|
||||||
|
|
||||||
const { _id: billID, name, paid, attachment, notes, payedAmount, hub3aText, proofOfPayment } = bill ?? { _id: undefined, name: "", paid: false, notes: "" };
|
const { _id: billID, name, paid, attachment, notes, payedAmount, hub3aText, proofOfPayment } = bill ?? { _id: undefined, name: "", paid: false, notes: "" };
|
||||||
@@ -49,6 +51,7 @@ export const ViewBillCard: FC<ViewBillCardProps> = ({ location, bill }) => {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
setProofOfPaymentFilename(file.name);
|
setProofOfPaymentFilename(file.name);
|
||||||
setProofOfPaymentUploadedAt(new Date());
|
setProofOfPaymentUploadedAt(new Date());
|
||||||
|
router.refresh();
|
||||||
} else {
|
} else {
|
||||||
setUploadError(result.error || 'Upload failed');
|
setUploadError(result.error || 'Upload failed');
|
||||||
}
|
}
|
||||||
@@ -125,7 +128,7 @@ export const ViewBillCard: FC<ViewBillCardProps> = ({ location, bill }) => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
className='text-center w-full max-w-[20rem] text-nowrap truncate inline-block'
|
className='text-center w-full max-w-[20rem] text-nowrap truncate inline-block'
|
||||||
>
|
>
|
||||||
<DocumentIcon className="h-[1em] w-[1em] text-2xl inline-block mr-1" />
|
<TicketIcon className="h-[1em] w-[1em] text-2xl inline-block mr-1" />
|
||||||
{ decodeURIComponent(proofOfPaymentFilename) }
|
{ decodeURIComponent(proofOfPaymentFilename) }
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { BilledTo, BillingLocation, UserSettings } from "../lib/db-types";
|
|||||||
import { formatYearMonth } from "../lib/format";
|
import { formatYearMonth } from "../lib/format";
|
||||||
import { formatCurrency, formatIban } from "../lib/formatStrings";
|
import { formatCurrency, formatIban } from "../lib/formatStrings";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import { ViewBillBadge } from "./ViewBillBadge";
|
import { ViewBillBadge } from "./ViewBillBadge";
|
||||||
import { Pdf417Barcode } from "./Pdf417Barcode";
|
import { Pdf417Barcode } from "./Pdf417Barcode";
|
||||||
import { EncodePayment, PaymentParams } from "hub-3a-payment-encoder";
|
import { EncodePayment, PaymentParams } from "hub-3a-payment-encoder";
|
||||||
@@ -34,6 +35,7 @@ export const ViewLocationCard: FC<ViewLocationCardProps> = ({ location, userSett
|
|||||||
proofOfPaymentType,
|
proofOfPaymentType,
|
||||||
} = location;
|
} = location;
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
const t = useTranslations("home-page.location-card");
|
const t = useTranslations("home-page.location-card");
|
||||||
|
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
@@ -64,6 +66,7 @@ export const ViewLocationCard: FC<ViewLocationCardProps> = ({ location, userSett
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
setAttachmentFilename(file.name);
|
setAttachmentFilename(file.name);
|
||||||
setAttachmentUploadedAt(new Date());
|
setAttachmentUploadedAt(new Date());
|
||||||
|
router.refresh();
|
||||||
} else {
|
} else {
|
||||||
setUploadError(result.error || 'Upload failed');
|
setUploadError(result.error || 'Upload failed');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user