├── vite.config.ts.timestamp-1719693692326-f548269587e3a.mjs ├── .gitignore ├── env.d.ts ├── public └── favicon.ico ├── postcss.config.js ├── app ├── styles │ └── tailwind.css ├── routes │ ├── db.tsx │ ├── db.$name.tsx │ ├── db.$name._index.tsx │ ├── db.$name.docs.tsx │ ├── db._index.tsx │ ├── db.$name.history.tsx │ ├── _index.tsx │ ├── db.$name.create.tsx │ └── db.$name.doc.$id.tsx ├── entry.client.tsx ├── root.tsx └── components │ ├── FireproofMenu.tsx │ └── Sidebar.tsx ├── tailwind.config.js ├── vite.config.ts ├── tsconfig.json ├── package.json ├── README.md └── .eslintrc.cjs /vite.config.ts.timestamp-1719693692326-f548269587e3a.mjs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | /public/build 6 | .env 7 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/dashboard-remix/main/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; -------------------------------------------------------------------------------- /app/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer utilities { 6 | a:hover { 7 | @apply underline; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./app/**/*.{js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | }; -------------------------------------------------------------------------------- /app/routes/db.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import { FireproofMenu } from "~/components/FireproofMenu"; 3 | 4 | export default function DbIndex() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { vitePlugin as remix } from "@remix-run/dev"; 2 | import { defineConfig } from "vite"; 3 | import tsconfigPaths from "vite-tsconfig-paths"; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | remix({ 8 | ssr: false, 9 | future: { 10 | v3_fetcherPersist: true, 11 | v3_relativeSplatPath: true, 12 | v3_throwAbortReason: true, 13 | }, 14 | }), 15 | tsconfigPaths(), 16 | ], 17 | }); 18 | -------------------------------------------------------------------------------- /app/routes/db.$name.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import { Sidebar } from "~/components/Sidebar"; 3 | 4 | export default function Database() { 5 | return ( 6 |
7 |
{/* Fixed width of 16rem (64 units) */} 8 | 9 |
10 |
{/* Flex-grow to take remaining space */} 11 | 12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * By default, Remix will handle hydrating your app on the client for you. 3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ 4 | * For more information, see https://remix.run/file-conventions/entry.client 5 | */ 6 | 7 | import { RemixBrowser } from "@remix-run/react"; 8 | import { startTransition, StrictMode } from "react"; 9 | import { hydrateRoot } from "react-dom/client"; 10 | 11 | startTransition(() => { 12 | hydrateRoot( 13 | document, 14 | 15 | 16 | 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["env.d.ts", "**/*.ts", "**/*.tsx"], 3 | "compilerOptions": { 4 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 5 | "isolatedModules": true, 6 | "esModuleInterop": true, 7 | "jsx": "react-jsx", 8 | "module": "ESNext", 9 | "moduleResolution": "Bundler", 10 | "resolveJsonModule": true, 11 | "target": "ES2022", 12 | "strict": true, 13 | "allowJs": true, 14 | "skipLibCheck": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "~/*": ["./app/*"] 19 | }, 20 | 21 | // Remix takes care of building everything in `remix build`. 22 | "noEmit": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/routes/db.$name._index.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "@remix-run/react"; 2 | import { useFireproof } from "use-fireproof"; 3 | 4 | 5 | export default function DbInfo() { 6 | const { name } = useParams(); 7 | const { useLiveQuery, database } = useFireproof(name); 8 | const allDocs = useLiveQuery('_id') 9 | const head = database._crdt.clock.head.map(cid => cid.toString()) 10 | 11 | return ( 12 |
13 |

14 | Info: {name} 15 |

16 |

There are {allDocs.docs.length} documents

17 |

Head:

18 |
19 |         {JSON.stringify(head, null, 2)}
20 |       
21 |
22 | ); 23 | } -------------------------------------------------------------------------------- /app/root.tsx: -------------------------------------------------------------------------------- 1 | import "./styles/tailwind.css"; 2 | import { 3 | Links, 4 | Meta, 5 | Outlet, 6 | Scripts, 7 | ScrollRestoration 8 | } from "@remix-run/react"; 9 | 10 | export function Layout({ children }: { children: React.ReactNode }) { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {children} 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | 28 | export default function App() { 29 | return 30 | } 31 | 32 | export function HydrateFallback() { 33 | return

Loading...

; 34 | } -------------------------------------------------------------------------------- /app/routes/db.$name.docs.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "@remix-run/react"; 2 | import { Link } from "react-router-dom"; 3 | import { useFireproof } from "use-fireproof"; 4 | 5 | 6 | export default function AddDocuments() { 7 | const { name } = useParams(); 8 | const { useLiveQuery } = useFireproof(name); 9 | const allDocs = useLiveQuery('_id') 10 | 11 | return ( 12 |
13 |

All Documents

14 |

15 | These are all the documents in the {name} database. 16 |

17 |
    18 | {allDocs.docs.map(({ _id }) => ( 19 |
  • 20 | 21 | {_id} 22 | 23 |
  • 24 | ))} 25 |
26 |
27 | ); 28 | } -------------------------------------------------------------------------------- /app/components/FireproofMenu.tsx: -------------------------------------------------------------------------------- 1 | export function FireproofMenu() { 2 | return ( 3 |
4 | 5 | Fireproof Logo 10 | 11 | 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /app/routes/db._index.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | const databases = ["db1", "db2", "db3"]; // Replace with your actual database list 4 | 5 | import { Sidebar } from "~/components/Sidebar"; 6 | 7 | export default function DatabaseIndex() { 8 | return ( 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | ); 18 | } 19 | 20 | function DatabaseList() { 21 | return ( 22 |
23 |

Available Databases

24 |

This is the index page for the /db route.

25 |
    26 | {databases.map((db) => ( 27 |
  • 28 | 29 | {db} 30 | 31 |
  • 32 | ))} 33 |
34 |
35 | ); 36 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard", 3 | "private": true, 4 | "sideEffects": false, 5 | "type": "module", 6 | "scripts": { 7 | "build": "remix vite:build", 8 | "dev": "remix vite:dev", 9 | "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", 10 | "preview": "vite preview", 11 | "typecheck": "tsc" 12 | }, 13 | "dependencies": { 14 | "@remix-run/node": "^2.10.0", 15 | "@remix-run/react": "^2.10.0", 16 | "react": "^18.3.1", 17 | "react-dom": "^18.3.1", 18 | "use-fireproof": "^0.18.0" 19 | }, 20 | "devDependencies": { 21 | "@remix-run/dev": "^2.10.0", 22 | "@types/react": "^18.3.3", 23 | "@types/react-dom": "^18.3.0", 24 | "@typescript-eslint/eslint-plugin": "^7.14.1", 25 | "@typescript-eslint/parser": "^7.14.1", 26 | "autoprefixer": "^10.4.19", 27 | "eslint": "^8.5.0", 28 | "eslint-import-resolver-typescript": "^3.6.1", 29 | "eslint-plugin-import": "^2.29.1", 30 | "eslint-plugin-jsx-a11y": "^6.9.0", 31 | "eslint-plugin-react": "^7.34.3", 32 | "eslint-plugin-react-hooks": "^4.6.2", 33 | "postcss": "^8.4.39", 34 | "tailwindcss": "^3.4.4", 35 | "typescript": "^5.5.2", 36 | "vite": "^5.3.2", 37 | "vite-tsconfig-paths": "^4.3.2" 38 | }, 39 | "engines": { 40 | "node": ">=20.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/routes/db.$name.history.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { useParams } from "@remix-run/react"; 3 | import { Link } from "react-router-dom"; 4 | import { DocBase, useFireproof } from "use-fireproof"; 5 | 6 | 7 | export default function AddDocuments() { 8 | const { name } = useParams(); 9 | const { database } = useFireproof(name); 10 | 11 | const [history, setHistory] = useState({ rows: [] } as { rows: { key: string; value: DocBase }[] }); 12 | 13 | useEffect(() => { 14 | const handleChanges = async () => { 15 | const changes = await database.changes() 16 | setHistory(changes); 17 | }; 18 | 19 | void handleChanges() 20 | return database.subscribe(handleChanges); 21 | }, [database]); 22 | 23 | return ( 24 |
25 |

26 | Recent Changes in {name} Database 27 |

28 |

These are the recent changes in the database.

29 |
    30 | {history.rows.map(({ key }) => ( 31 |
  • 32 | 33 | {key} 34 | 35 |
  • 36 | ))} 37 |
38 |
39 | ); 40 | } -------------------------------------------------------------------------------- /app/routes/_index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | export default function Index() { 5 | const [dbNames, setDbNames] = useState([]); 6 | const [newDbName, setNewDbName] = useState(""); 7 | 8 | const handleAddDbName = (e: React.FormEvent) => { 9 | e.preventDefault(); 10 | if (newDbName && !dbNames.includes(newDbName)) { 11 | setDbNames([...dbNames, newDbName]); 12 | setNewDbName(""); 13 | } 14 | }; 15 | 16 | return ( 17 |
18 |

Database List

19 |
20 | setNewDbName(e.target.value)} 24 | placeholder="Enter database name" 25 | className="border p-2 mr-2" 26 | /> 27 | 30 |
31 |
    32 | {dbNames.map((name) => ( 33 |
  • 34 | 35 | {name} 36 | 37 |
  • 38 | ))} 39 |
40 |
41 | ); 42 | } -------------------------------------------------------------------------------- /app/routes/db.$name.create.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "@remix-run/react"; 2 | import { useState } from "react"; 3 | import { useFireproof } from "use-fireproof"; 4 | 5 | export default function CreateDocument() { 6 | const { name } = useParams(); 7 | const { database } = useFireproof(name); 8 | const [jsonInput, setJsonInput] = useState(""); 9 | const [error, setError] = useState(""); 10 | 11 | const handleSave = () => { 12 | try { 13 | const parsedJson = JSON.parse(jsonInput); 14 | database.put(parsedJson); 15 | setError(""); 16 | setJsonInput(""); 17 | } catch (e) { 18 | setError("Invalid JSON"); 19 | } 20 | }; 21 | 22 | return ( 23 |
24 |

Create Document

25 |

26 | Enter the JSON for the new document in the {name} database. 27 |

28 |