Skip to content

Commit

Permalink
feat: Add toasts (#149)
Browse files Browse the repository at this point in the history
  • Loading branch information
evadecker authored Oct 25, 2024
1 parent e77c33b commit 106b38d
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-badgers-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"namesake": patch
---

Display confirmation toasts on success actions throughout the app
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"react-helmet-async": "^2.0.5",
"react-markdown": "^9.0.1",
"resend": "^4.0.0",
"sonner": "^1.5.0",
"storybook": "^8.3.6",
"tailwind-variants": "^0.2.1",
"tailwindcss": "^3.4.14",
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

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

32 changes: 32 additions & 0 deletions src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import type { Role } from "@convex/constants";
import {
RiAlertLine,
RiCheckLine,
RiInformationLine,
RiLoader4Line,
RiSpam2Line,
} from "@remixicon/react";
import {
type NavigateOptions,
Outlet,
Expand All @@ -7,8 +14,10 @@ import {
useRouter,
} from "@tanstack/react-router";
import type { ConvexAuthState } from "convex/react";
import { useTheme } from "next-themes";
import React, { Suspense } from "react";
import { RouterProvider } from "react-aria-components";
import { Toaster } from "sonner";

declare module "react-aria-components" {
interface RouterConfig {
Expand All @@ -29,6 +38,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({

function RootRoute() {
const router = useRouter();
const { theme } = useTheme();

const TanStackRouterDevtools =
process.env.NODE_ENV === "production"
Expand Down Expand Up @@ -57,6 +67,28 @@ function RootRoute() {
<Suspense>
<TanStackRouterDevtools />
</Suspense>
<Toaster
theme={theme as "light" | "dark" | "system"}
offset={16}
gap={8}
toastOptions={{
unstyled: true,
classNames: {
toast:
"bg-gray-12 dark:bg-graydark-12 rounded-lg p-4 w-full font-sans text-sm shadow-md flex items-center gap-2 text-gray-1 dark:text-graydark-1",
title: "text-gray-1 dark:text-graydark-1",
description: "text-gray-3 dark:text-graydark-3",
icon: "text-gray-5 dark:text-graydark-5",
},
}}
icons={{
success: <RiCheckLine />,
info: <RiInformationLine />,
warning: <RiAlertLine />,
error: <RiSpam2Line />,
loading: <RiLoader4Line />,
}}
/>
</RouterProvider>
);
}
23 changes: 18 additions & 5 deletions src/routes/_authenticated/admin/quests/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import { api } from "@convex/_generated/api";
import type { DataModel } from "@convex/_generated/dataModel";
import { ICONS, JURISDICTIONS, type Jurisdiction } from "@convex/constants";
import { RiAddLine, RiMoreFill, RiSignpostLine } from "@remixicon/react";
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useMutation, useQuery } from "convex/react";
import { useState } from "react";
import { toast } from "sonner";

export const Route = createFileRoute("/_authenticated/admin/quests/")({
component: QuestsRoute,
Expand All @@ -43,19 +44,31 @@ const NewQuestModal = ({
const [icon, setIcon] = useState("");
const [title, setTitle] = useState("");
const [jurisdiction, setJurisdiction] = useState<Jurisdiction | null>(null);
const navigate = useNavigate();

const clearForm = () => {
setIcon("");
setTitle("");
setJurisdiction(null);
};

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
createQuest({ title, icon, jurisdiction: jurisdiction ?? undefined });
const questId = await createQuest({
title,
icon,
jurisdiction: jurisdiction ?? undefined,
});

clearForm();
onSubmit();
if (questId) {
toast(`Created quest: ${title}`);
clearForm();
onSubmit();
navigate({
to: "/admin/quests/$questId",
params: { questId },
});
}
};

return (
Expand Down
37 changes: 25 additions & 12 deletions src/routes/_authenticated/quests/$questId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ICONS } from "@convex/constants";
import { RiMoreFill, RiSignpostLine } from "@remixicon/react";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useMutation, useQuery } from "convex/react";
import { toast } from "sonner";

export const Route = createFileRoute("/_authenticated/quests/$questId")({
component: QuestDetailRoute,
Expand All @@ -38,15 +39,21 @@ function QuestDetailRoute() {
const markIncomplete = useMutation(api.userQuests.markIncomplete);
const removeQuest = useMutation(api.userQuests.removeQuest);

const handleMarkComplete = (questId: Id<"quests">) =>
markComplete({ questId });
const handleMarkIncomplete = (questId: Id<"quests">) =>
markIncomplete({ questId });
const handleRemoveQuest = (questId: Id<"quests">) => {
// TODO: Add confirmation toast
removeQuest({ questId });
// Redirect to quests
navigate({ to: "/quests" });
const handleMarkComplete = (questId: Id<"quests">, title: string) => {
markComplete({ questId }).then(() => {
toast(`Marked ${title} complete`);
});
};
const handleMarkIncomplete = (questId: Id<"quests">, title: string) => {
markIncomplete({ questId }).then(() => {
toast(`Marked ${title} as in progress`);
});
};
const handleRemoveQuest = (questId: Id<"quests">, title: string) => {
removeQuest({ questId }).then(() => {
toast(`Removed ${title} quest`);
navigate({ to: "/quests" });
});
};

// TODO: Improve loading state to prevent flash of empty
Expand Down Expand Up @@ -80,17 +87,23 @@ function QuestDetailRoute() {
/>
<Menu placement="bottom end">
{!userQuest.completionTime && (
<MenuItem onAction={() => handleMarkComplete(quest._id)}>
<MenuItem
onAction={() => handleMarkComplete(quest._id, quest.title)}
>
Mark complete
</MenuItem>
)}
{userQuest.completionTime && (
<MenuItem onAction={() => handleMarkIncomplete(quest._id)}>
<MenuItem
onAction={() => handleMarkIncomplete(quest._id, quest.title)}
>
Mark as in progress
</MenuItem>
)}
<MenuSeparator />
<MenuItem onAction={() => handleRemoveQuest(quest._id)}>
<MenuItem
onAction={() => handleRemoveQuest(quest._id, quest.title)}
>
Remove quest
</MenuItem>
</Menu>
Expand Down
17 changes: 12 additions & 5 deletions src/routes/_authenticated/quests/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from "convex/react";
import { useEffect, useState } from "react";
import type { Selection } from "react-aria-components";
import { toast } from "sonner";
import { twMerge } from "tailwind-merge";

export const Route = createFileRoute("/_authenticated/quests")({
Expand Down Expand Up @@ -59,12 +60,15 @@ const NewQuestModal = ({
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

for (const questId of selectedQuests) {
addQuest({ questId: questId as Id<"quests"> });
}
const promises = Array.from(selectedQuests).map((questId) =>
addQuest({ questId: questId as Id<"quests"> }),
);

clearForm();
onSubmit();
Promise.all(promises).then(() => {
toast(`Added ${promises.length} quest${promises.length > 1 ? "s" : ""}`);
clearForm();
onSubmit();
});
};

return (
Expand Down Expand Up @@ -130,6 +134,9 @@ function IndexRoute() {
const [isNewQuestModalOpen, setIsNewQuestModalOpen] = useState(false);

const toggleShowCompleted = () => {
toast(
showCompleted ? "Hiding completed quests" : "Showing completed quests",
);
setShowCompleted(!showCompleted);
};

Expand Down

0 comments on commit 106b38d

Please sign in to comment.