From 30dfaa17af61b6ef084f2b678b263c194f03180b Mon Sep 17 00:00:00 2001 From: jude Date: Sun, 5 Nov 2023 13:45:04 +0000 Subject: [PATCH] Message flash provider Allows errors to be displayed neatly in ephemeral boxes at bottom of screen --- src/api.ts | 20 +++++++++---- src/components/App/FlashContext.tsx | 6 ++++ src/components/App/FlashProvider.tsx | 30 +++++++++++++++++++ src/components/App/index.tsx | 18 +++++------ src/components/Guild/GuildError.tsx | 25 ++++++++++++++++ .../index.tsx => Guild/GuildReminders.tsx} | 7 +++-- src/components/Guild/index.tsx | 18 +++++++++++ .../Reminder/ButtonRow/CreateButtonRow.tsx | 22 +++++++++----- .../Reminder/ButtonRow/EditButtonRow.tsx | 8 ++++- src/components/Reminder/EditReminder.tsx | 2 +- src/components/Reminder/IntervalSelector.tsx | 6 ++-- 11 files changed, 131 insertions(+), 31 deletions(-) create mode 100644 src/components/App/FlashContext.tsx create mode 100644 src/components/App/FlashProvider.tsx create mode 100644 src/components/Guild/GuildError.tsx rename src/components/{GuildReminders/index.tsx => Guild/GuildReminders.tsx} (91%) create mode 100644 src/components/Guild/index.tsx diff --git a/src/api.ts b/src/api.ts index 20b5fda..bd6b990 100644 --- a/src/api.ts +++ b/src/api.ts @@ -9,8 +9,9 @@ type UserInfo = { }; export type GuildInfo = { - id: string; + patreon: boolean; name: string; + error?: string; }; type EmbedField = { @@ -95,6 +96,13 @@ export const fetchUserGuilds = () => ({ staleTime: USER_INFO_STALE_TIME, }); +export const fetchGuildInfo = (guild: string) => ({ + queryKey: ["GUILD_INFO", guild], + queryFn: () => + axios.get(`/dashboard/api/guild/${guild}`).then((resp) => resp.data) as Promise, + staleTime: GUILD_INFO_STALE_TIME, +}); + export const fetchGuildChannels = (guild: string) => ({ queryKey: ["GUILD_CHANNELS", guild], queryFn: () => @@ -142,10 +150,12 @@ export const patchGuildReminder = (guild: string) => ({ export const postGuildReminder = (guild: string) => ({ mutationFn: (reminder: Reminder) => - axios.post(`/dashboard/api/guild/${guild}/reminders`, { - ...reminder, - utc_time: reminder.utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss"), - }), + axios + .post(`/dashboard/api/guild/${guild}/reminders`, { + ...reminder, + utc_time: reminder.utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss"), + }) + .then((resp) => resp.data), }); export const deleteGuildReminder = (guild: string) => ({ diff --git a/src/components/App/FlashContext.tsx b/src/components/App/FlashContext.tsx new file mode 100644 index 0000000..a7d9cb5 --- /dev/null +++ b/src/components/App/FlashContext.tsx @@ -0,0 +1,6 @@ +import { createContext } from "preact"; +import { useContext } from "preact/compat"; + +export const FlashContext = createContext(null); + +export const useFlash = () => useContext(FlashContext); diff --git a/src/components/App/FlashProvider.tsx b/src/components/App/FlashProvider.tsx new file mode 100644 index 0000000..e2e12d2 --- /dev/null +++ b/src/components/App/FlashProvider.tsx @@ -0,0 +1,30 @@ +import { FlashContext } from "./FlashContext"; +import { useState } from "preact/hooks"; +import { MESSAGE_FLASH_TIME } from "../../consts"; + +export const FlashProvider = ({ children }) => { + const [messages, setMessages] = useState([] as string[]); + + return ( + { + setMessages((messages: string[]) => [...messages, message]); + setTimeout(() => { + setMessages((messages) => [...messages].splice(1)); + }, MESSAGE_FLASH_TIME); + }} + > + <> + {children} + {messages.map((message) => ( +
+ + + {" "} + {message} +
+ ))} + +
+ ); +}; diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index a35dc72..1b8347e 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -2,31 +2,29 @@ import { Sidebar } from "../Sidebar"; import { QueryClient, QueryClientProvider } from "react-query"; import { Route, Router, Switch } from "wouter"; import { Welcome } from "../Welcome"; -import { GuildReminders } from "../GuildReminders"; +import { Guild } from "../Guild"; +import { FlashProvider } from "./FlashProvider"; export function App() { const queryClient = new QueryClient(); return ( - - <> + +
- + - +
- -
+
+ ); } diff --git a/src/components/Guild/GuildError.tsx b/src/components/Guild/GuildError.tsx new file mode 100644 index 0000000..c0fdd8b --- /dev/null +++ b/src/components/Guild/GuildError.tsx @@ -0,0 +1,25 @@ +export const GuildError = () => { + return ( +
+
+
+

We couldn't get this server's data

+

+ Please check Reminder Bot is in the server, and has correct permissions. +

+ +

+ Add to Server{" "} + + + +

+
+
+
+
+ ); +}; diff --git a/src/components/GuildReminders/index.tsx b/src/components/Guild/GuildReminders.tsx similarity index 91% rename from src/components/GuildReminders/index.tsx rename to src/components/Guild/GuildReminders.tsx index 317edad..1b330e4 100644 --- a/src/components/GuildReminders/index.tsx +++ b/src/components/Guild/GuildReminders.tsx @@ -1,14 +1,13 @@ import { useParams } from "wouter"; import { useQuery } from "react-query"; import { fetchGuildReminders } from "../../api"; -import { QueryKeys } from "../../consts"; import { EditReminder } from "../Reminder/EditReminder"; import { CreateReminder } from "../Reminder/CreateReminder"; export const GuildReminders = () => { const { guild } = useParams(); - const { isSuccess, data } = useQuery(fetchGuildReminders(guild)); + const { isSuccess, data: guildReminders } = useQuery(fetchGuildReminders(guild)); return (
@@ -57,7 +56,9 @@ export const GuildReminders = () => {
{isSuccess && - data.map((reminder) => )} + guildReminders.map((reminder) => ( + + ))}
); diff --git a/src/components/Guild/index.tsx b/src/components/Guild/index.tsx new file mode 100644 index 0000000..e7aa5ea --- /dev/null +++ b/src/components/Guild/index.tsx @@ -0,0 +1,18 @@ +import { useQuery } from "react-query"; +import { fetchGuildInfo } from "../../api"; +import { useParams } from "wouter"; +import { GuildReminders } from "./GuildReminders"; +import { GuildError } from "./GuildError"; + +export const Guild = () => { + const { guild } = useParams(); + const { isSuccess, data: guildInfo } = useQuery(fetchGuildInfo(guild)); + + if (!isSuccess) { + return <>; + } else if (guildInfo.error) { + return ; + } else { + return ; + } +}; diff --git a/src/components/Reminder/ButtonRow/CreateButtonRow.tsx b/src/components/Reminder/ButtonRow/CreateButtonRow.tsx index 48b7730..28e21e7 100644 --- a/src/components/Reminder/ButtonRow/CreateButtonRow.tsx +++ b/src/components/Reminder/ButtonRow/CreateButtonRow.tsx @@ -5,6 +5,7 @@ import { postGuildReminder } from "../../../api"; import { useParams } from "wouter"; import { useState } from "preact/hooks"; import { ICON_FLASH_TIME } from "../../../consts"; +import { useFlash } from "../../App/FlashContext"; export const CreateButtonRow = () => { const { guild } = useParams(); @@ -12,17 +13,22 @@ export const CreateButtonRow = () => { const [recentlyCreated, setRecentlyCreated] = useState(false); + const flash = useFlash(); const queryClient = useQueryClient(); const mutation = useMutation({ ...postGuildReminder(guild), - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ["GUILD_REMINDERS", guild], - }); - setRecentlyCreated(true); - setTimeout(() => { - setRecentlyCreated(false); - }, ICON_FLASH_TIME); + onSuccess: (data) => { + if (data.error) { + flash(data.error); + } else { + queryClient.invalidateQueries({ + queryKey: ["GUILD_REMINDERS", guild], + }); + setRecentlyCreated(true); + setTimeout(() => { + setRecentlyCreated(false); + }, ICON_FLASH_TIME); + } }, }); diff --git a/src/components/Reminder/ButtonRow/EditButtonRow.tsx b/src/components/Reminder/ButtonRow/EditButtonRow.tsx index d9aea80..4128b80 100644 --- a/src/components/Reminder/ButtonRow/EditButtonRow.tsx +++ b/src/components/Reminder/ButtonRow/EditButtonRow.tsx @@ -5,14 +5,17 @@ import { useParams } from "wouter"; import { ICON_FLASH_TIME } from "../../../consts"; import { DeleteButton } from "./DeleteButton"; import { useReminder } from "../ReminderContext"; +import { useFlash } from "../../App/FlashContext"; export const EditButtonRow = () => { const { guild } = useParams(); - const [reminder, setReminder] = useReminder(); + const [reminder] = useReminder(); const [recentlySaved, setRecentlySaved] = useState(false); const queryClient = useQueryClient(); + const flash = useFlash(); + const mutation = useMutation({ ...patchGuildReminder(guild), onSuccess: () => { @@ -24,6 +27,9 @@ export const EditButtonRow = () => { setRecentlySaved(false); }, ICON_FLASH_TIME); }, + onError: (error) => { + flash(error); + }, }); return ( diff --git a/src/components/Reminder/EditReminder.tsx b/src/components/Reminder/EditReminder.tsx index 188e527..e4050d0 100644 --- a/src/components/Reminder/EditReminder.tsx +++ b/src/components/Reminder/EditReminder.tsx @@ -27,7 +27,7 @@ export const EditReminder = ({ reminder: initialReminder }: Props) => { - + ); diff --git a/src/components/Reminder/IntervalSelector.tsx b/src/components/Reminder/IntervalSelector.tsx index 958cce9..7d5c1bf 100644 --- a/src/components/Reminder/IntervalSelector.tsx +++ b/src/components/Reminder/IntervalSelector.tsx @@ -36,9 +36,9 @@ export const IntervalSelector = ({ useEffect(() => { if (seconds || minutes || hours || days || months) { setInterval({ - seconds: seconds + minutes * 60 + hours * 3600, - days: days, - months: months, + seconds: (seconds || 0) + (minutes || 0) * 60 + (hours || 0) * 3600, + days: days || 0, + months: months || 0, }); } else { clearInterval();