[nvim] envrc to flake, setup tanstack query for data loading
This commit is contained in:
2
.envrc
2
.envrc
@@ -1,4 +1,4 @@
|
|||||||
use nix
|
use flake
|
||||||
|
|
||||||
# Add Node modules only to the end of the path.
|
# Add Node modules only to the end of the path.
|
||||||
# `layout node` will add it to the front, which is not as secure
|
# `layout node` will add it to the front, which is not as secure
|
||||||
|
|||||||
44
package-lock.json
generated
44
package-lock.json
generated
@@ -8,7 +8,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.4",
|
"@headlessui/react": "^2.2.4",
|
||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.0.6",
|
||||||
"@tanstack/react-query": "^5.77.2",
|
"@tanstack/react-query": "^5.79.0",
|
||||||
|
"@tanstack/react-query-devtools": "^5.79.0",
|
||||||
"@tanstack/react-router": "^1.114.3",
|
"@tanstack/react-router": "^1.114.3",
|
||||||
"@tanstack/react-router-devtools": "^1.114.3",
|
"@tanstack/react-router-devtools": "^1.114.3",
|
||||||
"@tanstack/router-plugin": "^1.114.3",
|
"@tanstack/router-plugin": "^1.114.3",
|
||||||
@@ -1659,9 +1660,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/query-core": {
|
"node_modules/@tanstack/query-core": {
|
||||||
"version": "5.77.2",
|
"version": "5.79.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.77.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.79.0.tgz",
|
||||||
"integrity": "sha512-1lqJwPsR6GX6nZFw06erRt518O19tWU6Q+x0fJUygl4lxHCYF2nhzBPwLKk2NPjYOrpR0K567hxPc5K++xDe9Q==",
|
"integrity": "sha512-s+epTqqLM0/TbJzMAK7OEhZIzh63P9sWz5HEFc5XHL4FvKQXQkcjI8F3nee+H/xVVn7mrP610nVXwOytTSYd0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tanstack/query-devtools": {
|
||||||
|
"version": "5.76.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.76.0.tgz",
|
||||||
|
"integrity": "sha512-1p92nqOBPYVqVDU0Ua5nzHenC6EGZNrLnB2OZphYw8CNA1exuvI97FVgIKON7Uug3uQqvH/QY8suUKpQo8qHNQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -1669,12 +1680,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/react-query": {
|
"node_modules/@tanstack/react-query": {
|
||||||
"version": "5.77.2",
|
"version": "5.79.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.77.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.79.0.tgz",
|
||||||
"integrity": "sha512-BRHxWdy1mHmgAcYA/qy2IPLylT81oebLgkm9K85viN2Qol/Vq48t1dzDFeDIVQjTWDV96AmqsLNPlH5HjyKCxA==",
|
"integrity": "sha512-DjC4JIYZnYzxaTzbg3osOU63VNLP67dOrWet2cZvXgmgwAXNxfS52AMq86M5++ILuzW+BqTUEVMTjhrZ7/XBuA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/query-core": "5.77.2"
|
"@tanstack/query-core": "5.79.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -1684,6 +1695,23 @@
|
|||||||
"react": "^18 || ^19"
|
"react": "^18 || ^19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/react-query-devtools": {
|
||||||
|
"version": "5.79.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.79.0.tgz",
|
||||||
|
"integrity": "sha512-YVRWxjxsWycWChjKxvaIAPdNC5LX0zpiHoNyTB8teDZpQstM1b7mCuAp3x60cjX1MhLoO3vbaeY29EKst4D4ug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/query-devtools": "5.76.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tanstack/react-query": "^5.79.0",
|
||||||
|
"react": "^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/react-router": {
|
"node_modules/@tanstack/react-router": {
|
||||||
"version": "1.120.10",
|
"version": "1.120.10",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.120.10.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.120.10.tgz",
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.4",
|
"@headlessui/react": "^2.2.4",
|
||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.0.6",
|
||||||
"@tanstack/react-query": "^5.77.2",
|
"@tanstack/react-query": "^5.79.0",
|
||||||
|
"@tanstack/react-query-devtools": "^5.79.0",
|
||||||
"@tanstack/react-router": "^1.114.3",
|
"@tanstack/react-router": "^1.114.3",
|
||||||
"@tanstack/react-router-devtools": "^1.114.3",
|
"@tanstack/react-router-devtools": "^1.114.3",
|
||||||
"@tanstack/router-plugin": "^1.114.3",
|
"@tanstack/router-plugin": "^1.114.3",
|
||||||
|
|||||||
35
src/main.tsx
35
src/main.tsx
@@ -1,42 +1,47 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from "react";
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from "react-dom/client";
|
||||||
import { RouterProvider, createRouter } from '@tanstack/react-router'
|
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
|
||||||
// Import the generated route tree
|
// Import the generated route tree
|
||||||
import { routeTree } from './routeTree.gen'
|
import { routeTree } from "./routeTree.gen";
|
||||||
|
|
||||||
import './styles.css'
|
import "./styles.css";
|
||||||
import reportWebVitals from './reportWebVitals.ts'
|
import reportWebVitals from "./reportWebVitals.ts";
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
// Create a new router instance
|
// Create a new router instance
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
routeTree,
|
routeTree,
|
||||||
context: {},
|
context: { queryClient },
|
||||||
defaultPreload: 'intent',
|
defaultPreload: "intent",
|
||||||
scrollRestoration: true,
|
scrollRestoration: true,
|
||||||
defaultStructuralSharing: true,
|
defaultStructuralSharing: true,
|
||||||
defaultPreloadStaleTime: 0,
|
defaultPreloadStaleTime: 0,
|
||||||
})
|
});
|
||||||
|
|
||||||
// Register the router instance for type safety
|
// Register the router instance for type safety
|
||||||
declare module '@tanstack/react-router' {
|
declare module "@tanstack/react-router" {
|
||||||
interface Register {
|
interface Register {
|
||||||
router: typeof router
|
router: typeof router;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the app
|
// Render the app
|
||||||
const rootElement = document.getElementById('app')
|
const rootElement = document.getElementById("app");
|
||||||
if (rootElement && !rootElement.innerHTML) {
|
if (rootElement && !rootElement.innerHTML) {
|
||||||
const root = ReactDOM.createRoot(rootElement)
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
root.render(
|
root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
|
</QueryClientProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
reportWebVitals()
|
reportWebVitals();
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { Link, Outlet, createRootRoute } from "@tanstack/react-router";
|
import { Link, Outlet, createRootRoute } from "@tanstack/react-router";
|
||||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||||
import { AuthProvider, useAuth } from "@/context/auth/AuthContext";
|
import { AuthProvider, useAuth } from "@/context/auth/AuthContext";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root header with navigation and user authentication controls.
|
* Root header with navigation and user authentication controls.
|
||||||
@@ -69,12 +67,13 @@ function RootHeader() {
|
|||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
component: () => (
|
component: () => (
|
||||||
<QueryClientProvider client={queryClient}>
|
<>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<RootHeader />
|
<RootHeader />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<TanStackRouterDevtools />
|
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</QueryClientProvider>
|
<TanStackRouterDevtools />
|
||||||
|
<ReactQueryDevtools buttonPosition="bottom-right" />
|
||||||
|
</>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,28 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
import { pb } from "@/lib/pocketbase";
|
import { pb } from "@/lib/pocketbase";
|
||||||
import type { Campaign } from "@/lib/types";
|
|
||||||
import { SessionRow } from "@/components/documents/session/SessionRow";
|
import { SessionRow } from "@/components/documents/session/SessionRow";
|
||||||
|
import { Button } from "@headlessui/react";
|
||||||
|
import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
import { Loader } from "@/components/Loader";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticated/campaigns/$campaignId")({
|
export const Route = createFileRoute("/_authenticated/campaigns/$campaignId")({
|
||||||
loader: async ({ params }) => {
|
component: RouteComponent,
|
||||||
const record = await pb.collection("campaigns").getOne(params.campaignId);
|
pendingComponent: Loader,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const params = Route.useParams();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { campaign, sessions },
|
||||||
|
} = useSuspenseQuery({
|
||||||
|
queryKey: ["campaign"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const campaign = await pb
|
||||||
|
.collection("campaigns")
|
||||||
|
.getOne(params.campaignId);
|
||||||
// Fetch all documents for this campaign
|
// Fetch all documents for this campaign
|
||||||
const docs = await pb.collection("documents").getFullList({
|
const docs = await pb.collection("documents").getFullList({
|
||||||
filter: `campaign = "${params.campaignId}"`,
|
filter: `campaign = "${params.campaignId}"`,
|
||||||
@@ -13,15 +30,24 @@ export const Route = createFileRoute("/_authenticated/campaigns/$campaignId")({
|
|||||||
// Filter to only those with data.session
|
// Filter to only those with data.session
|
||||||
const sessions = docs.filter((doc: any) => doc.data && doc.data.session);
|
const sessions = docs.filter((doc: any) => doc.data && doc.data.session);
|
||||||
return {
|
return {
|
||||||
campaign: { id: record.id, name: record.name } as Campaign,
|
campaign,
|
||||||
sessions,
|
sessions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
component: RouteComponent,
|
});
|
||||||
});
|
|
||||||
|
const createNewSession = useCallback(async () => {
|
||||||
|
await pb.collection("documents").create({
|
||||||
|
campaign: campaign.id,
|
||||||
|
data: {
|
||||||
|
session: {
|
||||||
|
strongStart: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["campaign"] });
|
||||||
|
}, [campaign]);
|
||||||
|
|
||||||
function RouteComponent() {
|
|
||||||
const { campaign, sessions } = Route.useLoaderData();
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-xl mx-auto py-8">
|
<div className="max-w-xl mx-auto py-8">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
@@ -35,7 +61,17 @@ function RouteComponent() {
|
|||||||
<h2 className="text-2xl font-bold mb-4 text-slate-100">
|
<h2 className="text-2xl font-bold mb-4 text-slate-100">
|
||||||
{campaign.name}
|
{campaign.name}
|
||||||
</h2>
|
</h2>
|
||||||
|
<div className="flex justify-between">
|
||||||
<h3 className="text-lg font-semibold mb-2 text-slate-200">Sessions</h3>
|
<h3 className="text-lg font-semibold mb-2 text-slate-200">Sessions</h3>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={() => createNewSession()}
|
||||||
|
className="inline-flex items-center justify-center rounded bg-violet-600 hover:bg-violet-700 text-white px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-violet-400"
|
||||||
|
>
|
||||||
|
New Session
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{sessions && sessions.length > 0 ? (
|
{sessions && sessions.length > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
@@ -51,7 +87,6 @@ function RouteComponent() {
|
|||||||
No sessions found for this campaign.
|
No sessions found for this campaign.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* More campaign details can go here */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import { useRouter } from "@tanstack/react-router";
|
|||||||
export const Route = createFileRoute("/_authenticated/campaigns/")({
|
export const Route = createFileRoute("/_authenticated/campaigns/")({
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
const records = await pb.collection("campaigns").getFullList();
|
const records = await pb.collection("campaigns").getFullList();
|
||||||
return { campaigns: records.map((rec: any) => ({ id: rec.id, name: rec.name })) as Campaign[] };
|
return {
|
||||||
|
campaigns: records.map((rec: any) => ({
|
||||||
|
id: rec.id,
|
||||||
|
name: rec.name,
|
||||||
|
})) as Campaign[],
|
||||||
|
};
|
||||||
},
|
},
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
pendingComponent: Loader,
|
pendingComponent: Loader,
|
||||||
@@ -23,11 +28,12 @@ function RouteComponent() {
|
|||||||
await router.invalidate();
|
await router.invalidate();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!campaigns || campaigns.length === 0) return <div>No campaigns found.</div>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-xl mx-auto py-8">
|
<div className="max-w-xl mx-auto py-8">
|
||||||
<h2 className="text-xl font-bold mb-4 text-slate-100">Your Campaigns</h2>
|
<h2 className="text-xl font-bold mb-4 text-slate-100">Your Campaigns</h2>
|
||||||
|
{!campaigns || campaigns.length === 0 ? (
|
||||||
|
<div>No campaigns found.</div>
|
||||||
|
) : (
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{campaigns.map((c) => (
|
{campaigns.map((c) => (
|
||||||
<li key={c.id}>
|
<li key={c.id}>
|
||||||
@@ -41,6 +47,7 @@ function RouteComponent() {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
)}
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<CreateCampaignButton onCreated={handleCreated} />
|
<CreateCampaignButton onCreated={handleCreated} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user