All files / packages/app/utils navigation.tsx

100% Statements 16/16
100% Branches 2/2
100% Functions 5/5
100% Lines 16/16

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 145 146 147 148 149 150 151 152 153 154 155                                                                                                                                                          332x 2908x       4780x 4780x 4780x 4780x 4780x 4741x       2908x             1622x 1622x         2908x 1414x 1414x   2908x     332x                                                                                      
import React from "react";
 
import type { RegisteredRouter, RouteById } from "@tanstack/react-router";
import type {
	LinkOptions as RawLinkOptions,
	NavigateOptions as RawNavigationOptions,
	ValidateNavigateOptions,
} from "@tanstack/router-core";
import { z } from "zod";
 
import { NavigationContext } from "~app/contexts/navigation-context";
import {
	confirmEmailTokenSchema,
	currencyCodeSchema,
	limitSchema,
	offsetSchema,
	peerIdSchema,
	receiptsFiltersSchema,
	receiptsOrderBySchema,
	resetPasswordTokenSchema,
	voidUserTokenSchema,
} from "~app/utils/validation";
import type { TreeRouter } from "~web/entry/router";
import type { FileRoutesById, FileRoutesByTo } from "~web/entry/routeTree.gen";
 
declare module "@react-types/shared" {
	// oxlint-disable-next-line typescript/consistent-type-definitions
	interface RouterConfig {
		routerOptions: ValidateNavigateOptions;
	}
}
 
export type RouteId = keyof FileRoutesById;
export type RouteTo = keyof FileRoutesByTo;
export type PathParams<K extends RouteId> = RouteById<
	RegisteredRouter["routeTree"],
	K
>["types"]["allParams"];
export type OutputRouteSearchParams<K extends RouteId> =
	FileRoutesById[K]["types"]["searchSchema"];
type InputRouteSearchParams<K extends RouteId> =
	FileRoutesById[K]["types"]["searchSchemaInput"];
 
export type SearchParamStateByRoute<
	K extends RouteId,
	P extends keyof OutputRouteSearchParams<K>,
> = [
	OutputRouteSearchParams<K>[P],
	(
		setStateAction:
			| InputRouteSearchParams<K>[P]
			| ((
					prevState: OutputRouteSearchParams<K>[P],
			  ) => InputRouteSearchParams<K>[P]),
		options?: RawNavigationOptions<TreeRouter, "/">,
	) => void,
];
export type SearchParamState<
	K extends RouteId,
	P extends keyof OutputRouteSearchParams<K>,
> = SearchParamStateByRoute<K, P>;
 
export type SearchParamStateDefaulted<
	K extends RouteId,
	P extends keyof OutputRouteSearchParams<K>,
> = [NonNullable<SearchParamState<K, P>[0]>, SearchParamState<K, P>[1]];
 
export type NavigationOptions<K extends RouteTo> = Omit<
	RawNavigationOptions<TreeRouter, "/", K>,
	"from" | "href"
>;
 
export type LinkOptions<K extends RouteTo> = Omit<
	RawLinkOptions<TreeRouter, "/", K>,
	"from" | "href"
>;
 
export const getPathHooks = <K extends RouteId>(key: K) => {
	const useQueryState = <P extends keyof OutputRouteSearchParams<K>>(
		param: P,
	) => {
		const { useSearchParams, useUpdateSearchParam } =
			React.use(NavigationContext);
		const searchParams = useSearchParams(key);
		const getUpdateSearchParam = useUpdateSearchParam(key);
		const setValue = getUpdateSearchParam(param);
		return React.useMemo(
			() => [searchParams[param], setValue] as SearchParamStateByRoute<K, P>,
			[param, searchParams, setValue],
		);
	};
	const useDefaultedQueryState = <
		P extends keyof OutputRouteSearchParams<K>,
		V extends OutputRouteSearchParams<K>[P],
	>(
		param: P,
		defaultValue: V,
	) => {
		const [value, setValue] = useQueryState(param);
		return [value === undefined ? defaultValue : value, setValue] as [
			V,
			SearchParamStateByRoute<K, P>[1],
		];
	};
	const useParams = () => {
		const { useParams: useParamsRaw } = React.use(NavigationContext);
		return useParamsRaw<K>(key);
	};
	return { useQueryState, useDefaultedQueryState, useParams };
};
 
export const searchParamsMapping = {
	__root__: z.object({
		debug: z.coerce.boolean().default(false).catch(false),
		redirect: z.string().default("").catch(""),
	}),
	"/_public/void-user": z.object({
		token: voidUserTokenSchema.optional().catch(undefined),
	}),
	"/_public/reset-password": z.object({
		token: resetPasswordTokenSchema.optional().catch(undefined),
	}),
	"/_public/confirm-email": z.object({
		token: confirmEmailTokenSchema.optional().catch(undefined),
	}),
	"/_protected/peers/": z.object({
		limit: limitSchema.optional().catch(undefined),
		offset: offsetSchema.default(0).catch(0),
	}),
	"/_protected/receipts/": z.object({
		sort: receiptsOrderBySchema.default("date-desc").catch("date-desc"),
		filters: receiptsFiltersSchema.default({}).catch({}),
		limit: limitSchema.optional().catch(undefined),
		offset: offsetSchema.default(0).catch(0),
	}),
	"/_protected/debts/transfer": z.object({
		to: peerIdSchema.optional().catch(undefined),
		from: peerIdSchema.optional().catch(undefined),
	}),
	"/_protected/debts/": z.object({
		limit: limitSchema.optional().catch(undefined),
		offset: offsetSchema.default(0).catch(0),
	}),
	"/_protected/debts/peer/$id/": z.object({
		limit: limitSchema.optional().catch(undefined),
		offset: offsetSchema.default(0).catch(0),
	}),
	"/_protected/debts/add": z.object({
		peerId: peerIdSchema.optional().catch(undefined),
	}),
	"/_protected/debts/peer/$id/exchange/all": z.object({
		from: currencyCodeSchema.optional().catch(undefined),
	}),
} satisfies Partial<Record<RouteId, z.ZodType>>;