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 "./styles/App.css";
|
||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
|
import { useGuides } from "./lib/contexts/guide.context";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const guides = [
|
const { guides } = useGuides();
|
||||||
{ id: 1, title: "Guide 1" },
|
|
||||||
{ id: 2, title: "Guide 2" },
|
|
||||||
{ id: 3, title: "Guide 3" },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -18,7 +15,6 @@ function App() {
|
||||||
</h1>
|
</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<Link to="/new-guide">Create New Guide</Link>
|
<Link to="/new-guide">Create New Guide</Link>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{guides.map((guide) => (
|
{guides.map((guide) => (
|
||||||
<li key={guide.id}>
|
<li key={guide.id}>
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,13 @@ import {
|
||||||
type FrameType,
|
type FrameType,
|
||||||
type GuideType,
|
type GuideType,
|
||||||
} from "../../../lib/schemas";
|
} from "../../../lib/schemas";
|
||||||
|
import { useIDB } from "../../../lib/contexts/dbinit.context";
|
||||||
|
import { useGuides } from "../../../lib/contexts/guide.context";
|
||||||
|
|
||||||
export default function useNewGuide() {
|
export default function useNewGuide() {
|
||||||
|
const { db } = useIDB();
|
||||||
|
const { setGuides } = useGuides();
|
||||||
|
|
||||||
const [guide, setGuide] = useState<GuideType>({
|
const [guide, setGuide] = useState<GuideType>({
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
title: "",
|
title: "",
|
||||||
|
|
@ -107,7 +112,7 @@ export default function useNewGuide() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveGuide = (e: React.SubmitEvent<HTMLFormElement>) => {
|
const handleSaveGuide = async (e: React.SubmitEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const compiledGuide: CompiledGuideType = {
|
const compiledGuide: CompiledGuideType = {
|
||||||
|
|
@ -120,7 +125,19 @@ export default function useNewGuide() {
|
||||||
frames,
|
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 {
|
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 { RouterProvider } from "react-router";
|
||||||
import { router } from "./router.tsx";
|
import { router } from "./router.tsx";
|
||||||
import "./styles/index.css";
|
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(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<RouterProvider router={router} />
|
<IDBProvider>
|
||||||
|
<GuideProvider>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</GuideProvider>
|
||||||
|
</IDBProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue