Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
src/data/tutorial/*.mdx
src/data/deep-dives/*.mdx
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@shikijs/rehype": "^3.20.0",
"@shikijs/transformers": "^3.20.0",
"@tailwindcss/vite": "^4.0.6",
"@tanstack/db": "^0.5.16",
"@tanstack/query-db-collection": "^1.0.12",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/collections/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export const projectsCollection = createCollection(
// TODO: handle error
console.error("Failed to update todo item:", error);
}
// Re-throw to trigger immediate rollback of optimistic update
throw error;
}
},
getKey: (item) => item.id,
Expand Down
6 changes: 4 additions & 2 deletions src/collections/todoItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const todoItemsCollection = createCollection<TodoItemRecord>(
parsed.filters.forEach(({ field, operator, value }) => {
const fieldName = field.join(".");

// Currently only "eq" operator is supported in the API
// Currently only the "eq" operator is supported by our API
if (operator === "eq") {
params.set(fieldName, String(value));
}
Expand All @@ -90,6 +90,7 @@ export const todoItemsCollection = createCollection<TodoItemRecord>(
} catch (error) {
toast.error(`Failed to insert todo item "${newTodoItem.title}"`);
console.error("Failed to insert todo item:", error);
throw error;
}
},
onUpdate: async ({ transaction }) => {
Expand Down Expand Up @@ -133,7 +134,7 @@ export const todoItemsCollection = createCollection<TodoItemRecord>(
);
},
);
} catch (_) {
} catch (error) {
toast.error(`Failed to update todo item "${original.title}"`);

// TODO: handle this one later properly
Expand All @@ -143,6 +144,7 @@ export const todoItemsCollection = createCollection<TodoItemRecord>(
// // Sync back the server's data
// todoItemsCollection.utils.refetch();
// }
throw error;
}

// Do not sync back the server's data by default
Expand Down
43 changes: 23 additions & 20 deletions src/components/ApiPanelToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { eq, useLiveQuery } from "@tanstack/react-db";
import { ActivityIcon } from "lucide-react";
import { userPreferencesCollection } from "@/collections/UserPreferences";
import { HighlightWrapper } from "@/utils/highlight-collection-related-info";
import { USER_PLACEHOLDER } from "@/utils/USER_PLACEHOLDER_CONSTANT";
import { Button } from "./ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
Expand All @@ -20,25 +21,27 @@ export function ApiPanelToggle() {
const isOpen = userPreferences?.networkPanel === "open";

return (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={isOpen ? "default" : "outline"}
size="icon"
onClick={() =>
userPreferencesCollection.update(USER_PLACEHOLDER.id, (draft) => {
draft.networkPanel =
draft.networkPanel === "open" ? "closed" : "open";
})
}
aria-label={isOpen ? "Hide API requests" : "Show API requests"}
>
<ActivityIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{isOpen ? "Hide API requests" : "Show API requests"}</p>
</TooltipContent>
</Tooltip>
<HighlightWrapper highlightId="networkPanel_toggle">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={isOpen ? "default" : "outline"}
size="icon"
onClick={() =>
userPreferencesCollection.update(USER_PLACEHOLDER.id, (draft) => {
draft.networkPanel =
draft.networkPanel === "open" ? "closed" : "open";
})
}
aria-label={isOpen ? "Hide API requests" : "Show API requests"}
>
<ActivityIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{isOpen ? "Hide API requests" : "Show API requests"}</p>
</TooltipContent>
</Tooltip>
</HighlightWrapper>
);
}
101 changes: 52 additions & 49 deletions src/components/ApiRequestsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "@/collections/apiRequests";
import { userPreferencesCollection } from "@/collections/UserPreferences";
import { cn } from "@/lib/utils";
import { HighlightWrapper } from "@/utils/highlight-collection-related-info";
import { USER_PLACEHOLDER } from "@/utils/USER_PLACEHOLDER_CONSTANT";
import { Badge } from "./ui/badge";
import { Button } from "./ui/button";
Expand Down Expand Up @@ -196,58 +197,60 @@ export function ApiRequestsPanel() {
className="flex flex-col h-full max-h-full overflow-hidden bg-background"
style={{ width: API_PANEL_WIDTH }}
>
<motion.div
className="flex flex-col h-full overflow-hidden"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
duration: PANEL_ANIMATION_DURATION,
delay: PANEL_ANIMATION_DURATION,
}}
>
{/* Header */}
<div className="shrink-0 flex items-center justify-between px-3 py-2 border-b bg-muted/30">
<div className="flex items-center gap-2">
<h2 className="text-sm font-medium">API Requests</h2>
<Badge
variant="secondary"
className="text-[10px] px-1.5 py-0"
<HighlightWrapper highlightId="networkPanel_panel">
<motion.div
className="flex flex-col h-full overflow-hidden"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
duration: PANEL_ANIMATION_DURATION,
delay: PANEL_ANIMATION_DURATION,
}}
>
{/* Header */}
<div className="shrink-0 flex items-center justify-between px-3 py-2 border-b bg-muted/30">
<div className="flex items-center gap-2">
<h2 className="text-sm font-medium">API Requests</h2>
<Badge
variant="secondary"
className="text-[10px] px-1.5 py-0"
>
{requests.length}
</Badge>
</div>
<Button
variant="ghost"
size="icon"
onClick={clearRequests}
disabled={requests.length === 0}
>
{requests.length}
</Badge>
<Trash2Icon />
<span className="sr-only">Clear requests</span>
</Button>
</div>
<Button
variant="ghost"
size="icon"
onClick={clearRequests}
disabled={requests.length === 0}
>
<Trash2Icon />
<span className="sr-only">Clear requests</span>
</Button>
</div>

{/* Request list */}
<div className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden">
{requests.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full p-4 text-center">
<p className="text-sm text-muted-foreground">
No requests yet
</p>
<p className="text-xs text-muted-foreground mt-1">
API requests will appear here
</p>
</div>
) : (
<div>
{requests.map((request) => (
<RequestItem key={request.id} request={request} />
))}
</div>
)}
</div>
</motion.div>
{/* Request list */}
<div className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden">
{requests.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full p-4 text-center">
<p className="text-sm text-muted-foreground">
No requests yet
</p>
<p className="text-xs text-muted-foreground mt-1">
API requests will appear here
</p>
</div>
) : (
<div>
{requests.map((request) => (
<RequestItem key={request.id} request={request} />
))}
</div>
)}
</div>
</motion.div>
</HighlightWrapper>
</div>
</motion.div>
)}
Expand Down
18 changes: 15 additions & 3 deletions src/components/tutorial/TutorialWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ export function TutorialWindow({
const [isClosed, setIsClosed] = useState(tutorialData.isClosed);

const { article: activeArticleFromSearch } = useSearch({ strict: false });
const navigate = useNavigate();

const [activeStep, setActiveStep] = useState(
tutorialData.tutorialStep || articles[0].title,
Expand All @@ -498,13 +499,24 @@ export function TutorialWindow({
) {
const articleInSearch = decodeURIComponent(
activeArticleFromSearch.toLowerCase(),
).trim();

const article = articles.find(
(a) => a.title.toLowerCase().trim() === articleInSearch,
);

if (articles.find((a) => a.title === articleInSearch)) {
setActiveStep(articleInSearch);
if (article) {
setActiveStep(article.title);
navigate({
to: ".",
search: ({ article: _, ...old }) => {
return { ...old };
},
replace: true,
});
}
}
}, [activeArticleFromSearch]);
}, [activeArticleFromSearch, navigate]);

const router = useRouter();

Expand Down
65 changes: 64 additions & 1 deletion src/components/tutorial/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Link } from "@tanstack/react-router";
import { GithubIcon, SearchIcon } from "lucide-react";
import { type ReactNode, useMemo } from "react";
import { type ReactNode, useCallback, useMemo } from "react";
import z from "zod";
import { userPreferencesCollection } from "@/collections/UserPreferences";
import { USER_PLACEHOLDER } from "@/utils/USER_PLACEHOLDER_CONSTANT";
import { Button } from "../ui/button";

/**
Expand All @@ -17,6 +19,8 @@ export const highlightParamSchema = z.object({
"board",
"editProject",
"apiLatencyConfigurator",
"networkPanel_toggle",
"networkPanel_panel",
])
.optional(),
});
Expand Down Expand Up @@ -91,6 +95,38 @@ export function HighLightComponent({
);
}

export function HighlightLink({
h_id: newHighLightGroupId,
children,
}: {
h_id: string;
children: ReactNode;
}) {
const highlight = useMemo(() => {
try {
return highlightParamSchema.parse({
highlight: newHighLightGroupId,
}).highlight;
} catch (e) {
return undefined;
}
}, [newHighLightGroupId]);

return (
<Link
className="inline"
to="."
replace={true}
search={(s) => ({
...s,
highlight,
})}
>
{children}
</Link>
);
}

export function LinkToArticle({
children,
articleTitle,
Expand All @@ -115,3 +151,30 @@ export function LinkToArticle({
</Link>
);
}

export function OpenAPIRequestsPanelLink({
children,
}: {
children: ReactNode;
}) {
const openThePanel = useCallback(() => {
if (typeof window !== "undefined") {
userPreferencesCollection.update(USER_PLACEHOLDER.id, (draft) => {
draft.networkPanel = "open";
});
}
}, []);

return (
<Link
to="."
onClick={openThePanel}
search={{
// highlight the toggle button of the network panel
highlight: "networkPanel_panel",
}}
>
{children}
</Link>
);
}
Loading