this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

more stuff

+650
+132
src/components/tables/pokemon-table-skeleton.tsx
··· 1 + import { Suspense, type ReactNode } from "react"; 2 + import { 3 + Table, 4 + TableBody, 5 + TableCell, 6 + TableHead, 7 + TableHeader, 8 + TableRow, 9 + } from "~/components/ui/table"; 10 + import { cn } from "~/lib/utils"; 11 + 12 + interface PokemonTableSkeletonProps { 13 + /** 14 + * Number of skeleton rows to render (defaults to 10 for POKEMON_LIMIT) 15 + */ 16 + rowCount?: number; 17 + className?: string; 18 + } 19 + 20 + /** 21 + * Skeleton loading state for the Pokemon table. 22 + * 23 + * Renders skeleton rows that exactly match the height of real table rows 24 + * to prevent layout shift when data loads. Uses the same padding and 25 + * structure as the actual PokemonTable component. 26 + * 27 + * @example 28 + * <PokemonTableSkeleton rowCount={10} /> 29 + */ 30 + export function PokemonTableSkeleton({ rowCount = 10, className }: PokemonTableSkeletonProps) { 31 + return ( 32 + <Table className={cn("w-full border-collapse", "text-sm", "font-mono", className)}> 33 + <TableHeader> 34 + <TableRow className="border-b border-(--border-strong)"> 35 + <TableHead 36 + className={cn( 37 + "text-left py-3 px-3", 38 + "font-semibold text-(--text-secondary)", 39 + "uppercase text-xs tracking-wider", 40 + )} 41 + > 42 + # 43 + </TableHead> 44 + <TableHead 45 + className={cn( 46 + "text-left py-3 px-3", 47 + "font-semibold text-(--text-secondary)", 48 + "uppercase text-xs tracking-wider", 49 + )} 50 + > 51 + Name 52 + </TableHead> 53 + <TableHead 54 + className={cn( 55 + "text-left py-3 px-3", 56 + "font-semibold text-(--text-secondary)", 57 + "uppercase text-xs tracking-wider", 58 + )} 59 + > 60 + Details 61 + </TableHead> 62 + </TableRow> 63 + </TableHeader> 64 + <TableBody> 65 + {Array.from({ length: rowCount }).map((_, index) => ( 66 + <SkeletonRow key={index} /> 67 + ))} 68 + </TableBody> 69 + </Table> 70 + ); 71 + } 72 + 73 + /** 74 + * Skeleton row that exactly matches the height of a real table row. 75 + * 76 + * Real rows have py-3 (12px) padding + content. This skeleton uses 77 + * identical padding to ensure zero layout shift when data loads. 78 + */ 79 + function SkeletonRow() { 80 + return ( 81 + <TableRow className="border-b border-(--border-default)"> 82 + <TableCell className="py-3 px-3"> 83 + <div className="h-4 w-8 bg-(--bg-secondary) animate-pulse" /> 84 + </TableCell> 85 + <TableCell className="py-3 px-3"> 86 + <div className="h-4 w-32 bg-(--bg-secondary) animate-pulse" /> 87 + </TableCell> 88 + <TableCell className="py-3 px-3"> 89 + <div className="flex gap-2"> 90 + <div className="h-5 w-16 bg-(--bg-secondary) animate-pulse" /> 91 + <div className="h-5 w-16 bg-(--bg-secondary) animate-pulse" /> 92 + </div> 93 + </TableCell> 94 + </TableRow> 95 + ); 96 + } 97 + 98 + interface PokemonTableContainerProps { 99 + /** 100 + * The actual PokemonTable component with data 101 + */ 102 + children: ReactNode; 103 + /** 104 + * Number of rows expected (for skeleton sizing) 105 + */ 106 + rowCount?: number; 107 + className?: string; 108 + } 109 + 110 + /** 111 + * Suspense-wrapped container for Pokemon tables. 112 + * 113 + * Provides a consistent loading state across all routes while 114 + * maintaining the console aesthetic. The table header is shown 115 + * immediately to set expectations for the data structure. 116 + * 117 + * @example 118 + * <PokemonTableContainer rowCount={10}> 119 + * <PokemonTable pokemon={data.pokemon} /> 120 + * </PokemonTableContainer> 121 + */ 122 + export function PokemonTableContainer({ 123 + children, 124 + rowCount, 125 + className, 126 + }: PokemonTableContainerProps) { 127 + return ( 128 + <Suspense fallback={<PokemonTableSkeleton rowCount={rowCount} className={className} />}> 129 + {children} 130 + </Suspense> 131 + ); 132 + }
+88
src/components/tables/pokemon-table.tsx
··· 1 + import { 2 + Table, 3 + TableBody, 4 + TableCell, 5 + TableHead, 6 + TableHeader, 7 + TableRow, 8 + } from "~/components/ui/table"; 9 + import { TypeBadge } from "./type-badge"; 10 + import { cn } from "~/lib/utils"; 11 + 12 + interface Pokemon { 13 + id: number; 14 + name: string; 15 + types: Array<{ name: string }>; 16 + } 17 + 18 + interface PokemonTableProps { 19 + pokemon: Pokemon[]; 20 + className?: string; 21 + } 22 + 23 + /** 24 + * Pokemon data table with console styling. 25 + * 26 + * Displays Pokemon ID, name, and types in a monospace-styled table. 27 + * Used consistently across all example routes. 28 + * 29 + * @example 30 + * <PokemonTable pokemon={data.pokemon} /> 31 + */ 32 + export function PokemonTable({ pokemon, className }: PokemonTableProps) { 33 + return ( 34 + <Table className={cn("w-full border-collapse", "text-sm", "font-mono", className)}> 35 + <TableHeader> 36 + <TableRow className="border-b border-(--border-strong)"> 37 + <TableHead 38 + className={cn( 39 + "text-left py-3 px-3", 40 + "font-semibold text-(--text-secondary)", 41 + "uppercase text-xs tracking-wider", 42 + )} 43 + > 44 + # 45 + </TableHead> 46 + <TableHead 47 + className={cn( 48 + "text-left py-3 px-3", 49 + "font-semibold text-(--text-secondary)", 50 + "uppercase text-xs tracking-wider", 51 + )} 52 + > 53 + Name 54 + </TableHead> 55 + <TableHead 56 + className={cn( 57 + "text-left py-3 px-3", 58 + "font-semibold text-(--text-secondary)", 59 + "uppercase text-xs tracking-wider", 60 + )} 61 + > 62 + Types 63 + </TableHead> 64 + </TableRow> 65 + </TableHeader> 66 + <TableBody> 67 + {pokemon.map((p) => ( 68 + <TableRow 69 + key={p.name} 70 + className={cn( 71 + "border-b border-(--border-default)", 72 + "transition-colors duration-fast ease-default", 73 + "hover:bg-(--bg-secondary)", 74 + )} 75 + > 76 + <TableCell className="py-3 px-3 font-mono text-(--text-muted)">{p.id}</TableCell> 77 + <TableCell className="py-3 px-3 capitalize text-(--text-primary)">{p.name}</TableCell> 78 + <TableCell className="py-3 px-3"> 79 + {p.types.map((type) => ( 80 + <TypeBadge key={type.name} type={type.name} /> 81 + ))} 82 + </TableCell> 83 + </TableRow> 84 + ))} 85 + </TableBody> 86 + </Table> 87 + ); 88 + }
+36
src/components/tables/type-badge.tsx
··· 1 + import { cn } from "~/lib/utils"; 2 + 3 + interface TypeBadgeProps { 4 + type: string; 5 + className?: string; 6 + } 7 + 8 + /** 9 + * Badge for displaying Pokemon types. 10 + * 11 + * Simple bordered label that fits the console aesthetic. 12 + * Multiple badges can be displayed inline for dual-type Pokemon. 13 + * 14 + * @example 15 + * <TypeBadge type="fire" /> 16 + * <TypeBadge type="flying" /> 17 + */ 18 + export function TypeBadge({ type, className }: TypeBadgeProps) { 19 + return ( 20 + <span 21 + className={cn( 22 + "inline-block", 23 + "px-2 py-0.5", 24 + "text-xs font-mono", 25 + "border border-(--border-default)", 26 + "bg-(--bg-secondary)", 27 + "text-(--text-secondary)", 28 + "capitalize", 29 + "mr-1", 30 + className, 31 + )} 32 + > 33 + {type} 34 + </span> 35 + ); 36 + }
+158
src/content/strategies.server.ts
··· 1 + export const strategyContent: Record<string, string> = { 2 + basic: `## No prefetching (baseline) 3 + 4 + This is the baseline approach — no prefetching whatsoever. Every page navigation triggers a fresh network request, and the user sees loading skeletons while data is fetched on demand. 5 + 6 + ### How it works 7 + 8 + 1. The user navigates to the page 9 + 2. React Query fires the fetch request from the component 10 + 3. A loading skeleton renders while the query resolves 11 + 4. Data arrives and the table renders with results 12 + 13 + ### Trade-offs 14 + 15 + - **Latency**: Every navigation incurs a full server round-trip 16 + - **UX**: Loading skeletons are visible on every page visit 17 + - **Simplicity**: The implementation is the easiest to understand and maintain 18 + 19 + ### When to use 20 + 21 + This pattern works for low-traffic pages, internal tools, or anywhere the performance overhead of a round-trip is acceptable. It is the simplest possible starting point before adding any prefetching strategy.`, 22 + 23 + preloading: `## Route-level prefetch 24 + 25 + Route-level prefetching improves perceived performance by initiating data fetches before the user reaches the page. When a link enters the viewport or receives focus, TanStack Router fires the route's \`loader\` function to pre-warm the query cache. 26 + 27 + ### How it works 28 + 29 + 1. TanStack Router detects that a link is about to be visited 30 + 2. The route's \`loader\` calls \`prefetchQuery\` on the query client 31 + 3. Data is fetched and cached in React Query's cache 32 + 4. When the user clicks, the page renders instantly from cache 33 + 34 + ### Trade-offs 35 + 36 + - **Bandwidth**: May fetch data the user never actually views 37 + - **Freshness**: Cached data needs an appropriate \`staleTime\` 38 + - **Complexity**: Requires defining loaders and query options per route 39 + 40 + ### Best practices 41 + 42 + Set an appropriate \`staleTime\` to balance data freshness with cache hits. Use \`prefetchQuery\` for speculative fetches — it won't throw if the query fails, making it safe for preloading.`, 43 + 44 + "intent-preloading": `## Intent-based preloading 45 + 46 + Intent preloading takes route-level prefetching further by triggering data fetches on hover and focus events. This creates a near-instant experience — data begins loading the moment the user shows intent to navigate. 47 + 48 + ### How it works 49 + 50 + 1. The user hovers over or focuses a navigation link 51 + 2. The \`prefetch="intent"\` directive triggers the route's loader 52 + 3. Data is fetched and cached before the click completes 53 + 4. On click, the page renders immediately with zero loading time 54 + 55 + ### Why intent matters 56 + 57 + Unlike viewport-based preloading which can trigger fetches for links the user may never click, intent preloading only fires when the user explicitly shows interest in a link. This balances performance gains with bandwidth efficiency. 58 + 59 + ### Configuration 60 + 61 + Set \`prefetch="intent"\` on \`Link\` components or configure it as the default for specific routes in your router configuration.`, 62 + 63 + pagination: `## Viewport pagination preload 64 + 65 + Pagination preloading uses the viewport to determine which pages to prefetch. When "next" or "previous" pagination links enter the viewport, the corresponding page's data is preloaded before the user clicks. 66 + 67 + ### How it works 68 + 69 + 1. Pagination links render with \`prefetch="viewport"\` 70 + 2. When a link enters the viewport, the router triggers a prefetch 71 + 3. Data is cached before the user clicks the navigation button 72 + 4. Pagination navigation feels instant 73 + 74 + ### Why viewport preloading for pagination? 75 + 76 + Pagination is a natural fit for viewport-based prefetching. The next and previous buttons are always visible at the bottom of the page, so prefetching them is almost guaranteed to be useful. It strikes an ideal balance between eager and lazy loading. 77 + 78 + ### Optimization 79 + 80 + Combine with \`staleTime\` to avoid redundant refetches as the user pages back and forth through the data set.`, 81 + 82 + filters: `## Submitted filter prefetch 83 + 84 + Filter-based prefetching improves search experiences by preloading results when a filter form is submitted. Instead of waiting for the filter to be applied after submission, data is already being fetched as the form processes. 85 + 86 + ### How it works 87 + 88 + 1. The user enters filter criteria in the form fields 89 + 2. The form is submitted, updating search parameters in the URL 90 + 3. React Query prefetches results for the new parameter set 91 + 4. Results render with minimal loading time 92 + 93 + ### Key considerations 94 + 95 + - **URL-driven state**: Filters are stored in search params, making them shareable and bookmarkable 96 + - **Cache management**: Each filter combination creates a unique cache entry with its own \`staleTime\` 97 + - **Type safety**: Valibot validates search params at the route boundary, ensuring the filter state is always valid`, 98 + 99 + "debounced-preload-filters": `## Debounced filter prefetch 100 + 101 + Debounced preloading extends filter-based prefetching by initiating fetches on every keystroke — not just on form submission. A debounce window ensures the server is not overwhelmed by rapid-fire requests. 102 + 103 + ### How it works 104 + 105 + 1. The user types in the filter input 106 + 2. Each keystroke updates the search params after a short debounce delay 107 + 3. React Query fires a prefetch for the new parameter values 108 + 4. Results update incrementally as the user types 109 + 5. The user sees live-filtered results without needing to press submit 110 + 111 + ### Debounce strategy 112 + 113 + The debounce delay balances responsiveness with server load. A 300–400ms window prevents unnecessary intermediate requests while still feeling snappy to the user. TanStack Pacer provides the debouncing primitive. 114 + 115 + ### When to use 116 + 117 + Ideal for search-as-you-type interfaces against reasonably sized or indexed datasets. Pair with server-side indexing for larger collections.`, 118 + 119 + "live-query": `## Electric SQL synced collection 120 + 121 + Live queries use Electric SQL to keep client-side data in sync with the server database in real time. Instead of polling or manual refetching, database changes are pushed to connected clients automatically. 122 + 123 + ### How it works 124 + 125 + 1. The client establishes a sync connection to the Electric SQL server 126 + 2. A live query is defined using the TanStack DB collection API 127 + 3. When database records change, updates stream to connected clients 128 + 4. The UI re-renders automatically with fresh data 129 + 130 + ### Benefits 131 + 132 + - **Real-time updates**: Changes appear immediately without manual refresh 133 + - **Offline support**: Local data remains available during connectivity interruptions 134 + - **Multi-user**: All connected clients see each other's changes in real time 135 + 136 + ### Trade-offs 137 + 138 + Live queries require the Electric SQL sync infrastructure. They introduce additional complexity compared to simple REST fetching but are the right choice for collaborative or real-time applications where data freshness matters.`, 139 + 140 + "live-query-filters": `## Reactive filtered live search 141 + 142 + Combining live queries with client-side filtering creates a real-time search experience. The full dataset stays in sync with the server while the client filters and displays results reactively as the user types. 143 + 144 + ### How it works 145 + 146 + 1. A live query fetches the full dataset and keeps it in sync 147 + 2. Client-side filtering narrows results based on user input 148 + 3. Results update immediately with every keystroke 149 + 4. Underlying database changes are reflected in real time 150 + 151 + ### Architecture 152 + 153 + Electric SQL handles the server-to-client sync layer, while TanStack DB collections manage live query subscriptions. Client-side filtering provides instant feedback without additional network requests, and URL search params keep the filter state shareable. 154 + 155 + ### Best for 156 + 157 + Dashboards, monitoring interfaces, multi-user collaboration tools, and any application where data freshness takes priority over raw query throughput.`, 158 + };
+44
src/data/local/collections.ts
··· 1 + import { BasicIndex, createCollection } from "@tanstack/react-db"; 2 + import { electricCollectionOptions } from "@tanstack/electric-db-collection"; 3 + import { snakeCamelMapper } from "@electric-sql/client"; 4 + 5 + import { pokemonSelectSchema, typesSelectSchema, pokemonTypesSelectSchema } from "~/data/schema"; 6 + 7 + export const pokemonCollection = createCollection( 8 + electricCollectionOptions({ 9 + shapeOptions: { 10 + url: "http://localhost:3000/api/shapes/pokemon", 11 + columnMapper: snakeCamelMapper(), 12 + }, 13 + getKey: (item) => item.id, 14 + schema: pokemonSelectSchema, 15 + }), 16 + ); 17 + pokemonCollection.createIndex((row) => row.id, { indexType: BasicIndex }); 18 + pokemonCollection.createIndex((row) => row.dexId, { indexType: BasicIndex }); 19 + 20 + export const typesCollection = createCollection( 21 + electricCollectionOptions({ 22 + shapeOptions: { 23 + url: "http://localhost:3000/api/shapes/types", 24 + columnMapper: snakeCamelMapper(), 25 + }, 26 + getKey: (item) => item.id, 27 + schema: typesSelectSchema, 28 + }), 29 + ); 30 + typesCollection.createIndex((row) => row.id, { indexType: BasicIndex }); 31 + 32 + export const pokemonTypesCollection = createCollection( 33 + electricCollectionOptions({ 34 + shapeOptions: { 35 + url: "http://localhost:3000/api/shapes/pokemon-types", 36 + columnMapper: snakeCamelMapper(), 37 + }, 38 + getKey: (item) => item.id, 39 + schema: pokemonTypesSelectSchema, 40 + }), 41 + ); 42 + pokemonTypesCollection.createIndex((row) => row.id, { indexType: BasicIndex }); 43 + pokemonTypesCollection.createIndex((row) => row.pokemonId, { indexType: BasicIndex }); 44 + pokemonTypesCollection.createIndex((row) => row.typeId, { indexType: BasicIndex });
+40
src/lib/lazily.ts
··· 1 + /* 2 + * src: github.com/JLarky/react-lazily/blob/main/src/core/lazily.ts 3 + * 4 + * MIT License 5 + * Copyright (c) 2020 JLarky 6 + * 7 + * Permission is hereby granted, free of charge, to any person obtaining a copy 8 + * of this software and associated documentation files (the "Software"), to deal 9 + * in the Software without restriction, including without limitation the rights 10 + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 + * copies of the Software, and to permit persons to whom the Software is 12 + * furnished to do so, subject to the following conditions: 13 + * 14 + * The above copyright notice and this permission notice shall be included in all 15 + * copies or substantial portions of the Software. 16 + * 17 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 + SOFTWARE. 24 + * 25 + */ 26 + 27 + import { lazy } from "react"; 28 + 29 + export const lazily = <T extends {}, U extends keyof T>(loader: (x?: string) => Promise<T>) => 30 + new Proxy({} as unknown as T, { 31 + get: (_target, componentName: string | symbol) => { 32 + if (typeof componentName === "string") { 33 + return lazy(() => 34 + loader(componentName).then((x) => ({ 35 + default: x[componentName as U] as any as React.ComponentType<any>, 36 + })), 37 + ); 38 + } 39 + }, 40 + });
+84
src/server/pokemon.functions.ts
··· 1 + import { createServerFn } from "@tanstack/react-start"; 2 + import * as v from "valibot"; 3 + import { DB } from "~/data/db"; 4 + import { POKEMON_LIMIT } from "~/constants"; 5 + 6 + const PokemonListParamsSchema = v.object({ 7 + offset: v.optional(v.number()), 8 + }); 9 + 10 + const FilteredPokemonListParamsSchema = v.object({ 11 + offset: v.optional(v.number()), 12 + nameFilter: v.optional(v.string()), 13 + }); 14 + 15 + const innerGetPokemonList = async (offset: number) => { 16 + // Fetch one extra item to check if there are more results 17 + const pokemon = await DB.queries.getPokemonAtOffset(offset, POKEMON_LIMIT + 1); 18 + 19 + // Check if there are more results by looking at the extra item 20 + const hasMore = pokemon.length > POKEMON_LIMIT; 21 + 22 + // Remove the extra item if it exists 23 + const results = hasMore ? pokemon.slice(0, -1) : pokemon; 24 + 25 + return { 26 + pokemon: results, 27 + nextOffset: hasMore ? offset + POKEMON_LIMIT : null, 28 + prevOffset: offset > 0 ? Math.max(0, offset - POKEMON_LIMIT) : null, 29 + }; 30 + }; 31 + 32 + const innerGetFilteredPokemonList = async (offset: number, nameFilter: string) => { 33 + // If no filter is provided, fall back to regular query 34 + if (!nameFilter.trim()) { 35 + return await innerGetPokemonList(offset); 36 + } 37 + 38 + // Fetch one extra item to check if there are more results 39 + const pokemon = await DB.queries.getFilteredPokemonAtOffset( 40 + offset, 41 + POKEMON_LIMIT + 1, 42 + `%${nameFilter.trim()}%`, 43 + ); 44 + 45 + // Check if there are more results by looking at the extra item 46 + const hasMore = pokemon.length > POKEMON_LIMIT; 47 + 48 + // Remove the extra item if it exists 49 + const results = hasMore ? pokemon.slice(0, -1) : pokemon; 50 + 51 + return { 52 + pokemon: results, 53 + nextOffset: hasMore ? offset + POKEMON_LIMIT : null, 54 + prevOffset: offset > 0 ? Math.max(0, offset - POKEMON_LIMIT) : null, 55 + appliedFilter: nameFilter.trim(), 56 + }; 57 + }; 58 + 59 + export const getServerPokemonList = createServerFn({ method: "GET" }) 60 + .inputValidator((params) => { 61 + const validated = v.parse(PokemonListParamsSchema, params); 62 + const offset = validated.offset ?? 0; 63 + 64 + if (offset < 0) throw new Error("Offset must be greater than or equal to 0"); 65 + 66 + return { offset }; 67 + }) 68 + .handler(async ({ data }) => { 69 + return await innerGetPokemonList(data.offset); 70 + }); 71 + 72 + export const getServerFilteredPokemonList = createServerFn({ method: "GET" }) 73 + .inputValidator((params) => { 74 + const validated = v.parse(FilteredPokemonListParamsSchema, params); 75 + const offset = validated.offset ?? 0; 76 + const nameFilter = validated.nameFilter ?? ""; 77 + 78 + if (offset < 0) throw new Error("Offset must be greater than or equal to 0"); 79 + 80 + return { offset, nameFilter }; 81 + }) 82 + .handler(async ({ data }) => { 83 + return await innerGetFilteredPokemonList(data.offset, data.nameFilter); 84 + });
+17
src/server/strategy-article.functions.tsx
··· 1 + import { createServerFn } from "@tanstack/react-start"; 2 + import { renderServerComponent } from "@tanstack/react-start/rsc"; 3 + import * as v from "valibot"; 4 + import { StrategyArticle } from "~/components/strategy-article"; 5 + import { strategyContent } from "~/content/strategies.server"; 6 + 7 + export const getStrategyArticle = createServerFn({ method: "GET" }) 8 + .inputValidator(v.object({ title: v.string(), slug: v.string() })) 9 + .handler(async ({ data }) => { 10 + const markdown = strategyContent[data.slug] ?? "Content coming soon."; 11 + 12 + const Renderable = await renderServerComponent( 13 + <StrategyArticle title={data.title} markdown={markdown} />, 14 + ); 15 + 16 + return { Renderable }; 17 + });
+23
src/utils/markdown.ts
··· 1 + import { unified } from "unified"; 2 + import remarkParse from "remark-parse"; 3 + import remarkGfm from "remark-gfm"; 4 + import remarkRehype from "remark-rehype"; 5 + import rehypeRaw from "rehype-raw"; 6 + import rehypeReact from "rehype-react"; 7 + import * as jsxRuntime from "react/jsx-runtime"; 8 + 9 + export async function renderMarkdown(content: string): Promise<React.ReactNode> { 10 + const file = await unified() 11 + .use(remarkParse) 12 + .use(remarkGfm) 13 + .use(remarkRehype, { allowDangerousHtml: true }) 14 + .use(rehypeRaw) 15 + .use(rehypeReact, { 16 + Fragment: jsxRuntime.Fragment, 17 + jsx: jsxRuntime.jsx, 18 + jsxs: jsxRuntime.jsxs, 19 + }) 20 + .process(content); 21 + 22 + return file.result as React.ReactNode; 23 + }
+28
src/utils/pokemon.ts
··· 1 + import type { QueryFunctionContext } from "@tanstack/react-query"; 2 + import { getServerPokemonList, getServerFilteredPokemonList } from "~/server/pokemon"; 3 + 4 + export const getPokemonListQueryKey = (location: string, offset: number) => { 5 + return ["pokemon-list", location, { offset }] as const; 6 + }; 7 + 8 + export const getFilteredPokemonListQueryKey = ( 9 + location: string, 10 + offset: number, 11 + nameFilter: string, 12 + ) => { 13 + return ["pokemon-list", location, { offset, nameFilter }] as const; 14 + }; 15 + 16 + export const getPokemonListQueryFn = async ({ 17 + queryKey, 18 + }: QueryFunctionContext<ReturnType<typeof getPokemonListQueryKey>>) => { 19 + const { offset } = queryKey[2]; 20 + return getServerPokemonList({ data: { offset } }); 21 + }; 22 + 23 + export const getFilteredPokemonListQueryFn = async ({ 24 + queryKey, 25 + }: QueryFunctionContext<ReturnType<typeof getFilteredPokemonListQueryKey>>) => { 26 + const { offset, nameFilter } = queryKey[2]; 27 + return getServerFilteredPokemonList({ data: { offset, nameFilter } }); 28 + };