this repo has no description
0
fork

Configure Feed

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

derp

+76 -398
+2 -5
package.json
··· 3 3 "private": true, 4 4 "type": "module", 5 5 "scripts": { 6 - "dev": "dotenvx run -f .env.local -- vite", 7 - "dev:base": "vite", 6 + "dev": "vite", 8 7 "build": "vite build", 9 - "start:base": "node .output/server/index.mjs", 10 - "start": "dotenvx run -f .env.local -- node .output/server/index.mjs", 8 + "start": "node .output/server/index.mjs", 11 9 "format": "biome format", 12 10 "lint": "biome lint", 13 11 "check": "biome check" ··· 40 38 }, 41 39 "devDependencies": { 42 40 "@biomejs/biome": "1.9.4", 43 - "@dotenvx/dotenvx": "^1.44.1", 44 41 "@types/node": "^22.15.21", 45 42 "@types/react": "^19.1.5", 46 43 "@types/react-dom": "^19.1.5",
-137
pnpm-lock.yaml
··· 84 84 '@biomejs/biome': 85 85 specifier: 1.9.4 86 86 version: 1.9.4 87 - '@dotenvx/dotenvx': 88 - specifier: ^1.44.1 89 - version: 1.44.1 90 87 '@types/node': 91 88 specifier: ^22.15.21 92 89 version: 22.15.21 ··· 318 315 resolution: {integrity: sha512-KrkT6qO5NxqNfy68sBl6CTSoJ4SNDIS5iQArkibhlbGU4LaDukZ3q2HIkh8aUKDio6o4itU4xDR7t82Y2eP1Bg==} 319 316 engines: {node: '>=14'} 320 317 321 - '@dotenvx/dotenvx@1.44.1': 322 - resolution: {integrity: sha512-j1QImCqf/XJmhIjC1OPpgiZV9g370HG9MNT9s/CDwCKsoYzNCPEKK+GfsidahJx7yIlBbm+4dPLlGec+bKn7oA==} 323 - hasBin: true 324 - 325 318 '@drizzle-team/brocli@0.10.2': 326 319 resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} 327 - 328 - '@ecies/ciphers@0.2.3': 329 - resolution: {integrity: sha512-tapn6XhOueMwht3E2UzY0ZZjYokdaw9XtL9kEyjhQ/Fb9vL9xTFbOaI+fV0AWvTpYu4BNloC6getKW6NtSg4mA==} 330 - engines: {bun: '>=1', deno: '>=2', node: '>=16'} 331 - peerDependencies: 332 - '@noble/ciphers': ^1.0.0 333 320 334 321 '@esbuild-kit/core-utils@3.3.2': 335 322 resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} ··· 761 748 engines: {node: ^14.18.0 || >=16.0.0} 762 749 hasBin: true 763 750 764 - '@noble/ciphers@1.3.0': 765 - resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} 766 - engines: {node: ^14.21.3 || >=16} 767 - 768 - '@noble/curves@1.9.1': 769 - resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} 770 - engines: {node: ^14.21.3 || >=16} 771 - 772 - '@noble/hashes@1.8.0': 773 - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} 774 - engines: {node: ^14.21.3 || >=16} 775 - 776 751 '@nodelib/fs.scandir@2.1.5': 777 752 resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 778 753 engines: {node: '>= 8'} ··· 1760 1735 resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} 1761 1736 engines: {node: '>=14'} 1762 1737 1763 - commander@11.1.0: 1764 - resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} 1765 - engines: {node: '>=16'} 1766 - 1767 1738 commander@2.20.3: 1768 1739 resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} 1769 1740 ··· 2111 2082 eastasianwidth@0.2.0: 2112 2083 resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 2113 2084 2114 - eciesjs@0.4.15: 2115 - resolution: {integrity: sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA==} 2116 - engines: {bun: '>=1', deno: '>=2', node: '>=16'} 2117 - 2118 2085 ee-first@1.1.1: 2119 2086 resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} 2120 2087 ··· 2240 2207 resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} 2241 2208 engines: {node: '>=0.8.x'} 2242 2209 2243 - execa@5.1.1: 2244 - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} 2245 - engines: {node: '>=10'} 2246 - 2247 2210 execa@7.2.0: 2248 2211 resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} 2249 2212 engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} ··· 2498 2461 httpxy@0.1.7: 2499 2462 resolution: {integrity: sha512-pXNx8gnANKAndgga5ahefxc++tJvNL87CXoRwxn1cJE2ZkWEojF3tNfQIEhZX/vfpt+wzeAzpUI4qkediX1MLQ==} 2500 2463 2501 - human-signals@2.1.0: 2502 - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} 2503 - engines: {node: '>=10.17.0'} 2504 - 2505 2464 human-signals@4.3.1: 2506 2465 resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} 2507 2466 engines: {node: '>=14.18.0'} ··· 2651 2610 isexe@2.0.0: 2652 2611 resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 2653 2612 2654 - isexe@3.1.1: 2655 - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} 2656 - engines: {node: '>=16'} 2657 - 2658 2613 jackspeak@3.4.3: 2659 2614 resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 2660 2615 ··· 2901 2856 engines: {node: '>=16'} 2902 2857 hasBin: true 2903 2858 2904 - mimic-fn@2.1.0: 2905 - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 2906 - engines: {node: '>=6'} 2907 - 2908 2859 mimic-fn@4.0.0: 2909 2860 resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} 2910 2861 engines: {node: '>=12'} ··· 3050 3001 resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 3051 3002 engines: {node: '>=0.10.0'} 3052 3003 3053 - npm-run-path@4.0.1: 3054 - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} 3055 - engines: {node: '>=8'} 3056 - 3057 3004 npm-run-path@5.3.0: 3058 3005 resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} 3059 3006 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} ··· 3081 3028 resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} 3082 3029 engines: {node: '>= 0.4'} 3083 3030 3084 - object-treeify@1.1.33: 3085 - resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} 3086 - engines: {node: '>= 10'} 3087 - 3088 3031 ofetch@1.4.1: 3089 3032 resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} 3090 3033 ··· 3103 3046 3104 3047 one-time@1.0.0: 3105 3048 resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} 3106 - 3107 - onetime@5.1.2: 3108 - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 3109 - engines: {node: '>=6'} 3110 3049 3111 3050 onetime@6.0.0: 3112 3051 resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} ··· 3589 3528 resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 3590 3529 engines: {node: '>=12'} 3591 3530 3592 - strip-final-newline@2.0.0: 3593 - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 3594 - engines: {node: '>=6'} 3595 - 3596 3531 strip-final-newline@3.0.0: 3597 3532 resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} 3598 3533 engines: {node: '>=12'} ··· 3992 3927 engines: {node: '>= 8'} 3993 3928 hasBin: true 3994 3929 3995 - which@4.0.0: 3996 - resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} 3997 - engines: {node: ^16.13.0 || >=18.0.0} 3998 - hasBin: true 3999 - 4000 3930 wide-align@1.1.5: 4001 3931 resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} 4002 3932 ··· 4313 4243 gonzales-pe: 4.3.0 4314 4244 node-source-walk: 6.0.2 4315 4245 4316 - '@dotenvx/dotenvx@1.44.1': 4317 - dependencies: 4318 - commander: 11.1.0 4319 - dotenv: 16.5.0 4320 - eciesjs: 0.4.15 4321 - execa: 5.1.1 4322 - fdir: 6.4.4(picomatch@4.0.2) 4323 - ignore: 5.3.2 4324 - object-treeify: 1.1.33 4325 - picomatch: 4.0.2 4326 - which: 4.0.0 4327 - 4328 4246 '@drizzle-team/brocli@0.10.2': {} 4329 - 4330 - '@ecies/ciphers@0.2.3(@noble/ciphers@1.3.0)': 4331 - dependencies: 4332 - '@noble/ciphers': 1.3.0 4333 4247 4334 4248 '@esbuild-kit/core-utils@3.3.2': 4335 4249 dependencies: ··· 4699 4613 - encoding 4700 4614 - rollup 4701 4615 - supports-color 4702 - 4703 - '@noble/ciphers@1.3.0': {} 4704 - 4705 - '@noble/curves@1.9.1': 4706 - dependencies: 4707 - '@noble/hashes': 1.8.0 4708 - 4709 - '@noble/hashes@1.8.0': {} 4710 4616 4711 4617 '@nodelib/fs.scandir@2.1.5': 4712 4618 dependencies: ··· 5870 5776 5871 5777 commander@10.0.1: {} 5872 5778 5873 - commander@11.1.0: {} 5874 - 5875 5779 commander@2.20.3: {} 5876 5780 5877 5781 common-path-prefix@3.0.0: {} ··· 6103 6007 duplexer@0.1.2: {} 6104 6008 6105 6009 eastasianwidth@0.2.0: {} 6106 - 6107 - eciesjs@0.4.15: 6108 - dependencies: 6109 - '@ecies/ciphers': 0.2.3(@noble/ciphers@1.3.0) 6110 - '@noble/ciphers': 1.3.0 6111 - '@noble/curves': 1.9.1 6112 - '@noble/hashes': 1.8.0 6113 6010 6114 6011 ee-first@1.1.1: {} 6115 6012 ··· 6249 6146 6250 6147 events@3.3.0: {} 6251 6148 6252 - execa@5.1.1: 6253 - dependencies: 6254 - cross-spawn: 7.0.6 6255 - get-stream: 6.0.1 6256 - human-signals: 2.1.0 6257 - is-stream: 2.0.1 6258 - merge-stream: 2.0.0 6259 - npm-run-path: 4.0.1 6260 - onetime: 5.1.2 6261 - signal-exit: 3.0.7 6262 - strip-final-newline: 2.0.0 6263 - 6264 6149 execa@7.2.0: 6265 6150 dependencies: 6266 6151 cross-spawn: 7.0.6 ··· 6586 6471 6587 6472 httpxy@0.1.7: {} 6588 6473 6589 - human-signals@2.1.0: {} 6590 - 6591 6474 human-signals@4.3.1: {} 6592 6475 6593 6476 human-signals@5.0.0: {} ··· 6700 6583 isbot@5.1.28: {} 6701 6584 6702 6585 isexe@2.0.0: {} 6703 - 6704 - isexe@3.1.1: {} 6705 6586 6706 6587 jackspeak@3.4.3: 6707 6588 dependencies: ··· 6946 6827 6947 6828 mime@4.0.7: {} 6948 6829 6949 - mimic-fn@2.1.0: {} 6950 - 6951 6830 mimic-fn@4.0.0: {} 6952 6831 6953 6832 minimatch@3.1.2: ··· 7160 7039 7161 7040 normalize-path@3.0.0: {} 7162 7041 7163 - npm-run-path@4.0.1: 7164 - dependencies: 7165 - path-key: 3.1.1 7166 - 7167 7042 npm-run-path@5.3.0: 7168 7043 dependencies: 7169 7044 path-key: 4.0.0 ··· 7193 7068 7194 7069 object-inspect@1.13.4: {} 7195 7070 7196 - object-treeify@1.1.33: {} 7197 - 7198 7071 ofetch@1.4.1: 7199 7072 dependencies: 7200 7073 destr: 2.0.5 ··· 7217 7090 dependencies: 7218 7091 fn.name: 1.1.0 7219 7092 7220 - onetime@5.1.2: 7221 - dependencies: 7222 - mimic-fn: 2.1.0 7223 - 7224 7093 onetime@6.0.0: 7225 7094 dependencies: 7226 7095 mimic-fn: 4.0.0 ··· 7718 7587 dependencies: 7719 7588 ansi-regex: 6.1.0 7720 7589 7721 - strip-final-newline@2.0.0: {} 7722 - 7723 7590 strip-final-newline@3.0.0: {} 7724 7591 7725 7592 strip-literal@3.0.0: ··· 8054 7921 which@2.0.2: 8055 7922 dependencies: 8056 7923 isexe: 2.0.0 8057 - 8058 - which@4.0.0: 8059 - dependencies: 8060 - isexe: 3.1.1 8061 7924 8062 7925 wide-align@1.1.5: 8063 7926 dependencies:
+1 -3
src/data/db.ts
··· 1 1 import { sql } from "drizzle-orm"; 2 2 import { drizzle } from "drizzle-orm/libsql"; 3 - 4 - import { env } from "~/env/server"; 5 3 import * as schema from "./schema"; 6 4 7 - const db = drizzle(env.DB_NAME, { schema }); 5 + const db = drizzle("file:./local.db", { schema }); 8 6 9 7 const preparedGetPokemonAtOffset = db.query.pokemon 10 8 .findMany({
-9
src/env/server.ts
··· 1 - import { createEnv } from "@t3-oss/env-core"; 2 - import * as v from "valibot"; 3 - 4 - export const env = createEnv({ 5 - server: { 6 - DB_NAME: v.string(), 7 - }, 8 - runtimeEnv: process.env, 9 - });
-2
src/reset.d.ts
··· 1 - // Do not add any other lines of code to this file! 2 - import "@total-typescript/ts-reset";
+11 -25
src/routes/basic.tsx
··· 3 3 import { buttonVariants } from "~/components/ui/button"; 4 4 5 5 import { useServerFn } from "@tanstack/react-start"; 6 - import { useMemo } from "react"; 7 6 import * as v from "valibot"; 8 7 import { 9 8 Table, ··· 42 41 }, 43 42 }); 44 43 45 - const previousOffset = useMemo(() => { 46 - if (data?.prevOffset == null) { 47 - return null; 48 - } 49 - 50 - return data.prevOffset.toString(); 51 - }, [data?.prevOffset]); 52 - 53 - const nextOffset = useMemo(() => { 54 - if (data?.nextOffset == null) { 55 - return null; 56 - } 57 - 58 - return data.nextOffset.toString(); 59 - }, [data?.nextOffset]); 60 - 61 44 if (data) { 62 - const results = data.pokemon ?? []; 63 45 return ( 64 46 <div className="p-4"> 65 47 <h1 className="text-2xl font-bold mb-4"> ··· 75 57 </TableRow> 76 58 </TableHeader> 77 59 <TableBody> 78 - {results.map((pokemon) => ( 60 + {data.pokemon.map((pokemon) => ( 79 61 <TableRow key={pokemon.name}> 80 62 <TableCell>{pokemon.id}</TableCell> 81 63 <TableCell className="capitalize">{pokemon.name}</TableCell> ··· 98 80 to="/basic" 99 81 className={buttonVariants({ 100 82 variant: "outline", 101 - className: cn(!previousOffset && "opacity-50 cursor-not-allowed"), 83 + className: cn( 84 + !data.prevOffset && "opacity-50 cursor-not-allowed", 85 + ), 102 86 })} 103 - search={{ offset: Number(previousOffset) }} 104 - disabled={!previousOffset} 87 + search={{ offset: Number(data.prevOffset) }} 88 + disabled={!data.prevOffset} 105 89 > 106 90 Previous 107 91 </Link> 108 92 <Link 109 93 to="/basic" 110 - search={{ offset: Number(nextOffset) }} 94 + search={{ offset: Number(data.nextOffset) }} 111 95 className={buttonVariants({ 112 96 variant: "outline", 113 - className: cn(!nextOffset && "opacity-50 cursor-not-allowed"), 97 + className: cn( 98 + !data.nextOffset && "opacity-50 cursor-not-allowed", 99 + ), 114 100 })} 115 - disabled={!nextOffset} 101 + disabled={!data.nextOffset} 116 102 > 117 103 Next 118 104 </Link>
+13 -52
src/routes/intent-preloading.tsx
··· 2 2 import { Link } from "@tanstack/react-router"; 3 3 import { buttonVariants } from "~/components/ui/button"; 4 4 5 - import { useMemo } from "react"; 6 5 import * as v from "valibot"; 7 6 import { 8 7 Table, ··· 13 12 TableRow, 14 13 } from "~/components/ui/table"; 15 14 import { cn } from "~/lib/utils"; 16 - import { getServerPokemonList } from "~/util/pokemon"; 17 - 18 - const POKEMON_LIMIT = 20; 15 + import { POKEMON_LIMIT, getServerPokemonList } from "~/util/pokemon"; 19 16 20 17 const matchPokemonIdExp = /\/api\/v2\/pokemon\/(\d+)\/?/; 21 18 ··· 38 35 const pokemonListOptions = queryOptions({ 39 36 queryKey: newKey, 40 37 queryFn: async () => { 41 - const result = await getServerPokemonList({ 38 + return await getServerPokemonList({ 42 39 data: { offset: deps.offset }, 43 40 }); 44 - return { 45 - results: result.pokemon.map((p) => ({ 46 - name: p.name, 47 - url: `/api/v2/pokemon/${p.id}/`, 48 - })), 49 - next: 50 - result.nextOffset !== null 51 - ? `/api/v2/pokemon/?offset=${result.nextOffset}` 52 - : null, 53 - previous: 54 - result.prevOffset !== null 55 - ? `/api/v2/pokemon/?offset=${result.prevOffset}` 56 - : null, 57 - }; 58 41 }, 59 42 }); 60 43 ··· 94 77 95 78 const { data } = useSuspenseQuery(pokemonListOptions); 96 79 97 - const previousOffset = useMemo(() => { 98 - if (data?.previous == null) { 99 - return null; 100 - } 101 - 102 - return new URL(data.previous).searchParams.get("offset") ?? null; 103 - }, [data?.previous]); 104 - 105 - const nextOffset = useMemo(() => { 106 - if (data?.next == null) { 107 - return null; 108 - } 109 - 110 - return new URL(data.next).searchParams.get("offset") ?? null; 111 - }, [data?.next]); 112 - 113 - const results = data.results ?? []; 114 80 return ( 115 81 <div className="p-4"> 116 82 <h1 className="text-2xl font-bold mb-4"> ··· 126 92 </TableRow> 127 93 </TableHeader> 128 94 <TableBody> 129 - {results.map((pokemon: PokemonListResult) => ( 95 + {data.pokemon.map((pokemon) => ( 130 96 <TableRow key={pokemon.name}> 131 - <TableCell>{pokemon.url.match(matchPokemonIdExp)?.[1]}</TableCell> 97 + <TableCell>{pokemon.id}</TableCell> 132 98 <TableCell className="capitalize">{pokemon.name}</TableCell> 133 99 <TableCell> 134 - <a 135 - href={pokemon.url} 136 - target="_blank" 137 - rel="noopener noreferrer" 138 - className="text-blue-600 underline" 139 - > 140 - View 141 - </a> 100 + {pokemon.types.map((type) => ( 101 + <span key={type.type.name}>{type.type.name}</span> 102 + ))} 142 103 </TableCell> 143 104 </TableRow> 144 105 ))} ··· 150 111 preload="intent" 151 112 className={buttonVariants({ 152 113 variant: "outline", 153 - className: cn(!previousOffset && "opacity-50 cursor-not-allowed"), 114 + className: cn(!data.prevOffset && "opacity-50 cursor-not-allowed"), 154 115 })} 155 - search={{ offset: Number(previousOffset) }} 156 - disabled={!previousOffset} 116 + search={{ offset: Number(data.prevOffset) }} 117 + disabled={!data.prevOffset} 157 118 > 158 119 Previous 159 120 </Link> 160 121 <Link 161 122 to="/intent-preloading" 162 123 preload="intent" 163 - search={{ offset: Number(nextOffset) }} 124 + search={{ offset: Number(data.nextOffset) }} 164 125 className={buttonVariants({ 165 126 variant: "outline", 166 - className: cn(!nextOffset && "opacity-50 cursor-not-allowed"), 127 + className: cn(!data.nextOffset && "opacity-50 cursor-not-allowed"), 167 128 })} 168 - disabled={!nextOffset} 129 + disabled={!data.nextOffset} 169 130 > 170 131 Next 171 132 </Link>
+18 -55
src/routes/pagination.tsx
··· 6 6 import { Link } from "@tanstack/react-router"; 7 7 import { buttonVariants } from "~/components/ui/button"; 8 8 9 - import { useMemo } from "react"; 10 9 import * as v from "valibot"; 11 10 import { 12 11 Table, ··· 17 16 TableRow, 18 17 } from "~/components/ui/table"; 19 18 import { cn } from "~/lib/utils"; 20 - import { getServerPokemonList } from "~/util/pokemon"; 21 - 22 - const POKEMON_LIMIT = 20; 23 - 24 - const matchPokemonIdExp = /\/api\/v2\/pokemon\/(\d+)\/?/; 19 + import { POKEMON_LIMIT, getServerPokemonList } from "~/util/pokemon"; 25 20 26 21 const searchParamsSchema = v.object({ 27 22 offset: v.optional(v.number(), 0), ··· 36 31 const newKey = [ 37 32 "pokemon-list", 38 33 "pagination", 39 - { limit: POKEMON_LIMIT, offset: deps.offset }, 34 + { offset: deps.offset }, 40 35 ] as const; 41 36 42 37 const pokemonListOptions = queryOptions({ 43 38 queryKey: newKey, 44 39 queryFn: async ({ queryKey }) => { 45 - const result = await getServerPokemonList({ 40 + return await getServerPokemonList({ 46 41 data: { offset: queryKey[2].offset }, 47 42 }); 48 - 49 - return result.pokemon; 50 43 }, 51 44 }); 52 45 ··· 87 80 88 81 const { data } = useSuspenseQuery(pokemonListOptions); 89 82 90 - const previousOffset = useMemo(() => { 91 - if (data?.previous == null) { 92 - return null; 93 - } 94 - 95 - return new URL(data.previous).searchParams.get("offset") ?? null; 96 - }, [data?.previous]); 97 - 98 - const nextOffset = useMemo(() => { 99 - if (data?.next == null) { 100 - return null; 101 - } 102 - 103 - return new URL(data.next).searchParams.get("offset") ?? null; 104 - }, [data?.next]); 105 - 106 - if (previousOffset !== null) { 83 + if (data.prevOffset !== null) { 107 84 void queryClient.prefetchQuery({ 108 85 ...pokemonListOptions, 109 - queryKey: [ 110 - "pokemon-list", 111 - "pagination", 112 - { limit: POKEMON_LIMIT, offset: Number(previousOffset) }, 113 - ], 86 + queryKey: ["pokemon-list", "pagination", { offset: data.prevOffset }], 114 87 }); 115 88 } 116 89 117 - if (nextOffset !== null) { 90 + if (data.nextOffset !== null) { 118 91 void queryClient.prefetchQuery({ 119 92 ...pokemonListOptions, 120 - queryKey: [ 121 - "pokemon-list", 122 - "pagination", 123 - { limit: POKEMON_LIMIT, offset: Number(nextOffset) }, 124 - ], 93 + queryKey: ["pokemon-list", "pagination", { offset: data.nextOffset }], 125 94 }); 126 95 } 127 96 128 - const results = data.results ?? []; 129 97 return ( 130 98 <div className="p-4"> 131 99 <h1 className="text-2xl font-bold mb-4"> ··· 141 109 </TableRow> 142 110 </TableHeader> 143 111 <TableBody> 144 - {results.map((pokemon: PokemonListResult) => ( 112 + {data.pokemon.map((pokemon) => ( 145 113 <TableRow key={pokemon.name}> 146 - <TableCell>{pokemon.url.match(matchPokemonIdExp)?.[1]}</TableCell> 114 + <TableCell>{pokemon.id}</TableCell> 147 115 <TableCell className="capitalize">{pokemon.name}</TableCell> 148 116 <TableCell> 149 - <a 150 - href={pokemon.url} 151 - target="_blank" 152 - rel="noopener noreferrer" 153 - className="text-blue-600 underline" 154 - > 155 - View 156 - </a> 117 + {pokemon.types.map((type) => ( 118 + <span key={type.type.name}>{type.type.name}</span> 119 + ))} 157 120 </TableCell> 158 121 </TableRow> 159 122 ))} ··· 165 128 preload="intent" 166 129 className={buttonVariants({ 167 130 variant: "outline", 168 - className: cn(!previousOffset && "opacity-50 cursor-not-allowed"), 131 + className: cn(!data.prevOffset && "opacity-50 cursor-not-allowed"), 169 132 })} 170 - search={{ offset: Number(previousOffset) }} 171 - disabled={!previousOffset} 133 + search={{ offset: Number(data.prevOffset) }} 134 + disabled={!data.prevOffset} 172 135 > 173 136 Previous 174 137 </Link> 175 138 <Link 176 139 to="/pagination" 177 140 preload="intent" 178 - search={{ offset: Number(nextOffset) }} 141 + search={{ offset: Number(data.nextOffset) }} 179 142 className={buttonVariants({ 180 143 variant: "outline", 181 - className: cn(!nextOffset && "opacity-50 cursor-not-allowed"), 144 + className: cn(!data.nextOffset && "opacity-50 cursor-not-allowed"), 182 145 })} 183 - disabled={!nextOffset} 146 + disabled={!data.nextOffset} 184 147 > 185 148 Next 186 149 </Link>
+13 -51
src/routes/preloading.tsx
··· 13 13 TableRow, 14 14 } from "~/components/ui/table"; 15 15 import { cn } from "~/lib/utils"; 16 - import { getServerPokemonList } from "~/util/pokemon"; 17 - 18 - const POKEMON_LIMIT = 20; 16 + import { POKEMON_LIMIT, getServerPokemonList } from "~/util/pokemon"; 19 17 20 18 const matchPokemonIdExp = /\/api\/v2\/pokemon\/(\d+)\/?/; 21 19 ··· 38 36 const pokemonListOptions = queryOptions({ 39 37 queryKey: newKey, 40 38 queryFn: async () => { 41 - const result = await getServerPokemonList({ 39 + return await getServerPokemonList({ 42 40 data: { offset: deps.offset }, 43 41 }); 44 - return { 45 - results: result.pokemon.map((p) => ({ 46 - name: p.name, 47 - url: `/api/v2/pokemon/${p.id}/`, 48 - })), 49 - next: 50 - result.nextOffset !== null 51 - ? `/api/v2/pokemon/?offset=${result.nextOffset}` 52 - : null, 53 - previous: 54 - result.prevOffset !== null 55 - ? `/api/v2/pokemon/?offset=${result.prevOffset}` 56 - : null, 57 - }; 58 42 }, 59 43 }); 60 44 ··· 94 78 95 79 const { data } = useSuspenseQuery(pokemonListOptions); 96 80 97 - const previousOffset = useMemo(() => { 98 - if (data?.previous == null) { 99 - return null; 100 - } 101 - 102 - return new URL(data.previous).searchParams.get("offset") ?? null; 103 - }, [data?.previous]); 104 - 105 - const nextOffset = useMemo(() => { 106 - if (data?.next == null) { 107 - return null; 108 - } 109 - 110 - return new URL(data.next).searchParams.get("offset") ?? null; 111 - }, [data?.next]); 112 - 113 - const results = data.results ?? []; 114 81 return ( 115 82 <div className="p-4"> 116 83 <h1 className="text-2xl font-bold mb-4"> ··· 126 93 </TableRow> 127 94 </TableHeader> 128 95 <TableBody> 129 - {results.map((pokemon: PokemonListResult) => ( 96 + {data.pokemon.map((pokemon) => ( 130 97 <TableRow key={pokemon.name}> 131 - <TableCell>{pokemon.url.match(matchPokemonIdExp)?.[1]}</TableCell> 98 + <TableCell>{pokemon.id}</TableCell> 132 99 <TableCell className="capitalize">{pokemon.name}</TableCell> 133 100 <TableCell> 134 - <a 135 - href={pokemon.url} 136 - target="_blank" 137 - rel="noopener noreferrer" 138 - className="text-blue-600 underline" 139 - > 140 - View 141 - </a> 101 + {pokemon.types.map((type) => ( 102 + <span key={type.type.name}>{type.type.name}</span> 103 + ))} 142 104 </TableCell> 143 105 </TableRow> 144 106 ))} ··· 149 111 to="/preloading" 150 112 className={buttonVariants({ 151 113 variant: "outline", 152 - className: cn(!previousOffset && "opacity-50 cursor-not-allowed"), 114 + className: cn(!data.prevOffset && "opacity-50 cursor-not-allowed"), 153 115 })} 154 - search={{ offset: Number(previousOffset) }} 155 - disabled={!previousOffset} 116 + search={{ offset: Number(data.prevOffset) }} 117 + disabled={!data.prevOffset} 156 118 > 157 119 Previous 158 120 </Link> 159 121 <Link 160 122 to="/preloading" 161 - search={{ offset: Number(nextOffset) }} 123 + search={{ offset: Number(data.nextOffset) }} 162 124 className={buttonVariants({ 163 125 variant: "outline", 164 - className: cn(!nextOffset && "opacity-50 cursor-not-allowed"), 126 + className: cn(!data.nextOffset && "opacity-50 cursor-not-allowed"), 165 127 })} 166 - disabled={!nextOffset} 128 + disabled={!data.nextOffset} 167 129 > 168 130 Next 169 131 </Link>
+18 -59
src/routes/suspense.tsx
··· 2 2 import { Link } from "@tanstack/react-router"; 3 3 import { buttonVariants } from "~/components/ui/button"; 4 4 5 - import { useMemo } from "react"; 6 5 import * as v from "valibot"; 7 6 import { 8 7 Table, ··· 13 12 TableRow, 14 13 } from "~/components/ui/table"; 15 14 import { cn } from "~/lib/utils"; 16 - import { getPokemonList, getServerPokemonList } from "~/util/pokemon"; 17 - 18 - const POKEMON_LIMIT = 20; 19 - 20 - const matchPokemonIdExp = /\/api\/v2\/pokemon\/(\d+)\/?/; 15 + import { POKEMON_LIMIT, getServerPokemonList } from "~/util/pokemon"; 21 16 22 17 const searchParamsSchema = v.object({ 23 18 offset: v.optional(v.number(), 0), ··· 31 26 pendingComponent: LoadingComponent, 32 27 }); 33 28 34 - interface PokemonListResult { 35 - name: string; 36 - url: string; 37 - } 38 - 39 29 function NotFoundComponent() { 40 30 return <div>Not Found</div>; 41 31 } ··· 60 50 const { data } = useSuspenseQuery({ 61 51 queryKey: newKey, 62 52 queryFn: async () => { 63 - const result = await getServerPokemonList({ 53 + return await getServerPokemonList({ 64 54 data: { offset: currentOffset }, 65 55 }); 66 - return { 67 - results: result.pokemon.map((p) => ({ 68 - name: p.name, 69 - url: `/api/v2/pokemon/${p.id}/`, 70 - })), 71 - next: 72 - result.nextOffset !== null 73 - ? `/api/v2/pokemon/?offset=${result.nextOffset}` 74 - : null, 75 - previous: 76 - result.prevOffset !== null 77 - ? `/api/v2/pokemon/?offset=${result.prevOffset}` 78 - : null, 79 - }; 80 56 }, 81 57 }); 82 58 83 - const previousOffset = useMemo(() => { 84 - if (data?.previous == null) { 85 - return null; 86 - } 87 - 88 - return new URL(data.previous).searchParams.get("offset") ?? null; 89 - }, [data?.previous]); 90 - 91 - const nextOffset = useMemo(() => { 92 - if (data?.next == null) { 93 - return null; 94 - } 95 - 96 - return new URL(data.next).searchParams.get("offset") ?? null; 97 - }, [data?.next]); 98 - 99 - const results = data.results ?? []; 100 59 return ( 101 60 <div className="p-4"> 102 61 <h1 className="text-2xl font-bold mb-4"> ··· 112 71 </TableRow> 113 72 </TableHeader> 114 73 <TableBody> 115 - {results.map((pokemon: PokemonListResult) => ( 74 + {data.pokemon.map((pokemon) => ( 116 75 <TableRow key={pokemon.name}> 117 - <TableCell>{pokemon.url.match(matchPokemonIdExp)?.[1]}</TableCell> 76 + <TableCell>{pokemon.id}</TableCell> 118 77 <TableCell className="capitalize">{pokemon.name}</TableCell> 119 78 <TableCell> 120 - <a 121 - href={pokemon.url} 122 - target="_blank" 123 - rel="noopener noreferrer" 124 - className="text-blue-600 underline" 125 - > 126 - View 127 - </a> 79 + {pokemon.types.map((type) => ( 80 + <span 81 + key={type.type.name} 82 + className="inline-block px-2 py-1 mr-1 text-sm font-medium rounded-full bg-gray-100" 83 + > 84 + {type.type.name} 85 + </span> 86 + ))} 128 87 </TableCell> 129 88 </TableRow> 130 89 ))} ··· 135 94 to="/suspense" 136 95 className={buttonVariants({ 137 96 variant: "outline", 138 - className: cn(!previousOffset && "opacity-50 cursor-not-allowed"), 97 + className: cn(!data.prevOffset && "opacity-50 cursor-not-allowed"), 139 98 })} 140 - search={{ offset: Number(previousOffset) }} 141 - disabled={!previousOffset} 99 + search={{ offset: Number(data.prevOffset) }} 100 + disabled={!data.prevOffset} 142 101 > 143 102 Previous 144 103 </Link> 145 104 <Link 146 105 to="/suspense" 147 - search={{ offset: Number(nextOffset) }} 106 + search={{ offset: Number(data.nextOffset) }} 148 107 className={buttonVariants({ 149 108 variant: "outline", 150 - className: cn(!nextOffset && "opacity-50 cursor-not-allowed"), 109 + className: cn(!data.nextOffset && "opacity-50 cursor-not-allowed"), 151 110 })} 152 - disabled={!nextOffset} 111 + disabled={!data.nextOffset} 153 112 > 154 113 Next 155 114 </Link>