diff --git a/web/src/components/ReviewStats.tsx b/web/src/components/ReviewStats.tsx index 1f3546f..d3b9c15 100644 --- a/web/src/components/ReviewStats.tsx +++ b/web/src/components/ReviewStats.tsx @@ -1,80 +1,51 @@ import { fadeIn } from "$lib/animations"; -import { api } from "$lib/api"; -import type { ReviewCard, StudyStats } from "$lib/model"; +import type { StudyStats } from "$lib/model"; import { Skeleton } from "$ui/Skeleton"; import { type Component, Show } from "solid-js"; import { Motion } from "solid-motionone"; type ReviewStatsProps = { stats: StudyStats | null; loading?: boolean }; -export const ReviewStats: Component = (props) => { - return ( - - = (props) => ( + + + + + + + }> + No stats available

}> + {stats => (
- - - -
- }> - No stats available

}> - {stats => ( -
-
-

Study Progress

- {/* TODO: fire icon */} - 🔥 {stats().current_streak} day streak -
+
+

Study Progress

+ {/* TODO: fire icon */} + 🔥 {stats().current_streak} day streak +
-
-
-

{stats().due_count}

-

Due Today

-
-
-

{stats().reviewed_today}

-

Reviewed

-
-
-

{stats().total_reviews}

-

Total

-
+
+
+

{stats().due_count}

+

Due Today

+
+
+

{stats().reviewed_today}

+

Reviewed

+
+
+

{stats().total_reviews}

+

Total

- 0}> -

Longest streak: {stats().longest_streak} days

-
- )} - + 0}> +

Longest streak: {stats().longest_streak} days

+
+
+ )} - - ); -}; - -// TODO: move this to api.ts -export async function fetchStudyStats(): Promise { - try { - const response = await api.getStats(); - if (response.ok) { - return response.json(); - } - } catch (err) { - console.error("Failed to fetch stats:", err); - } - return null; -} - -// TODO: move this to api.ts -export async function fetchDueCards(deckId?: string): Promise { - try { - const response = await api.getDueCards(deckId); - if (response.ok) { - return response.json(); - } - } catch (err) { - console.error("Failed to fetch due cards:", err); - } - return []; -} + + +); diff --git a/web/src/components/social/CommentSection.tsx b/web/src/components/social/CommentSection.tsx index 79964c7..cf3f063 100644 --- a/web/src/components/social/CommentSection.tsx +++ b/web/src/components/social/CommentSection.tsx @@ -1,18 +1,9 @@ import { api } from "$lib/api"; +import type { Comment } from "$lib/model"; import { authStore } from "$lib/store"; import { Button } from "$ui/Button"; import { createResource, createSignal, For, Show } from "solid-js"; -// TODO: move to model.ts -type Comment = { - id: string; - deck_id: string; - author_did: string; - content: string; - parent_id: string | null; - created_at: string; -}; - type CommentNode = { comment: Comment; children: CommentNode[] }; type CommentSectionProps = { deckId: string }; diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 3e8e5f8..a18378b 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -1,3 +1,4 @@ +import type { CreateDeckPayload } from "./model"; import { authStore } from "./store"; const API_BASE = "/api"; @@ -32,17 +33,42 @@ export const api = { if (deckId) params.set("deck_id", deckId); return apiFetch(`/review/due?${params}`, { method: "GET" }); }, - submitReview: (cardId: string, grade: number) => - apiFetch("/review/submit", { method: "POST", body: JSON.stringify({ card_id: cardId, grade }) }), + submitReview: (cardId: string, grade: number) => { + return apiFetch("/review/submit", { method: "POST", body: JSON.stringify({ card_id: cardId, grade }) }); + }, getStats: () => apiFetch("/review/stats", { method: "GET" }), follow: (did: string) => apiFetch(`/social/follow/${did}`, { method: "POST" }), unfollow: (did: string) => apiFetch(`/social/unfollow/${did}`, { method: "POST" }), getFollowers: (did: string) => apiFetch(`/social/followers/${did}`, { method: "GET" }), getFollowing: (did: string) => apiFetch(`/social/following/${did}`, { method: "GET" }), - addComment: (deckId: string, content: string, parentId?: string) => - apiFetch(`/decks/${deckId}/comments`, { method: "POST", body: JSON.stringify({ content, parent_id: parentId }) }), + addComment: (deckId: string, content: string, parentId?: string) => { + return apiFetch(`/decks/${deckId}/comments`, { + method: "POST", + body: JSON.stringify({ content, parent_id: parentId }), + }); + }, getComments: (deckId: string) => apiFetch(`/decks/${deckId}/comments`, { method: "GET" }), getFeedFollows: () => apiFetch("/feeds/follows", { method: "GET" }), getFeedTrending: () => apiFetch("/feeds/trending", { method: "GET" }), forkDeck: (deckId: string) => apiFetch(`/decks/${deckId}/fork`, { method: "POST" }), + getDecks: () => apiFetch("/decks", { method: "GET" }), + getDeck: (id: string) => apiFetch(`/decks/${id}`, { method: "GET" }), + getDeckCards: (id: string) => apiFetch(`/decks/${id}/cards`, { method: "GET" }), + createDeck: async (payload: CreateDeckPayload) => { + const { cards, ...deckPayload } = payload; + const res = await apiFetch("/decks", { method: "POST", body: JSON.stringify(deckPayload) }); + if (!res.ok) return res; + + const deck = await res.json(); + if (cards && cards.length > 0) { + await Promise.all(cards.map((c) => + apiFetch("/cards", { + method: "POST", + body: JSON.stringify({ deck_id: deck.id, front: c.front, back: c.back, media_url: c.mediaUrl }), + }) + )); + } + + return { ok: true, json: async () => deck }; + }, }; diff --git a/web/src/lib/model.ts b/web/src/lib/model.ts index 9d36c2c..f80e89b 100644 --- a/web/src/lib/model.ts +++ b/web/src/lib/model.ts @@ -58,3 +58,14 @@ export type StudyStats = { }; export type ReviewResponse = { ease_factor: number; interval_days: number; repetitions: number; due_at: string }; + +export type Comment = { + id: string; + deck_id: string; + author_did: string; + content: string; + parent_id: string | null; + created_at: string; +}; + +export type CommentNode = { comment: Comment; children: CommentNode[] }; diff --git a/web/src/pages/DeckNew.tsx b/web/src/pages/DeckNew.tsx index 5d864c5..f3c91bb 100644 --- a/web/src/pages/DeckNew.tsx +++ b/web/src/pages/DeckNew.tsx @@ -1,6 +1,6 @@ import { DeckEditor } from "$components/DeckEditor"; import { api } from "$lib/api"; -import type { Card, CreateDeckPayload } from "$lib/model"; +import type { CreateDeckPayload } from "$lib/model"; import { toast } from "$lib/toast"; import { useNavigate } from "@solidjs/router"; import type { Component } from "solid-js"; @@ -9,22 +9,10 @@ const DeckNew: Component = () => { const navigate = useNavigate(); const handleSave = async (data: CreateDeckPayload) => { - // TODO: some of this can be in api.ts try { - const { cards, ...deckPayload } = data; - const res = await api.post("/decks", deckPayload); - + const res = await api.createDeck(data); if (res.ok) { const deck = await res.json(); - - if (cards && cards.length > 0) { - await Promise.all( - cards.map((c: Card) => - api.post("/cards", { deck_id: deck.id, front: c.front, back: c.back, media_url: c.mediaUrl }) - ), - ); - } - toast.success("Deck created successfully"); navigate(`/decks/${deck.id}`); } else { diff --git a/web/src/pages/DeckView.tsx b/web/src/pages/DeckView.tsx index 52df762..02d06e4 100644 --- a/web/src/pages/DeckView.tsx +++ b/web/src/pages/DeckView.tsx @@ -7,24 +7,16 @@ import { A, useParams } from "@solidjs/router"; import type { Component } from "solid-js"; import { createResource, For, Show } from "solid-js"; -// TODO: use api.ts -const fetchDeck = async (id: string): Promise => { - const res = await api.get(`/decks/${id}`); - if (!res.ok) return null; - return res.json(); -}; - -// TODO: use api.ts -const fetchCards = async (id: string): Promise => { - const res = await api.get(`/decks/${id}/cards`); - if (!res.ok) return []; - return res.json(); -}; - const DeckView: Component = () => { const params = useParams(); - const [deck] = createResource(() => params.id, fetchDeck); - const [cards] = createResource(() => params.id, fetchCards); + const [deck] = createResource(() => params.id, async (id) => { + const res = await api.getDeck(id); + return res.ok ? (await res.json() as Deck) : null; + }); + const [cards] = createResource(() => params.id, async (id) => { + const res = await api.getDeckCards(id); + return res.ok ? (await res.json() as Card[]) : []; + }); const handleFork = async () => { if (!deck()) return; diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index bc655de..f38f161 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -4,13 +4,6 @@ import { A } from "@solidjs/router"; import type { Component } from "solid-js"; import { createResource, For, Show } from "solid-js"; -// TODO: use api.ts -const fetchDecks = async (): Promise => { - const res = await api.get("/decks"); - if (!res.ok) return []; - return res.json(); -}; - const DeckCard: Component<{ deck: Deck }> = (props) => (
@@ -42,7 +35,10 @@ const DeckCard: Component<{ deck: Deck }> = (props) => ( ); const Home: Component = () => { - const [decks] = createResource(fetchDecks); + const [decks] = createResource(async () => { + const res = await api.getDecks(); + return res.ok ? (await res.json() as Deck[]) : []; + }); return (
diff --git a/web/src/pages/Review.tsx b/web/src/pages/Review.tsx index f7f52b4..e700866 100644 --- a/web/src/pages/Review.tsx +++ b/web/src/pages/Review.tsx @@ -1,6 +1,7 @@ -import { fetchDueCards, fetchStudyStats, ReviewStats } from "$components/ReviewStats"; +import { ReviewStats } from "$components/ReviewStats"; import { StudySession } from "$components/StudySession"; import { fadeIn } from "$lib/animations"; +import { api } from "$lib/api"; import type { ReviewCard, StudyStats as StudyStatsType } from "$lib/model"; import { Button } from "$ui/Button"; import { Skeleton } from "$ui/Skeleton"; @@ -19,9 +20,9 @@ const Review: Component = () => { const [sessionComplete, setSessionComplete] = createSignal(false); onMount(async () => { - const [statsData, cardsData] = await Promise.all([fetchStudyStats(), fetchDueCards(params.deckId)]); - setStats(statsData); - setCards(cardsData); + const [statsRes, cardsRes] = await Promise.all([api.getStats(), api.getDueCards(params.deckId)]); + if (statsRes.ok) setStats(await statsRes.json()); + if (cardsRes.ok) setCards(await cardsRes.json()); setLoading(false); }); @@ -35,8 +36,8 @@ const Review: Component = () => { const handleComplete = async () => { setSessionActive(false); setSessionComplete(true); - const newStats = await fetchStudyStats(); - setStats(newStats); + const res = await api.getStats(); + if (res.ok) setStats(await res.json()); }; const handleExit = () => {