Message flash provider
Allows errors to be displayed neatly in ephemeral boxes at bottom of screen
This commit is contained in:
parent
f310bbef54
commit
30dfaa17af
20
src/api.ts
20
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<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`, {
|
||||
...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) => ({
|
||||
|
6
src/components/App/FlashContext.tsx
Normal file
6
src/components/App/FlashContext.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import { createContext } from "preact";
|
||||
import { useContext } from "preact/compat";
|
||||
|
||||
export const FlashContext = createContext(null);
|
||||
|
||||
export const useFlash = () => useContext(FlashContext);
|
30
src/components/App/FlashProvider.tsx
Normal file
30
src/components/App/FlashProvider.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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 (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<>
|
||||
<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>
|
||||
</QueryClientProvider>
|
||||
</FlashProvider>
|
||||
);
|
||||
}
|
||||
|
25
src/components/Guild/GuildError.tsx
Normal file
25
src/components/Guild/GuildError.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
18
src/components/Guild/index.tsx
Normal file
18
src/components/Guild/index.tsx
Normal 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 />;
|
||||
}
|
||||
};
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -27,7 +27,7 @@ export const EditReminder = ({ reminder: initialReminder }: Props) => {
|
||||
<Message />
|
||||
<Settings />
|
||||
</div>
|
||||
<EditButtonRow></EditButtonRow>
|
||||
<EditButtonRow />
|
||||
</div>
|
||||
</ReminderContext.Provider>
|
||||
);
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user