Compare commits

...

10 Commits

23 changed files with 5096 additions and 752 deletions

21
app.config.ts Normal file
View File

@@ -0,0 +1,21 @@
// app.config.ts
import { defineConfig } from '@tanstack/react-start/config';
import tsConfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
tsr: {
appDirectory: 'src',
},
server: {
prerender: {
routes: ['/about'],
},
},
vite: {
plugins: [
tsConfigPaths({
projects: ['./tsconfig.json'],
}),
],
},
});

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="icon" href="/favicon.ico"/>
<meta
name="description"
content="TanStack/Router Address Book"
/>
<link rel="apple-touch-icon" href="/logo192.png"/>
<link rel="manifest" href="/manifest.json"/>
<title>Create TanStack App - tsr-address-book</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -3,41 +3,48 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "vite --port 3000", "dev": "vinxi dev",
"build": "vite build && tsc", "build": "vinxi build",
"serve": "vite preview", "start": "vinxi start",
"test": "vitest run" "test": "vitest run"
}, },
"dependencies": { "dependencies": {
"@tanstack/react-router": "^1.114.3", "@tanstack/react-router": "^1.114.29",
"@tanstack/react-router-devtools": "^1.114.3", "@tanstack/react-router-devtools": "^1.114.29",
"@tanstack/router-plugin": "^1.114.3", "@tanstack/react-start": "^1.114.30",
"react": "^19.0.0", "@tanstack/router-plugin": "^1.114.30",
"react-dom": "^19.0.0", "lowdb": "^7.0.1",
"match-sorter": "^8.0.0", "match-sorter": "^8.0.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"server-only": "^0.0.1",
"sort-by": "^1.2.0", "sort-by": "^1.2.0",
"tiny-invariant": "^1.3.3" "tiny-invariant": "^1.3.3",
"vinxi": "^0.5.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.23.0", "@eslint/js": "^9.23.0",
"@tanstack/eslint-plugin-router": "^1.114.29", "@tanstack/eslint-plugin-router": "^1.114.29",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@types/react": "^19.0.8", "@types/node": "^22.13.17",
"@types/react-dom": "^19.0.3", "@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.23.0", "eslint": "^9.23.0",
"eslint-plugin-react": "^7.37.4", "eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.29.0",
"vite": "^6.1.0", "vite": "^6.2.4",
"vitest": "^3.0.5", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.1",
"web-vitals": "^4.2.4" "web-vitals": "^4.2.4"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"@parcel/watcher",
"esbuild" "esbuild"
] ]
} }

4464
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

53
public/logo.svg Normal file
View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1"
xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 841.9 595.3">
<!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 146) -->
<defs>
<style>
.st0 {
fill: #9ae7fc;
}
.st1 {
fill: #61dafb;
}
</style>
</defs>
<g>
<path class="st1"
d="M666.3,296.5c0-32.5-40.7-63.3-103.1-82.4,14.4-63.6,8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6,0,8.3.9,11.4,2.6,13.6,7.8,19.5,37.5,14.9,75.7-1.1,9.4-2.9,19.3-5.1,29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50,32.6-30.3,63.2-46.9,84-46.9v-22.3c-27.5,0-63.5,19.6-99.9,53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7,0,51.4,16.5,84,46.6-14,14.7-28,31.4-41.3,49.9-22.6,2.4-44,6.1-63.6,11-2.3-10-4-19.7-5.2-29-4.7-38.2,1.1-67.9,14.6-75.8,3-1.8,6.9-2.6,11.5-2.6v-22.3c-8.4,0-16,1.8-22.6,5.6-28.1,16.2-34.4,66.7-19.9,130.1-62.2,19.2-102.7,49.9-102.7,82.3s40.7,63.3,103.1,82.4c-14.4,63.6-8,114.2,20.2,130.4,6.5,3.8,14.1,5.6,22.5,5.6,27.5,0,63.5-19.6,99.9-53.6,36.4,33.8,72.4,53.2,99.9,53.2,8.4,0,16-1.8,22.6-5.6,28.1-16.2,34.4-66.7,19.9-130.1,62-19.1,102.5-49.9,102.5-82.3zm-130.2-66.7c-3.7,12.9-8.3,26.2-13.5,39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4,14.2,2.1,27.9,4.7,41,7.9zm-45.8,106.5c-7.8,13.5-15.8,26.3-24.1,38.2-14.9,1.3-30,2-45.2,2s-30.2-.7-45-1.9c-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8,6.2-13.4,13.2-26.8,20.7-39.9,7.8-13.5,15.8-26.3,24.1-38.2,14.9-1.3,30-2,45.2-2s30.2.7,45,1.9c8.3,11.9,16.4,24.6,24.2,38,7.6,13.1,14.5,26.4,20.8,39.8-6.3,13.4-13.2,26.8-20.7,39.9zm32.3-13c5.4,13.4,10,26.8,13.8,39.8-13.1,3.2-26.9,5.9-41.2,8,4.9-7.7,9.8-15.6,14.4-23.7,4.6-8,8.9-16.1,13-24.1zm-101.4,106.7c-9.3-9.6-18.6-20.3-27.8-32,9,.4,18.2.7,27.5.7s18.7-.2,27.8-.7c-9,11.7-18.3,22.4-27.5,32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9,3.7-12.9,8.3-26.2,13.5-39.5,4.1,8,8.4,16,13.1,24s9.5,15.8,14.4,23.4zm73.9-208.1c9.3,9.6,18.6,20.3,27.8,32-9-.4-18.2-.7-27.5-.7s-18.7.2-27.8.7c9-11.7,18.3-22.4,27.5-32zm-74,58.9c-4.9,7.7-9.8,15.6-14.4,23.7-4.6,8-8.9,16-13,24-5.4-13.4-10-26.8-13.8-39.8,13.1-3.1,26.9-5.8,41.2-7.9zm-90.5,125.2c-35.4-15.1-58.3-34.9-58.3-50.6s22.9-35.6,58.3-50.6c8.6-3.7,18-7,27.7-10.1,5.7,19.6,13.2,40,22.5,60.9-9.2,20.8-16.6,41.1-22.2,60.6-9.9-3.1-19.3-6.5-28-10.2zm53.8,142.9c-13.6-7.8-19.5-37.5-14.9-75.7,1.1-9.4,2.9-19.3,5.1-29.4,19.6,4.8,41,8.5,63.5,10.9,13.5,18.5,27.5,35.3,41.6,50-32.6,30.3-63.2,46.9-84,46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7,38.2-1.1,67.9-14.6,75.8-3,1.8-6.9,2.6-11.5,2.6-20.7,0-51.4-16.5-84-46.6,14-14.7,28-31.4,41.3-49.9,22.6-2.4,44-6.1,63.6-11,2.3,10.1,4.1,19.8,5.2,29.1zm38.5-66.7c-8.6,3.7-18,7-27.7,10.1-5.7-19.6-13.2-40-22.5-60.9,9.2-20.8,16.6-41.1,22.2-60.6,9.9,3.1,19.3,6.5,28.1,10.2,35.4,15.1,58.3,34.9,58.3,50.6,0,15.7-23,35.6-58.4,50.6zm-264.9-268.7z"/>
<circle class="st1" cx="420.9" cy="296.5" r="45.7"/>
<path class="st1" d="M520.5,78.1"/>
</g>
<circle class="st0" cx="420.8" cy="296.6" r="43"/>
<path class="st1"
d="M466.1,296.6c0,25-20.2,45.2-45.2,45.2s-45.2-20.2-45.2-45.2,20.2-45.2,45.2-45.2,45.2,20.2,45.2,45.2ZM386,295.6v-6.3c0-1.1,1.2-5.1,1.8-6.2,1-1.9,2.9-3.5,4.6-4.7l-3.4-3.4c4-3.6,9.4-3.7,13.7-.7,1.9-4.7,6.6-7.1,11.6-6.7l-.8,4.2c5.9.2,13.1,4.1,13.1,10.8s0,.5-.7.7c-1.7.3-3.4-.4-5-.6s-1.2-.4-1.2.3,2.5,4.1,3,5.5,1,3.5.8,5.3c-5.6-.8-10.5-3.2-14.8-6.7.3,2.6,4.1,21.7,5.3,21.9s.8-.6,1-1.1,1.3-6.3,1.3-6.7c0-1-1.7-1.8-2.2-2.8-1.2-2.7,1.3-4.7,3.7-3.3s5.2,6.2,7.5,7.3,13,1.4,14.8,3.3-2.9,4.6-1.5,7.6c6.7-2.6,13.5-3.3,20.6-2.5,3.1-9.7,3.1-20.3-.9-29.8-7.3,0-14.7-3.6-17.2-10.8-2.5-7.2-.7-8.6-1.3-9.3-.8-1-6.3.6-7.4-1.5s.3-1.1-.2-1.4-1.9-.6-2.6-.8c-26-6.4-51.3,15.7-49.7,42.1,0,1.6,1.6,10.3,2.4,11.1s4.8,0,6.3,0,3.7.3,5,.5c2.9.4,7.2,2.4,9.4,2.5s2.4-.8,2.7-2.4c.4-2.6.5-7.4.5-10.1s-1-7.8-1.3-11.6c-.9-.2-.7,0-.9.5-.7,1.3-1.1,3.2-1.9,4.8s-5.2,8.7-5.7,9-.7-.5-.8-.8c-1.6-3.5-2-7.9-1.9-11.8-.9-1-5.4,4.9-6.7,5.3l-.8-.4v-.3h-.2ZM455.6,276.4c1.1-1.2-6-8.9-7.2-10-3-2.7-5.4-4.5-3.5,1.4s5.7,7.8,10.6,8.5h.1ZM410.9,270.1c-.4-.5-6.1,2.9-5.5,4.6,1.9-1.3,5.9-1.7,5.5-4.6ZM400.4,276.4c-.3-2.4-6.3-2.7-7.2-1s1.6,1.4,1.9,1.4c1.8.3,3.5-.6,5.2-.4h.1ZM411.3,276.8c3.8,1.3,6.6,3.6,10.9,3.7s0-3-1.2-3.9c-2.2-1.7-5.1-2.4-7.8-2.4s-1.6-.3-1.4.4c2.8.6,7.3.7,8.4,3.8-2.3-.3-3.9-1.6-6.2-2s-2.5-.5-2.6.3h0ZM420.6,290.3c-.8-5.1-5.7-10.8-10.9-11.6s-1.3-.4-.8.5,4.7,3.2,5.7,4,4.5,4.2,2.1,3.8-8.4-7.8-9.4-6.7c.2.9,1.1,1.9,1.7,2.7,3,3.8,6.9,6.8,11.8,7.4h-.2ZM395.3,279.8c-5,1.1-6.9,6.3-6.7,11,.7.8,5-3.8,5.4-4.5s2.7-4.6,1.1-4-2.9,4.4-4.2,4.6.2-2.1.4-2.5c1.1-1.6,2.9-3.1,4-4.6h0ZM400.4,281.5c-.4-.5-2,1.3-2.3,1.7-2.9,3.9-2.6,10.2-1.5,14.8.8.2.8-.3,1.2-.7,3-3.8,5.5-10.5,4.5-15.4-2.1,3.1-3.1,7.3-3.6,11h-1.3c0-4,1.9-7.7,3-11.4h0ZM426.9,305.9c0-1.7-1.7-1.4-2.5-1.9s-1.3-1.9-3-1.4c1.3,2.1,3,3.2,5.5,3.4h0ZM417.2,308.5c7.6.7,5.5-1.9,1.4-5.5-1.3-.3-1.5,4.5-1.4,5.5ZM437,309.7c-3.5-.3-7.8-2-11.2-2.1s-1.3,0-1.9.7c4,1.3,8.4,1.7,12.1,4l1-2.5h0ZM420.5,312.8c-7.3,0-15.1,3.7-20.4,8.8s-4.8,5.3-4.8,6.2c0,1.8,8.6,6.2,10.5,6.8,12.1,4.8,27.5,3.5,38.2-4.2s3.1-2.7,0-6.2c-5.7-6.6-14.7-11.4-23.4-11.3h-.1ZM398.7,316.9c-1.4-1.4-5-1.9-7-2.1s-5.3-.3-6.9.6l13.9,1.4h0ZM456.9,314.8h-7.4c-.9,0-4.9,1.1-6,1.6s-.8.6,0,.5c2.4,0,5.1-1,7.6-1.3s3.5.2,5.1,0,1.3-.3.6-.8h0Z"/>
<path class="st0"
d="M386,295.6l.8.4c1.3-.3,5.8-6.2,6.7-5.3,0,3.9.3,8.3,1.9,11.8s0,1.2.8.8,5.1-7.8,5.7-9,1.3-3.5,1.9-4.8,0-.7.9-.5c.3,3.8,1.2,7.8,1.3,11.6s0,7.5-.5,10.1-1.1,2.4-2.7,2.4-6.5-2.1-9.4-2.5-3.7-.5-5-.5-5.4,1.1-6.3,0-2.2-9.5-2.4-11.1c-1.5-26.4,23.7-48.5,49.7-42.1s2.2.4,2.6.8,0,1,.2,1.4c1.1,2,6.5.5,7.4,1.5s.4,6.9,1.3,9.3c2.5,7.2,10,10.9,17.2,10.8,4,9.4,4,20.1.9,29.8-7.2-.7-13.9,0-20.6,2.5-1.3-3.1,4.1-5.1,1.5-7.6s-11.8-1.9-14.8-3.3-5.4-6.1-7.5-7.3-4.9.6-3.7,3.3,2.1,1.8,2.2,2.8-1,6.2-1.3,6.7-.3,1.3-1,1.1c-1.1-.3-5-19.3-5.3-21.9,4.3,3.5,9.2,5.9,14.8,6.7.2-1.9-.3-3.5-.8-5.3s-3-5.1-3-5.5c0-.8.9-.3,1.2-.3,1.6,0,3.3.8,5,.6s.7.3.7-.7c0-6.6-7.2-10.6-13.1-10.8l.8-4.2c-5.1-.3-9.6,2-11.6,6.7-4.3-3-9.8-3-13.7.7l3.4,3.4c-1.8,1.3-3.5,2.8-4.6,4.7s-1.8,5.1-1.8,6.2v6.6h.2ZM431.6,265c7.8,2.1,8.7-3.5.2-1.3l-.2,1.3ZM432.4,270.9c.3.6,6.4-.4,5.8-2.3s-4.6.6-5.7.6l-.2,1.7h.1ZM434.5,276c.8,1.2,5.7-1.8,5.5-2.7-.4-1.9-6.6,1.2-5.5,2.7ZM442.9,276.4c-.9-.9-5,2.8-4.6,4,.6,2.4,5.7-3,4.6-4ZM445.1,279.9c-.3.2-3.1,4.6-1.5,5s3.5-3.4,3.5-4-1.3-1.3-2-.9h0ZM448.9,287.4c2.1.8,3.8-5.1,2.3-5.5-1.9-.6-2.6,5.1-2.3,5.5ZM457.3,288.6c.5-1.7,1.1-4.7-1-5.5-1,.3-.6,3.9-.6,4.8l.3.5,1.3.2h0Z"/>
<path class="st0" d="M455.6,276.4c-5-.8-9.1-3.6-10.6-8.5s.5-4,3.5-1.4,8.3,8.7,7.2,10h-.1Z"/>
<path class="st0"
d="M420.6,290.3c-4.9-.6-8.9-3.6-11.8-7.4s-1.5-1.8-1.7-2.7c1-1,8.5,6.6,9.4,6.7,2.4.4-1.8-3.5-2.1-3.8-1-.8-5.4-3.5-5.7-4-.4-.8.5-.5.8-.5,5.2.8,10.1,6.6,10.9,11.6h.2Z"/>
<path class="st0"
d="M400.4,281.5c-1.1,3.7-3,7.3-3,11.4h1.3c.5-3.7,1.5-7.8,3.6-11,1,4.8-1.5,11.6-4.5,15.4s-.4.8-1.2.7c-1.1-4.5-1.3-10.8,1.5-14.8s1.9-2.2,2.3-1.7h0Z"/>
<path class="st0"
d="M411.3,276.8c0-.8,2.1-.4,2.6-.3,2.4.4,4,1.7,6.2,2-1.2-3.1-5.7-3.2-8.4-3.8,0-.8.9-.4,1.4-.4,2.8,0,5.6.7,7.8,2.4,2.2,1.7,4,4,1.2,3.9-4.3,0-7.1-2.4-10.9-3.7h0Z"/>
<path class="st0"
d="M395.3,279.8c-1.1,1.6-3,3-4,4.6s-1.9,2.8-.4,2.5,2.8-4,4.2-4.6-.9,3.6-1.1,4c-.4.7-4.7,5.2-5.4,4.5-.2-4.6,1.8-9.9,6.7-11h0Z"/>
<path class="st0" d="M437,309.7l-1,2.5c-3.6-2.3-8-2.8-12.1-4,.5-.7,1.1-.7,1.9-.7,3.4,0,7.8,1.8,11.2,2.1h0Z"/>
<path class="st0" d="M417.2,308.5c0-1,0-5.8,1.4-5.5,4,3.5,6.1,6.2-1.4,5.5Z"/>
<path class="st0" d="M400.4,276.4c-1.8-.3-3.5.7-5.2.4s-2.3-.8-1.9-1.4c.8-1.6,6.9-1.4,7.2,1h-.1Z"/>
<path class="st0" d="M410.9,270.1c.4,3-3.6,3.3-5.5,4.6-.6-1.8,5-5.1,5.5-4.6Z"/>
<path class="st0" d="M426.9,305.9c-2.5-.2-4.1-1.3-5.5-3.4,1.7-.4,2,.8,3,1.4s2.6.3,2.5,1.9h0Z"/>
<path class="st1" d="M432.4,270.9l.2-1.7c1.1,0,5.1-2.2,5.7-.6s-5.5,2.9-5.8,2.3h-.1Z"/>
<path class="st1" d="M431.6,265l.2-1.3c8.4-2.1,7.7,3.4-.2,1.3Z"/>
<path class="st1" d="M434.5,276c-1.1-1.5,5.1-4.6,5.5-2.7s-4.6,4-5.5,2.7Z"/>
<path class="st1" d="M442.9,276.4c1.1,1.1-4,6.4-4.6,4s3.7-4.9,4.6-4Z"/>
<path class="st1" d="M445.1,279.9c.7-.4,2.1,0,2,.9s-2.4,4.4-3.5,4,1.3-4.8,1.5-5h0Z"/>
<path class="st1" d="M448.9,287.4c-.3-.3.4-6.1,2.3-5.5,1.4.4-.2,6.2-2.3,5.5Z"/>
<path class="st1" d="M457.3,288.6l-1.3-.2-.3-.5c0-.9-.4-4.6.6-4.8,2.1.8,1.5,3.8,1,5.5h0Z"/>
<path class="st0"
d="M420.5,312.8c8.9,0,17.9,4.7,23.4,11.3,5.6,6.6,3.8,3.5,0,6.2-10.7,7.7-26.1,9-38.2,4.2-1.9-.8-10.5-5.1-10.5-6.8s4-5.3,4.8-6.2c5.3-5,13.1-8.6,20.4-8.8h.1Z"/>
<path class="st0" d="M398.7,316.9l-13.9-1.4c1.7-1,5-.8,6.9-.6s5.6.7,7,2.1h0Z"/>
<path class="st0"
d="M456.9,314.8c.7.5,0,.8-.6.8-1.6.2-3.5-.2-5.1,0-2.4.3-5.2,1.2-7.6,1.3s-1.1,0,0-.5,5.1-1.6,6-1.6h7.4,0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

File diff suppressed because one or more lines are too long

14
src/client.tsx Normal file
View File

@@ -0,0 +1,14 @@
/// <reference types="vinxi/types/client" />
import { hydrateRoot } from 'react-dom/client';
import { StartClient } from '@tanstack/react-start';
import { createRouter } from './router';
import reportWebVitals from "@/reportWebVitals.ts";
const router = createRouter();
hydrateRoot(document, <StartClient router={router}/>);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@@ -2,11 +2,14 @@
// 🛑 Nothing in here has anything to do with React Router, it's just a fake database // 🛑 Nothing in here has anything to do with React Router, it's just a fake database
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// noinspection UnnecessaryLocalVariableJS,JSUnusedGlobalSymbols // noinspection UnnecessaryLocalVariableJS,JSUnusedGlobalSymbols
import { matchSorter } from "match-sorter"; import { matchSorter } from "match-sorter";
// @ts-expect-error - no types, but it's a tiny function // @ts-expect-error - no types, but it's a tiny function
import sortBy from "sort-by"; import sortBy from "sort-by";
import invariant from "tiny-invariant"; import invariant from "tiny-invariant";
import { Low } from "lowdb";
import { JSONFile } from "lowdb/node";
import { join } from "node:path";
import { mkdir } from "node:fs/promises";
type ContactMutation = { type ContactMutation = {
id?: string; id?: string;
@@ -23,42 +26,97 @@ export type ContactRecord = ContactMutation & {
createdAt: string; createdAt: string;
}; };
//////////////////////////////////////////////////////////////////////////////// // Define the database schema
// This is just a fake DB table. In a real app you'd be talking to a real db or type Schema = {
// fetching from an existing API. records: Record<string, ContactRecord>;
const fakeContacts = { };
records: {} as Record<string, ContactRecord>,
// Set up lowdb
const initDb = async (): Promise<Low<Schema>> => {
// Ensure the db directory exists
const dbDir = join(process.cwd(), '.db');
await mkdir(dbDir, { recursive: true });
const file = join(dbDir, 'contacts.json');
const adapter = new JSONFile<Schema>(file);
const defaultData: Schema = { records: {} };
const db = new Low<Schema>(adapter, defaultData);
// Load existing data
await db.read();
// Initialize if needed
if (!db.data) {
db.data = defaultData;
}
return db;
};
// Create a singleton instance of the database
let dbPromise: Promise<Low<Schema>> | null = null;
const getDb = () => {
if (!dbPromise) {
dbPromise = initDb();
}
return dbPromise;
};
////////////////////////////////////////////////////////////////////////////////
// This is a DB wrapper using lowdb for persistence
const fakeContacts = {
async getAll(): Promise<ContactRecord[]> { async getAll(): Promise<ContactRecord[]> {
return Object.keys(fakeContacts.records) const db = await getDb();
.map((key) => fakeContacts.records[key]) return Object.keys(db.data.records)
.map((key) => db.data.records[key])
.sort(sortBy("-createdAt", "last")); .sort(sortBy("-createdAt", "last"));
}, },
async get(id: string): Promise<ContactRecord | null> { async get(id: string): Promise<ContactRecord | null> {
return fakeContacts.records[id] || null; const db = await getDb();
return db.data.records[id] || null;
}, },
async create(values: ContactMutation): Promise<ContactRecord> { async create(values: ContactMutation): Promise<ContactRecord> {
const db = await getDb();
const id = values.id || Math.random().toString(36).substring(2, 9); const id = values.id || Math.random().toString(36).substring(2, 9);
const createdAt = new Date().toISOString(); const createdAt = new Date().toISOString();
const newContact = { id, createdAt, ...values }; const newContact = { id, createdAt, ...values };
fakeContacts.records[id] = newContact; db.data.records[id] = newContact;
await db.write();
return newContact; return newContact;
}, },
async set(id: string, values: ContactMutation): Promise<ContactRecord> { async set(id: string, values: ContactMutation): Promise<ContactRecord> {
const contact = await fakeContacts.get(id); const db = await getDb();
const contact = await this.get(id);
invariant(contact, `No contact found for ${id}`); invariant(contact, `No contact found for ${id}`);
const updatedContact = { ...contact, ...values }; const updatedContact = { ...contact, ...values };
fakeContacts.records[id] = updatedContact; db.data.records[id] = updatedContact;
await db.write();
return updatedContact; return updatedContact;
}, },
destroy(id: string): null { async destroy(id: string): Promise<null> {
delete fakeContacts.records[id]; const db = await getDb();
delete db.data.records[id];
await db.write();
return null; return null;
}, },
// New reset function to restore the initial data
async reset(): Promise<void> {
const db = await getDb();
db.data.records = {};
await db.write();
// Restore initial data
const initialContacts = getInitialContacts();
for (const contact of initialContacts) {
await this.create(contact);
}
}
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -93,10 +151,18 @@ export async function updateContact(id: string, updates: ContactMutation) {
} }
export async function deleteContact(id: string) { export async function deleteContact(id: string) {
fakeContacts.destroy(id); await fakeContacts.destroy(id);
} }
[ // New function to reset the database to initial state
export async function resetDatabase() {
console.log("RESET DATABASE");
await fakeContacts.reset();
}
// Helper function to get initial contacts data
function getInitialContacts() {
return [
{ {
avatar: avatar:
"https://sessionize.com/image/124e-400o400o2-wHVdAuNaxi8KJrgtN3ZKci.jpg", "https://sessionize.com/image/124e-400o400o2-wHVdAuNaxi8KJrgtN3ZKci.jpg",
@@ -309,12 +375,19 @@ export async function deleteContact(id: string) {
last: "Jensen", last: "Jensen",
twitter: "@jenseng", twitter: "@jenseng",
}, },
].forEach((contact) => { ].map(contact => ({
fakeContacts.create({
...contact, ...contact,
id: `${contact.first id: `${contact.first
.toLowerCase() .toLowerCase()
.split(" ") .split(" ")
.join("_")}-${contact.last.toLocaleLowerCase()}`, .join("_")}-${contact.last.toLowerCase()}`,
}); }));
}); }
// Initialize with seed data on first load
(async () => {
const db = await getDb();
if (Object.keys(db.data.records).length === 0) {
await resetDatabase();
}
})();

View File

@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1"
xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 841.9 595.3">
<!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 146) -->
<defs>
<style>
.st0 {
fill: #9ae7fc;
}
.st1 {
fill: #61dafb;
}
</style>
</defs>
<g>
<path class="st1" d="M666.3,296.5c0-32.5-40.7-63.3-103.1-82.4,14.4-63.6,8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6,0,8.3.9,11.4,2.6,13.6,7.8,19.5,37.5,14.9,75.7-1.1,9.4-2.9,19.3-5.1,29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50,32.6-30.3,63.2-46.9,84-46.9v-22.3c-27.5,0-63.5,19.6-99.9,53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7,0,51.4,16.5,84,46.6-14,14.7-28,31.4-41.3,49.9-22.6,2.4-44,6.1-63.6,11-2.3-10-4-19.7-5.2-29-4.7-38.2,1.1-67.9,14.6-75.8,3-1.8,6.9-2.6,11.5-2.6v-22.3c-8.4,0-16,1.8-22.6,5.6-28.1,16.2-34.4,66.7-19.9,130.1-62.2,19.2-102.7,49.9-102.7,82.3s40.7,63.3,103.1,82.4c-14.4,63.6-8,114.2,20.2,130.4,6.5,3.8,14.1,5.6,22.5,5.6,27.5,0,63.5-19.6,99.9-53.6,36.4,33.8,72.4,53.2,99.9,53.2,8.4,0,16-1.8,22.6-5.6,28.1-16.2,34.4-66.7,19.9-130.1,62-19.1,102.5-49.9,102.5-82.3zm-130.2-66.7c-3.7,12.9-8.3,26.2-13.5,39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4,14.2,2.1,27.9,4.7,41,7.9zm-45.8,106.5c-7.8,13.5-15.8,26.3-24.1,38.2-14.9,1.3-30,2-45.2,2s-30.2-.7-45-1.9c-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8,6.2-13.4,13.2-26.8,20.7-39.9,7.8-13.5,15.8-26.3,24.1-38.2,14.9-1.3,30-2,45.2-2s30.2.7,45,1.9c8.3,11.9,16.4,24.6,24.2,38,7.6,13.1,14.5,26.4,20.8,39.8-6.3,13.4-13.2,26.8-20.7,39.9zm32.3-13c5.4,13.4,10,26.8,13.8,39.8-13.1,3.2-26.9,5.9-41.2,8,4.9-7.7,9.8-15.6,14.4-23.7,4.6-8,8.9-16.1,13-24.1zm-101.4,106.7c-9.3-9.6-18.6-20.3-27.8-32,9,.4,18.2.7,27.5.7s18.7-.2,27.8-.7c-9,11.7-18.3,22.4-27.5,32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9,3.7-12.9,8.3-26.2,13.5-39.5,4.1,8,8.4,16,13.1,24s9.5,15.8,14.4,23.4zm73.9-208.1c9.3,9.6,18.6,20.3,27.8,32-9-.4-18.2-.7-27.5-.7s-18.7.2-27.8.7c9-11.7,18.3-22.4,27.5-32zm-74,58.9c-4.9,7.7-9.8,15.6-14.4,23.7-4.6,8-8.9,16-13,24-5.4-13.4-10-26.8-13.8-39.8,13.1-3.1,26.9-5.8,41.2-7.9zm-90.5,125.2c-35.4-15.1-58.3-34.9-58.3-50.6s22.9-35.6,58.3-50.6c8.6-3.7,18-7,27.7-10.1,5.7,19.6,13.2,40,22.5,60.9-9.2,20.8-16.6,41.1-22.2,60.6-9.9-3.1-19.3-6.5-28-10.2zm53.8,142.9c-13.6-7.8-19.5-37.5-14.9-75.7,1.1-9.4,2.9-19.3,5.1-29.4,19.6,4.8,41,8.5,63.5,10.9,13.5,18.5,27.5,35.3,41.6,50-32.6,30.3-63.2,46.9-84,46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7,38.2-1.1,67.9-14.6,75.8-3,1.8-6.9,2.6-11.5,2.6-20.7,0-51.4-16.5-84-46.6,14-14.7,28-31.4,41.3-49.9,22.6-2.4,44-6.1,63.6-11,2.3,10.1,4.1,19.8,5.2,29.1zm38.5-66.7c-8.6,3.7-18,7-27.7,10.1-5.7-19.6-13.2-40-22.5-60.9,9.2-20.8,16.6-41.1,22.2-60.6,9.9,3.1,19.3,6.5,28.1,10.2,35.4,15.1,58.3,34.9,58.3,50.6,0,15.7-23,35.6-58.4,50.6zm-264.9-268.7z"/>
<circle class="st1" cx="420.9" cy="296.5" r="45.7"/>
<path class="st1" d="M520.5,78.1"/>
</g>
<circle class="st0" cx="420.8" cy="296.6" r="43"/>
<path class="st1" d="M466.1,296.6c0,25-20.2,45.2-45.2,45.2s-45.2-20.2-45.2-45.2,20.2-45.2,45.2-45.2,45.2,20.2,45.2,45.2ZM386,295.6v-6.3c0-1.1,1.2-5.1,1.8-6.2,1-1.9,2.9-3.5,4.6-4.7l-3.4-3.4c4-3.6,9.4-3.7,13.7-.7,1.9-4.7,6.6-7.1,11.6-6.7l-.8,4.2c5.9.2,13.1,4.1,13.1,10.8s0,.5-.7.7c-1.7.3-3.4-.4-5-.6s-1.2-.4-1.2.3,2.5,4.1,3,5.5,1,3.5.8,5.3c-5.6-.8-10.5-3.2-14.8-6.7.3,2.6,4.1,21.7,5.3,21.9s.8-.6,1-1.1,1.3-6.3,1.3-6.7c0-1-1.7-1.8-2.2-2.8-1.2-2.7,1.3-4.7,3.7-3.3s5.2,6.2,7.5,7.3,13,1.4,14.8,3.3-2.9,4.6-1.5,7.6c6.7-2.6,13.5-3.3,20.6-2.5,3.1-9.7,3.1-20.3-.9-29.8-7.3,0-14.7-3.6-17.2-10.8-2.5-7.2-.7-8.6-1.3-9.3-.8-1-6.3.6-7.4-1.5s.3-1.1-.2-1.4-1.9-.6-2.6-.8c-26-6.4-51.3,15.7-49.7,42.1,0,1.6,1.6,10.3,2.4,11.1s4.8,0,6.3,0,3.7.3,5,.5c2.9.4,7.2,2.4,9.4,2.5s2.4-.8,2.7-2.4c.4-2.6.5-7.4.5-10.1s-1-7.8-1.3-11.6c-.9-.2-.7,0-.9.5-.7,1.3-1.1,3.2-1.9,4.8s-5.2,8.7-5.7,9-.7-.5-.8-.8c-1.6-3.5-2-7.9-1.9-11.8-.9-1-5.4,4.9-6.7,5.3l-.8-.4v-.3h-.2ZM455.6,276.4c1.1-1.2-6-8.9-7.2-10-3-2.7-5.4-4.5-3.5,1.4s5.7,7.8,10.6,8.5h.1ZM410.9,270.1c-.4-.5-6.1,2.9-5.5,4.6,1.9-1.3,5.9-1.7,5.5-4.6ZM400.4,276.4c-.3-2.4-6.3-2.7-7.2-1s1.6,1.4,1.9,1.4c1.8.3,3.5-.6,5.2-.4h.1ZM411.3,276.8c3.8,1.3,6.6,3.6,10.9,3.7s0-3-1.2-3.9c-2.2-1.7-5.1-2.4-7.8-2.4s-1.6-.3-1.4.4c2.8.6,7.3.7,8.4,3.8-2.3-.3-3.9-1.6-6.2-2s-2.5-.5-2.6.3h0ZM420.6,290.3c-.8-5.1-5.7-10.8-10.9-11.6s-1.3-.4-.8.5,4.7,3.2,5.7,4,4.5,4.2,2.1,3.8-8.4-7.8-9.4-6.7c.2.9,1.1,1.9,1.7,2.7,3,3.8,6.9,6.8,11.8,7.4h-.2ZM395.3,279.8c-5,1.1-6.9,6.3-6.7,11,.7.8,5-3.8,5.4-4.5s2.7-4.6,1.1-4-2.9,4.4-4.2,4.6.2-2.1.4-2.5c1.1-1.6,2.9-3.1,4-4.6h0ZM400.4,281.5c-.4-.5-2,1.3-2.3,1.7-2.9,3.9-2.6,10.2-1.5,14.8.8.2.8-.3,1.2-.7,3-3.8,5.5-10.5,4.5-15.4-2.1,3.1-3.1,7.3-3.6,11h-1.3c0-4,1.9-7.7,3-11.4h0ZM426.9,305.9c0-1.7-1.7-1.4-2.5-1.9s-1.3-1.9-3-1.4c1.3,2.1,3,3.2,5.5,3.4h0ZM417.2,308.5c7.6.7,5.5-1.9,1.4-5.5-1.3-.3-1.5,4.5-1.4,5.5ZM437,309.7c-3.5-.3-7.8-2-11.2-2.1s-1.3,0-1.9.7c4,1.3,8.4,1.7,12.1,4l1-2.5h0ZM420.5,312.8c-7.3,0-15.1,3.7-20.4,8.8s-4.8,5.3-4.8,6.2c0,1.8,8.6,6.2,10.5,6.8,12.1,4.8,27.5,3.5,38.2-4.2s3.1-2.7,0-6.2c-5.7-6.6-14.7-11.4-23.4-11.3h-.1ZM398.7,316.9c-1.4-1.4-5-1.9-7-2.1s-5.3-.3-6.9.6l13.9,1.4h0ZM456.9,314.8h-7.4c-.9,0-4.9,1.1-6,1.6s-.8.6,0,.5c2.4,0,5.1-1,7.6-1.3s3.5.2,5.1,0,1.3-.3.6-.8h0Z"/>
<path class="st0" d="M386,295.6l.8.4c1.3-.3,5.8-6.2,6.7-5.3,0,3.9.3,8.3,1.9,11.8s0,1.2.8.8,5.1-7.8,5.7-9,1.3-3.5,1.9-4.8,0-.7.9-.5c.3,3.8,1.2,7.8,1.3,11.6s0,7.5-.5,10.1-1.1,2.4-2.7,2.4-6.5-2.1-9.4-2.5-3.7-.5-5-.5-5.4,1.1-6.3,0-2.2-9.5-2.4-11.1c-1.5-26.4,23.7-48.5,49.7-42.1s2.2.4,2.6.8,0,1,.2,1.4c1.1,2,6.5.5,7.4,1.5s.4,6.9,1.3,9.3c2.5,7.2,10,10.9,17.2,10.8,4,9.4,4,20.1.9,29.8-7.2-.7-13.9,0-20.6,2.5-1.3-3.1,4.1-5.1,1.5-7.6s-11.8-1.9-14.8-3.3-5.4-6.1-7.5-7.3-4.9.6-3.7,3.3,2.1,1.8,2.2,2.8-1,6.2-1.3,6.7-.3,1.3-1,1.1c-1.1-.3-5-19.3-5.3-21.9,4.3,3.5,9.2,5.9,14.8,6.7.2-1.9-.3-3.5-.8-5.3s-3-5.1-3-5.5c0-.8.9-.3,1.2-.3,1.6,0,3.3.8,5,.6s.7.3.7-.7c0-6.6-7.2-10.6-13.1-10.8l.8-4.2c-5.1-.3-9.6,2-11.6,6.7-4.3-3-9.8-3-13.7.7l3.4,3.4c-1.8,1.3-3.5,2.8-4.6,4.7s-1.8,5.1-1.8,6.2v6.6h.2ZM431.6,265c7.8,2.1,8.7-3.5.2-1.3l-.2,1.3ZM432.4,270.9c.3.6,6.4-.4,5.8-2.3s-4.6.6-5.7.6l-.2,1.7h.1ZM434.5,276c.8,1.2,5.7-1.8,5.5-2.7-.4-1.9-6.6,1.2-5.5,2.7ZM442.9,276.4c-.9-.9-5,2.8-4.6,4,.6,2.4,5.7-3,4.6-4ZM445.1,279.9c-.3.2-3.1,4.6-1.5,5s3.5-3.4,3.5-4-1.3-1.3-2-.9h0ZM448.9,287.4c2.1.8,3.8-5.1,2.3-5.5-1.9-.6-2.6,5.1-2.3,5.5ZM457.3,288.6c.5-1.7,1.1-4.7-1-5.5-1,.3-.6,3.9-.6,4.8l.3.5,1.3.2h0Z"/>
<path class="st0" d="M455.6,276.4c-5-.8-9.1-3.6-10.6-8.5s.5-4,3.5-1.4,8.3,8.7,7.2,10h-.1Z"/>
<path class="st0" d="M420.6,290.3c-4.9-.6-8.9-3.6-11.8-7.4s-1.5-1.8-1.7-2.7c1-1,8.5,6.6,9.4,6.7,2.4.4-1.8-3.5-2.1-3.8-1-.8-5.4-3.5-5.7-4-.4-.8.5-.5.8-.5,5.2.8,10.1,6.6,10.9,11.6h.2Z"/>
<path class="st0" d="M400.4,281.5c-1.1,3.7-3,7.3-3,11.4h1.3c.5-3.7,1.5-7.8,3.6-11,1,4.8-1.5,11.6-4.5,15.4s-.4.8-1.2.7c-1.1-4.5-1.3-10.8,1.5-14.8s1.9-2.2,2.3-1.7h0Z"/>
<path class="st0" d="M411.3,276.8c0-.8,2.1-.4,2.6-.3,2.4.4,4,1.7,6.2,2-1.2-3.1-5.7-3.2-8.4-3.8,0-.8.9-.4,1.4-.4,2.8,0,5.6.7,7.8,2.4,2.2,1.7,4,4,1.2,3.9-4.3,0-7.1-2.4-10.9-3.7h0Z"/>
<path class="st0" d="M395.3,279.8c-1.1,1.6-3,3-4,4.6s-1.9,2.8-.4,2.5,2.8-4,4.2-4.6-.9,3.6-1.1,4c-.4.7-4.7,5.2-5.4,4.5-.2-4.6,1.8-9.9,6.7-11h0Z"/>
<path class="st0" d="M437,309.7l-1,2.5c-3.6-2.3-8-2.8-12.1-4,.5-.7,1.1-.7,1.9-.7,3.4,0,7.8,1.8,11.2,2.1h0Z"/>
<path class="st0" d="M417.2,308.5c0-1,0-5.8,1.4-5.5,4,3.5,6.1,6.2-1.4,5.5Z"/>
<path class="st0" d="M400.4,276.4c-1.8-.3-3.5.7-5.2.4s-2.3-.8-1.9-1.4c.8-1.6,6.9-1.4,7.2,1h-.1Z"/>
<path class="st0" d="M410.9,270.1c.4,3-3.6,3.3-5.5,4.6-.6-1.8,5-5.1,5.5-4.6Z"/>
<path class="st0" d="M426.9,305.9c-2.5-.2-4.1-1.3-5.5-3.4,1.7-.4,2,.8,3,1.4s2.6.3,2.5,1.9h0Z"/>
<path class="st1" d="M432.4,270.9l.2-1.7c1.1,0,5.1-2.2,5.7-.6s-5.5,2.9-5.8,2.3h-.1Z"/>
<path class="st1" d="M431.6,265l.2-1.3c8.4-2.1,7.7,3.4-.2,1.3Z"/>
<path class="st1" d="M434.5,276c-1.1-1.5,5.1-4.6,5.5-2.7s-4.6,4-5.5,2.7Z"/>
<path class="st1" d="M442.9,276.4c1.1,1.1-4,6.4-4.6,4s3.7-4.9,4.6-4Z"/>
<path class="st1" d="M445.1,279.9c.7-.4,2.1,0,2,.9s-2.4,4.4-3.5,4,1.3-4.8,1.5-5h0Z"/>
<path class="st1" d="M448.9,287.4c-.3-.3.4-6.1,2.3-5.5,1.4.4-.2,6.2-2.3,5.5Z"/>
<path class="st1" d="M457.3,288.6l-1.3-.2-.3-.5c0-.9-.4-4.6.6-4.8,2.1.8,1.5,3.8,1,5.5h0Z"/>
<path class="st0" d="M420.5,312.8c8.9,0,17.9,4.7,23.4,11.3,5.6,6.6,3.8,3.5,0,6.2-10.7,7.7-26.1,9-38.2,4.2-1.9-.8-10.5-5.1-10.5-6.8s4-5.3,4.8-6.2c5.3-5,13.1-8.6,20.4-8.8h.1Z"/>
<path class="st0" d="M398.7,316.9l-13.9-1.4c1.7-1,5-.8,6.9-.6s5.6.7,7,2.1h0Z"/>
<path class="st0" d="M456.9,314.8c.7.5,0,.8-.6.8-1.6.2-3.5-.2-5.1,0-2.4.3-5.2,1.2-7.6,1.3s-1.1,0,0-.5,5.1-1.6,6-1.6h7.4,0Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -1,41 +0,0 @@
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider, createRouter } from '@tanstack/react-router';
// Import the generated route tree
import { routeTree } from './routeTree.gen';
import reportWebVitals from './reportWebVitals.ts';
// Create a new router instance
const router = createRouter({
routeTree,
context: {},
defaultPreload: 'intent',
scrollRestoration: true,
defaultStructuralSharing: true,
defaultPreloadStaleTime: 0,
});
// Register the router instance for type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
}
// Render the app
const rootElement = document.getElementById('app');
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<StrictMode>
<RouterProvider router={router}/>
</StrictMode>,
);
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@@ -1,13 +1,13 @@
const reportWebVitals = (onPerfEntry?: () => void) => { const reportWebVitals = (onPerfEntry?: () => void) => {
if (onPerfEntry && onPerfEntry instanceof Function) { if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => { import('web-vitals').then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => {
onCLS(onPerfEntry) onCLS(onPerfEntry);
onINP(onPerfEntry) onINP(onPerfEntry);
onFCP(onPerfEntry) onFCP(onPerfEntry);
onLCP(onPerfEntry) onLCP(onPerfEntry);
onTTFB(onPerfEntry) onTTFB(onPerfEntry);
}) });
} }
} };
export default reportWebVitals export default reportWebVitals;

View File

@@ -11,60 +11,159 @@
// Import Routes // Import Routes
import { Route as rootRoute } from './routes/__root' import { Route as rootRoute } from './routes/__root'
import { Route as IndexImport } from './routes/index' import { Route as AboutImport } from './routes/about'
import { Route as SidebarImport } from './routes/_sidebar'
import { Route as SidebarIndexImport } from './routes/_sidebar/index'
import { Route as SidebarContactsImport } from './routes/_sidebar/contacts'
import { Route as SidebarContactsContactIdImport } from './routes/_sidebar/contacts.$contactId'
// Create/Update Routes // Create/Update Routes
const IndexRoute = IndexImport.update({ const AboutRoute = AboutImport.update({
id: '/about',
path: '/about',
getParentRoute: () => rootRoute,
} as any)
const SidebarRoute = SidebarImport.update({
id: '/_sidebar',
getParentRoute: () => rootRoute,
} as any)
const SidebarIndexRoute = SidebarIndexImport.update({
id: '/', id: '/',
path: '/', path: '/',
getParentRoute: () => rootRoute, getParentRoute: () => SidebarRoute,
} as any)
const SidebarContactsRoute = SidebarContactsImport.update({
id: '/contacts',
path: '/contacts',
getParentRoute: () => SidebarRoute,
} as any)
const SidebarContactsContactIdRoute = SidebarContactsContactIdImport.update({
id: '/$contactId',
path: '/$contactId',
getParentRoute: () => SidebarContactsRoute,
} as any) } as any)
// Populate the FileRoutesByPath interface // Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
interface FileRoutesByPath { interface FileRoutesByPath {
'/': { '/_sidebar': {
id: '/' id: '/_sidebar'
path: ''
fullPath: ''
preLoaderRoute: typeof SidebarImport
parentRoute: typeof rootRoute
}
'/about': {
id: '/about'
path: '/about'
fullPath: '/about'
preLoaderRoute: typeof AboutImport
parentRoute: typeof rootRoute
}
'/_sidebar/contacts': {
id: '/_sidebar/contacts'
path: '/contacts'
fullPath: '/contacts'
preLoaderRoute: typeof SidebarContactsImport
parentRoute: typeof SidebarImport
}
'/_sidebar/': {
id: '/_sidebar/'
path: '/' path: '/'
fullPath: '/' fullPath: '/'
preLoaderRoute: typeof IndexImport preLoaderRoute: typeof SidebarIndexImport
parentRoute: typeof rootRoute parentRoute: typeof SidebarImport
}
'/_sidebar/contacts/$contactId': {
id: '/_sidebar/contacts/$contactId'
path: '/$contactId'
fullPath: '/contacts/$contactId'
preLoaderRoute: typeof SidebarContactsContactIdImport
parentRoute: typeof SidebarContactsImport
} }
} }
} }
// Create and export the route tree // Create and export the route tree
interface SidebarContactsRouteChildren {
SidebarContactsContactIdRoute: typeof SidebarContactsContactIdRoute
}
const SidebarContactsRouteChildren: SidebarContactsRouteChildren = {
SidebarContactsContactIdRoute: SidebarContactsContactIdRoute,
}
const SidebarContactsRouteWithChildren = SidebarContactsRoute._addFileChildren(
SidebarContactsRouteChildren,
)
interface SidebarRouteChildren {
SidebarContactsRoute: typeof SidebarContactsRouteWithChildren
SidebarIndexRoute: typeof SidebarIndexRoute
}
const SidebarRouteChildren: SidebarRouteChildren = {
SidebarContactsRoute: SidebarContactsRouteWithChildren,
SidebarIndexRoute: SidebarIndexRoute,
}
const SidebarRouteWithChildren =
SidebarRoute._addFileChildren(SidebarRouteChildren)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '': typeof SidebarRouteWithChildren
'/about': typeof AboutRoute
'/contacts': typeof SidebarContactsRouteWithChildren
'/': typeof SidebarIndexRoute
'/contacts/$contactId': typeof SidebarContactsContactIdRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/about': typeof AboutRoute
'/contacts': typeof SidebarContactsRouteWithChildren
'/': typeof SidebarIndexRoute
'/contacts/$contactId': typeof SidebarContactsContactIdRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRoute __root__: typeof rootRoute
'/': typeof IndexRoute '/_sidebar': typeof SidebarRouteWithChildren
'/about': typeof AboutRoute
'/_sidebar/contacts': typeof SidebarContactsRouteWithChildren
'/_sidebar/': typeof SidebarIndexRoute
'/_sidebar/contacts/$contactId': typeof SidebarContactsContactIdRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' fullPaths: '' | '/about' | '/contacts' | '/' | '/contacts/$contactId'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' to: '/about' | '/contacts' | '/' | '/contacts/$contactId'
id: '__root__' | '/' id:
| '__root__'
| '/_sidebar'
| '/about'
| '/_sidebar/contacts'
| '/_sidebar/'
| '/_sidebar/contacts/$contactId'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute SidebarRoute: typeof SidebarRouteWithChildren
AboutRoute: typeof AboutRoute
} }
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, SidebarRoute: SidebarRouteWithChildren,
AboutRoute: AboutRoute,
} }
export const routeTree = rootRoute export const routeTree = rootRoute
@@ -77,11 +176,34 @@ export const routeTree = rootRoute
"__root__": { "__root__": {
"filePath": "__root.tsx", "filePath": "__root.tsx",
"children": [ "children": [
"/" "/_sidebar",
"/about"
] ]
}, },
"/": { "/_sidebar": {
"filePath": "index.tsx" "filePath": "_sidebar.tsx",
"children": [
"/_sidebar/contacts",
"/_sidebar/"
]
},
"/about": {
"filePath": "about.tsx"
},
"/_sidebar/contacts": {
"filePath": "_sidebar/contacts.tsx",
"parent": "/_sidebar",
"children": [
"/_sidebar/contacts/$contactId"
]
},
"/_sidebar/": {
"filePath": "_sidebar/index.tsx",
"parent": "/_sidebar"
},
"/_sidebar/contacts/$contactId": {
"filePath": "_sidebar/contacts.$contactId.tsx",
"parent": "/_sidebar/contacts"
} }
} }
} }

16
src/router.tsx Normal file
View File

@@ -0,0 +1,16 @@
import { createRouter as createTanStackRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';
export function createRouter() {
return createTanStackRouter({
routeTree,
defaultPreload: 'intent',
scrollRestoration: true,
});
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>;
}
}

View File

@@ -1,21 +1,57 @@
import { Outlet, createRootRoute } from '@tanstack/react-router'; import { Outlet, createRootRoute, HeadContent, Scripts } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'; import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
import appCss from '@/App.css?url';
import type { ReactNode } from "react";
export const Route = createRootRoute({ export const Route = createRootRoute({
component: () => ( head: () => ({
<> meta: [{
<Outlet/> charSet: 'utf-8',
<TanStackRouterDevtools/> }, {
</> name: 'viewport', content: 'width=device-width, initial-scale=1'
), }, {
notFoundComponent: NotFoundComponent, name: 'description', content: 'TanStack/Router Address Book'
}); }],
links: [{
function NotFoundComponent() { rel: 'stylesheet', href: appCss
}, {
rel: 'icon', href: '/favicon.ico'
}, {
rel: 'apple-touch-icon', href: '/logo192.png'
}, {
rel: 'manifest', href: '/manifest.json'
}]
}),
component: RootLayout,
notFoundComponent: () => {
return ( return (
<main id="error-page"> <main id="error-page">
<h1>404</h1> <h1>404</h1>
<p>The requested page could not be found.</p> <p>The requested page could not be found.</p>
</main> </main>
); );
}
});
function RootLayout() {
return (
<RootDocument>
<Outlet/>
<TanStackRouterDevtools/>
</RootDocument>
);
}
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
return (
<html>
<head>
<HeadContent/>
</head>
<body>
{children}
<Scripts/>
</body>
</html>
);
} }

78
src/routes/_sidebar.tsx Normal file
View File

@@ -0,0 +1,78 @@
import { createFileRoute, Link, Outlet } from '@tanstack/react-router';
import { fetchContacts } from "@/utils/contacts.tsx";
export const Route = createFileRoute('/_sidebar')({
component: Sidebar,
loader: async () => fetchContacts(),
notFoundComponent: () => <div>Not Found</div>,
});
function Sidebar() {
const contacts = Route.useLoaderData();
if (contacts === undefined) {
console.log("SIDEBAR ERROR!?");
return (
<>
<div id="sidebar">UNDEFINED LOADER DATA IN SIDEBAR</div>
<div id={'detail'}>
<Outlet/>
</div>
</>
);
}
return (
<>
<div id="sidebar">
<h1>
<Link to="/about">TanStack Start Contacts</Link>
</h1>
<div>
<form id="search-form" role="search">
<input
aria-label="Search contacts"
id="q"
name="q"
placeholder="Search"
type="search"
/>
<div aria-hidden hidden={true} id="search-spinner"/>
</form>
<form method="post">
<button type="submit">New</button>
</form>
</div>
<nav>
{contacts.length ? (
<ul>
{contacts.map((contact) => (
<li key={contact.id}>
<Link to="/contacts/$contactId" params={{ contactId: contact.id }}>
{contact.first || contact.last ? (
<>
{contact.first} {contact.last}
</>
) : (
<i>No Name</i>
)}
{contact.favorite ? (
<span></span>
) : null}
</Link>
</li>
))}
</ul>
) : (
<p>
<i>No contacts</i>
</p>
)}
</nav>
</div>
<div id={'detail'}>
<Outlet/>
</div>
</>
);
}

View File

@@ -0,0 +1,101 @@
import { createFileRoute } from '@tanstack/react-router';
import { type ContactRecord, fetchContact } from "@/utils/contacts.tsx";
export const Route = createFileRoute('/_sidebar/contacts/$contactId')({
loader: async ({ params }) => {
const contact = await fetchContact({ data: params.contactId });
return { contact };
},
component: Contact,
notFoundComponent: () => {
return <p>Post not found!</p>;
},
});
function Contact() {
const { contact } = Route.useLoaderData();
return (
<div id="contact">
<div>
<img
alt={`${contact.first} ${contact.last} avatar`}
key={contact.avatar}
src={contact.avatar}
/>
</div>
<div>
<h1>
{contact.first || contact.last ? (
<>
{contact.first} {contact.last}
</>
) : (
<i>No Name</i>
)}
<Favorite contact={contact}/>
</h1>
{contact.twitter ? (
<p>
<a
href={`https://twitter.com/${contact.twitter}`}
>
{contact.twitter}
</a>
</p>
) : null}
{contact.notes ? <p>{contact.notes}</p> : null}
<div>
<form action="edit">
<button type="submit">Edit</button>
</form>
<form
action="destroy"
method="post"
onSubmit={(event) => {
const response = confirm(
"Please confirm you want to delete this record."
);
if (!response) {
event.preventDefault();
}
}}
>
<button type="submit">Delete</button>
</form>
</div>
</div>
</div>
);
}
function Favorite(
{
contact,
}: {
contact: Pick<ContactRecord, "favorite">;
}
) {
const favorite = contact.favorite;
return (
<form method="post">
<button
aria-label={
favorite
? "Remove from favorites"
: "Add to favorites"
}
name="favorite"
value={favorite ? "false" : "true"}
>
{favorite ? "★" : "☆"}
</button>
</form>
);
}

View File

@@ -0,0 +1,19 @@
import { createFileRoute, Outlet } from '@tanstack/react-router';
export const Route = createFileRoute('/_sidebar/contacts')({
component: RouteComponent,
notFoundComponent: NotFoundContact,
});
function RouteComponent() {
return <Outlet/>;
}
function NotFoundContact() {
return (
<main id="error-page">
<h1>404</h1>
<p>The requested page could not be found.</p>
</main>
);
}

View File

@@ -0,0 +1,19 @@
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_sidebar/')({
component: App,
});
function App() {
return (
<p id="index-page">
This is a demo for TanStack/Start and Router.
<br/>
Check out{" "}
<a href="https://tanstack.com/">
the docs at tanstack.com
</a>
.
</p>
);
}

49
src/routes/about.tsx Normal file
View File

@@ -0,0 +1,49 @@
import { createFileRoute, Link } from '@tanstack/react-router';
export const Route = createFileRoute('/about')({
component: RouteComponent,
});
function RouteComponent() {
return (
<div id="about">
<Link to="/"> Go to demo</Link>
<h1>About React Router Contacts</h1>
<div>
<p>
This is a demo application showing off some of the
powerful features of React Router, including
dynamic routing, nested routes, loaders, actions,
and more.
</p>
<h2>Features</h2>
<p>
Explore the demo to see how React Router handles:
</p>
<ul>
<li>
Data loading and mutations with loaders and
actions
</li>
<li>
Nested routing with parent/child relationships
</li>
<li>URL-based routing with dynamic segments</li>
<li>Pending and optimistic UI</li>
</ul>
<h2>Learn More</h2>
<p>
Check out the official documentation at{" "}
<a href="https://reactrouter.com">
reactrouter.com
</a>{" "}
to learn more about building great web
applications with React Router.
</p>
</div>
</div>
);
}

View File

@@ -1,41 +0,0 @@
import { createFileRoute } from '@tanstack/react-router';
import '../App.css';
export const Route = createFileRoute('/')({
component: App,
});
function App() {
return (
<>
<div id="sidebar">
<h1>React Router Contacts</h1>
<div>
<form id="search-form" role="search">
<input
aria-label="Search contacts"
id="q"
name="q"
placeholder="Search"
type="search"
/>
<div aria-hidden hidden={true} id="search-spinner"/>
</form>
<form method="post">
<button type="submit">New</button>
</form>
</div>
<nav>
<ul>
<li>
<a href={`/contacts/1`}>Your Name</a>
</li>
<li>
<a href={`/contacts/2`}>Your Friend</a>
</li>
</ul>
</nav>
</div>
</>
);
}

13
src/ssr.tsx Normal file
View File

@@ -0,0 +1,13 @@
/// <reference types="vinxi/types/server" />
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server';
import { getRouterManifest } from '@tanstack/react-start/router-manifest';
import { createRouter } from './router';
export default createStartHandler({
createRouter,
getRouterManifest,
})(defaultStreamHandler);

20
src/utils/contacts.tsx Normal file
View File

@@ -0,0 +1,20 @@
import { createServerFn } from "@tanstack/react-start";
import { getContact, getContacts } from "@/data.ts";
import { notFound } from "@tanstack/react-router";
export type { ContactRecord } from "@/data.ts";
export const fetchContacts = createServerFn({ method: 'GET' })
.handler(async () => {
console.log("Fetch Contacts");
return await getContacts();
});
export const fetchContact = createServerFn({ method: 'GET' })
.validator((data: string) => data)
.handler(async (ctx) => {
console.log(`Fetch Contact ${ctx.data}`);
const contact = await getContact(ctx.data);
if (contact === null) throw notFound();
return contact;
});

View File

@@ -1,21 +0,0 @@
// noinspection JSUnresolvedReference
import { defineConfig } from "vite";
import viteReact from "@vitejs/plugin-react";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
import { resolve } from "node:path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [TanStackRouterVite({ autoCodeSplitting: true }), viteReact()],
test: {
globals: true,
environment: "jsdom",
},
resolve: {
alias: {
// eslint-disable-next-line no-undef
'@': resolve(__dirname, './src'),
},
}
});