All files / packages/app/features/peer-debts peer-debt-preview.tsx

100% Statements 19/19
100% Branches 10/10
100% Functions 5/5
100% Lines 19/19

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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144                                                            1666x                 2651x                               105x 842x                         30x     1945x 1945x     1945x 135x   1726x     30x             2096x 2096x     2096x 2096x 2096x   335x   2096x 2096x                                                                                      
import type React from "react";
 
import { useSuspenseQuery } from "@tanstack/react-query";
 
import { DebtSyncStatus } from "~app/components/app/debt-sync-status";
import { suspendedFallback } from "~app/components/suspense-wrapper";
import { useFormat } from "~app/hooks/use-format";
import { useLocale } from "~app/hooks/use-locale";
import { useTrpcMutationState } from "~app/hooks/use-trpc-mutation-state";
import { formatCurrency } from "~app/utils/currency";
import { useTRPC } from "~app/utils/trpc";
import { Checkbox } from "~components/checkbox";
import { Link } from "~components/link";
import { Skeleton } from "~components/skeleton";
import { Text } from "~components/text";
import { cn } from "~components/utils";
import type { ViewReactNode } from "~components/view";
import { View } from "~components/view";
import type { DebtId, PeerId } from "~db/ids";
 
type DebtShape = {
	amount: ViewReactNode;
	timestamp: ViewReactNode;
	synced: ViewReactNode;
	note: ViewReactNode;
	startContent?: ViewReactNode;
};
 
const PeerDebtPreviewShape: React.FC<
	DebtShape & React.ComponentProps<typeof View>
> = ({
	amount,
	timestamp,
	note,
	synced,
	className,
	startContent,
	...props
}) => (
	<View
		className={cn("flex flex-1 flex-col items-stretch", className)}
		testID="peer-debt-preview"
		{...props}
	>
		<View className="flex-1 flex-row gap-2 p-3 sm:p-2">
			{startContent}
			<View className="flex-2">{amount}</View>
			<View className="flex-2">{timestamp}</View>
			<View className="flex-1 empty:hidden">{synced}</View>
			<View className="hidden flex-3 sm:flex">{note}</View>
		</View>
		<View className="p-3 pb-5 sm:hidden">{note}</View>
	</View>
);
 
export const PeerDebtPreviewSkeleton = () => (
	<PeerDebtPreviewShape
		startContent={
			<View className="size-6 shrink-0">
				<Checkbox isDisabled color="secondary" />
			</View>
		}
		amount={<Skeleton className="h-6 w-16 rounded-sm" />}
		timestamp={<Skeleton className="h-6 w-24 rounded-sm" />}
		synced={<Skeleton className="size-6 rounded-sm" />}
		note={<Skeleton className="h-6 w-32 rounded-sm" />}
	/>
);
 
const OnlyWithConnectedUser = suspendedFallback<
	React.PropsWithChildren<{ peerId: PeerId }>
>(({ peerId, children }) => {
	const trpc = useTRPC();
	const { data: peer } = useSuspenseQuery(
		trpc.peers.get.queryOptions({ id: peerId }),
	);
	if (peer.connectedUser) {
		return <>{children}</>;
	}
	return null;
}, null);
 
export const PeerDebtPreview = suspendedFallback<{
	debtId: DebtId;
	resolved: boolean;
	isSelected: boolean;
	onValueChange: (nextValue: boolean) => void;
}>(
	({ debtId, resolved, isSelected, onValueChange }) => {
		const trpc = useTRPC();
		const { data: debt } = useSuspenseQuery(
			trpc.debts.get.queryOptions({ id: debtId }),
		);
		const locale = useLocale();
		const { formatPlainDate } = useFormat();
		const lastMutationState = useTrpcMutationState<"debts.remove">(
			trpc.debts.remove.mutationKey(),
			(vars) => vars.id === debtId,
		);
		const isRemoving = lastMutationState?.status === "pending";
		return (
			<Link
				to="/debts/$id"
				params={{ id: debt.id }}
				className={cn(
					"overflow-hidden py-2 first-of-type:rounded-t-2xl last-of-type:rounded-b-2xl",
					isSelected ? "bg-secondary/20" : "",
				)}
				color="foreground"
			>
				<PeerDebtPreviewShape
					startContent={
						<View className="size-6 shrink-0">
							<Checkbox
								isSelected={isSelected}
								onValueChange={isRemoving ? undefined : onValueChange}
								isDisabled={isRemoving}
								color="secondary"
							/>
						</View>
					}
					className={resolved ? "opacity-50" : undefined}
					amount={
						<Text
							className={debt.amount >= 0 ? "text-success" : "text-danger"}
							testID="preview-debt-amount"
						>
							{formatCurrency(locale, debt.currencyCode, Math.abs(debt.amount))}
						</Text>
					}
					timestamp={<Text>{formatPlainDate(debt.timestamp)}</Text>}
					synced={
						<OnlyWithConnectedUser peerId={debt.peerId}>
							<DebtSyncStatus debt={debt} theirDebt={debt.their} />
						</OnlyWithConnectedUser>
					}
					note={<Text>{debt.note}</Text>}
				/>
			</Link>
		);
	},
	<PeerDebtPreviewSkeleton />,
);