Message flash provider

Allows errors to be displayed neatly in ephemeral boxes at bottom of screen
This commit is contained in:
jude 2023-11-05 13:45:04 +00:00
parent f310bbef54
commit 30dfaa17af
11 changed files with 131 additions and 31 deletions

View File

@ -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<GuildInfo>,
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`, {
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) => ({

View File

@ -0,0 +1,6 @@
import { createContext } from "preact";
import { useContext } from "preact/compat";
export const FlashContext = createContext(null);
export const useFlash = () => useContext(FlashContext);

View File

@ -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 (
<FlashContext.Provider
value={(message: string) => {
setMessages((messages: string[]) => [...messages, message]);
setTimeout(() => {
setMessages((messages) => [...messages].splice(1));
}, MESSAGE_FLASH_TIME);
}}
>
<>
{children}
{messages.map((message) => (
<div class="notification is-danger flash-message is-active">
<span class="icon">
<i class="far fa-exclamation-circle"></i>
</span>{" "}
<span class="error-message">{message}</span>
</div>
))}
</>
</FlashContext.Provider>
);
};

View File

@ -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 (
<FlashProvider>
<QueryClientProvider client={queryClient}>
<>
<Router base={"/dashboard"}>
<div class="columns is-gapless dashboard-frame">
<Sidebar></Sidebar>
<div class="column is-main-content">
<Switch>
<Route
path={"/:guild/reminders"}
component={GuildReminders}
></Route>
<Route path={"/:guild/reminders"} component={Guild}></Route>
<Route>
<Welcome></Welcome>
<Welcome />
</Route>
</Switch>
</div>
</div>
</Router>
</>
</QueryClientProvider>
</FlashProvider>
);
}

View File

@ -0,0 +1,25 @@
export const GuildError = () => {
return (
<div class="hero is-fullheight">
<div class="hero-body">
<div class="container has-text-centered">
<p class="title">We couldn't get this server's data</p>
<p class="subtitle">
Please check Reminder Bot is in the server, and has correct permissions.
</p>
<a
class="button is-size-4 is-rounded is-success"
href="https://invite.reminder-bot.com"
>
<p class="is-size-4">
<span>Add to Server</span>{" "}
<span class="icon">
<i class="fas fa-chevron-right"></i>
</span>
</p>
</a>
</div>
</div>
</div>
);
};

View File

@ -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 (
<div style={{ margin: "0 12px 12px 12px" }}>
@ -57,7 +56,9 @@ export const GuildReminders = () => {
<div id={"guildReminders"}>
{isSuccess &&
data.map((reminder) => <EditReminder reminder={reminder}></EditReminder>)}
guildReminders.map((reminder) => (
<EditReminder reminder={reminder}></EditReminder>
))}
</div>
</div>
);

View File

@ -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 <GuildError />;
} else {
return <GuildReminders />;
}
};

View File

@ -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,10 +13,14 @@ export const CreateButtonRow = () => {
const [recentlyCreated, setRecentlyCreated] = useState(false);
const flash = useFlash();
const queryClient = useQueryClient();
const mutation = useMutation({
...postGuildReminder(guild),
onSuccess: () => {
onSuccess: (data) => {
if (data.error) {
flash(data.error);
} else {
queryClient.invalidateQueries({
queryKey: ["GUILD_REMINDERS", guild],
});
@ -23,6 +28,7 @@ export const CreateButtonRow = () => {
setTimeout(() => {
setRecentlyCreated(false);
}, ICON_FLASH_TIME);
}
},
});

View File

@ -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 (

View File

@ -27,7 +27,7 @@ export const EditReminder = ({ reminder: initialReminder }: Props) => {
<Message />
<Settings />
</div>
<EditButtonRow></EditButtonRow>
<EditButtonRow />
</div>
</ReminderContext.Provider>
);

View File

@ -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();