feat(guides): integrate GuideContext and persist new guides to IndexedDB
- Wrap application root with IDBProvider and GuideProvider
- Replace mock data in App component with real guides from useGuides hook
- Update useNewGuide hook to save compiled guides to IndexedDB and update global state
This commit is contained in:
parent
f44b5a9133
commit
7cdeb98161
6 changed files with 150 additions and 9 deletions
|
|
@ -2,13 +2,10 @@ import "./styles/reset.css";
|
|||
import "./styles/App.css";
|
||||
import { Outlet } from "react-router";
|
||||
import { Link } from "react-router";
|
||||
import { useGuides } from "./lib/contexts/guide.context";
|
||||
|
||||
function App() {
|
||||
const guides = [
|
||||
{ id: 1, title: "Guide 1" },
|
||||
{ id: 2, title: "Guide 2" },
|
||||
{ id: 3, title: "Guide 3" },
|
||||
];
|
||||
const { guides } = useGuides();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -18,7 +15,6 @@ function App() {
|
|||
</h1>
|
||||
<nav>
|
||||
<Link to="/new-guide">Create New Guide</Link>
|
||||
|
||||
<ul>
|
||||
{guides.map((guide) => (
|
||||
<li key={guide.id}>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,13 @@ import {
|
|||
type FrameType,
|
||||
type GuideType,
|
||||
} from "../../../lib/schemas";
|
||||
import { useIDB } from "../../../lib/contexts/dbinit.context";
|
||||
import { useGuides } from "../../../lib/contexts/guide.context";
|
||||
|
||||
export default function useNewGuide() {
|
||||
const { db } = useIDB();
|
||||
const { setGuides } = useGuides();
|
||||
|
||||
const [guide, setGuide] = useState<GuideType>({
|
||||
id: crypto.randomUUID(),
|
||||
title: "",
|
||||
|
|
@ -107,7 +112,7 @@ export default function useNewGuide() {
|
|||
);
|
||||
};
|
||||
|
||||
const handleSaveGuide = (e: React.SubmitEvent<HTMLFormElement>) => {
|
||||
const handleSaveGuide = async (e: React.SubmitEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
const compiledGuide: CompiledGuideType = {
|
||||
|
|
@ -120,7 +125,19 @@ export default function useNewGuide() {
|
|||
frames,
|
||||
};
|
||||
|
||||
console.log(compiledGuide);
|
||||
if (!db) return;
|
||||
await db.put("guides", compiledGuide);
|
||||
setGuide({
|
||||
id: crypto.randomUUID(),
|
||||
title: "",
|
||||
description: "",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: undefined,
|
||||
});
|
||||
setFrames([]);
|
||||
|
||||
setGuides((prev) => [compiledGuide, ...prev]);
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
55
src/lib/contexts/dbinit.context.tsx
Normal file
55
src/lib/contexts/dbinit.context.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// IDBContext.tsx
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { initDB, type AppDB } from "../db";
|
||||
|
||||
interface IDBContextValue {
|
||||
db: AppDB | null;
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
const IDBContext = createContext<IDBContextValue | undefined>(undefined);
|
||||
|
||||
export function IDBProvider({ children }: { children: ReactNode }) {
|
||||
const [db, setDb] = useState<AppDB | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
initDB()
|
||||
.then((instance) => {
|
||||
if (!cancelled) setDb(instance);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!cancelled)
|
||||
setError(err instanceof Error ? err : new Error(String(err)));
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IDBContext.Provider value={{ db, loading, error }}>
|
||||
{children}
|
||||
</IDBContext.Provider>
|
||||
);
|
||||
}
|
||||
/* eslint-disable-next-line react-refresh/only-export-components */
|
||||
export function useIDB() {
|
||||
const ctx = useContext(IDBContext);
|
||||
if (!ctx) throw new Error("useIDB must be used within IDBProvider");
|
||||
return ctx;
|
||||
}
|
||||
48
src/lib/contexts/guide.context.tsx
Normal file
48
src/lib/contexts/guide.context.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// GuideContext.tsx
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import type { CompiledGuideType } from "../schemas";
|
||||
import { useIDB } from "./dbinit.context";
|
||||
|
||||
interface GuideContextValue {
|
||||
guides: CompiledGuideType[];
|
||||
setGuides: React.Dispatch<React.SetStateAction<CompiledGuideType[]>>;
|
||||
}
|
||||
|
||||
const GuideContext = createContext<GuideContextValue | undefined>(undefined);
|
||||
|
||||
export function GuideProvider({ children }: { children: ReactNode }) {
|
||||
const [guides, setGuides] = useState<CompiledGuideType[]>([]);
|
||||
const { db } = useIDB();
|
||||
|
||||
useEffect(() => {
|
||||
if (!db) return;
|
||||
db.getAll("guides").then((fetchedGuides) => {
|
||||
const sortedGuides = [...fetchedGuides].sort((a, b) => {
|
||||
return (
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
);
|
||||
});
|
||||
setGuides(sortedGuides);
|
||||
});
|
||||
}, [db]);
|
||||
|
||||
return (
|
||||
<GuideContext.Provider value={{ guides, setGuides }}>
|
||||
{children}
|
||||
</GuideContext.Provider>
|
||||
);
|
||||
}
|
||||
/* eslint-disable-next-line react-refresh/only-export-components */
|
||||
export function useGuides() {
|
||||
const ctx = useContext(GuideContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useGuides must be used within a GuideProvider");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
19
src/lib/db.ts
Normal file
19
src/lib/db.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { openDB, type DBSchema, type IDBPDatabase } from "idb";
|
||||
import type { CompiledGuideType } from "./schemas";
|
||||
|
||||
export interface MyDB extends DBSchema {
|
||||
guides: {
|
||||
key: string;
|
||||
value: CompiledGuideType;
|
||||
};
|
||||
}
|
||||
|
||||
export type AppDB = IDBPDatabase<MyDB>;
|
||||
|
||||
export function initDB(): Promise<AppDB> {
|
||||
return openDB<MyDB>("my-guides", 1, {
|
||||
upgrade(db) {
|
||||
db.createObjectStore("guides", { keyPath: "id" });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -3,9 +3,15 @@ import { createRoot } from "react-dom/client";
|
|||
import { RouterProvider } from "react-router";
|
||||
import { router } from "./router.tsx";
|
||||
import "./styles/index.css";
|
||||
import { IDBProvider } from "./lib/contexts/dbinit.context.tsx";
|
||||
import { GuideProvider } from "./lib/contexts/guide.context.tsx"; // Fix import name
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
<IDBProvider>
|
||||
<GuideProvider>
|
||||
<RouterProvider router={router} />
|
||||
</GuideProvider>
|
||||
</IDBProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue