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 = {
|
export type GuildInfo = {
|
||||||
id: string;
|
patreon: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EmbedField = {
|
type EmbedField = {
|
||||||
@ -95,6 +96,13 @@ export const fetchUserGuilds = () => ({
|
|||||||
staleTime: USER_INFO_STALE_TIME,
|
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) => ({
|
export const fetchGuildChannels = (guild: string) => ({
|
||||||
queryKey: ["GUILD_CHANNELS", guild],
|
queryKey: ["GUILD_CHANNELS", guild],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
@ -142,10 +150,12 @@ export const patchGuildReminder = (guild: string) => ({
|
|||||||
|
|
||||||
export const postGuildReminder = (guild: string) => ({
|
export const postGuildReminder = (guild: string) => ({
|
||||||
mutationFn: (reminder: Reminder) =>
|
mutationFn: (reminder: Reminder) =>
|
||||||
axios.post(`/dashboard/api/guild/${guild}/reminders`, {
|
axios
|
||||||
...reminder,
|
.post(`/dashboard/api/guild/${guild}/reminders`, {
|
||||||
utc_time: reminder.utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss"),
|
...reminder,
|
||||||
}),
|
utc_time: reminder.utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss"),
|
||||||
|
})
|
||||||
|
.then((resp) => resp.data),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const deleteGuildReminder = (guild: string) => ({
|
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 { QueryClient, QueryClientProvider } from "react-query";
|
||||||
import { Route, Router, Switch } from "wouter";
|
import { Route, Router, Switch } from "wouter";
|
||||||
import { Welcome } from "../Welcome";
|
import { Welcome } from "../Welcome";
|
||||||
import { GuildReminders } from "../GuildReminders";
|
import { Guild } from "../Guild";
|
||||||
|
import { FlashProvider } from "./FlashProvider";
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<FlashProvider>
|
||||||
<>
|
<QueryClientProvider client={queryClient}>
|
||||||
<Router base={"/dashboard"}>
|
<Router base={"/dashboard"}>
|
||||||
<div class="columns is-gapless dashboard-frame">
|
<div class="columns is-gapless dashboard-frame">
|
||||||
<Sidebar></Sidebar>
|
<Sidebar></Sidebar>
|
||||||
<div class="column is-main-content">
|
<div class="column is-main-content">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route path={"/:guild/reminders"} component={Guild}></Route>
|
||||||
path={"/:guild/reminders"}
|
|
||||||
component={GuildReminders}
|
|
||||||
></Route>
|
|
||||||
<Route>
|
<Route>
|
||||||
<Welcome></Welcome>
|
<Welcome />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</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 { useParams } from "wouter";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { fetchGuildReminders } from "../../api";
|
import { fetchGuildReminders } from "../../api";
|
||||||
import { QueryKeys } from "../../consts";
|
|
||||||
import { EditReminder } from "../Reminder/EditReminder";
|
import { EditReminder } from "../Reminder/EditReminder";
|
||||||
import { CreateReminder } from "../Reminder/CreateReminder";
|
import { CreateReminder } from "../Reminder/CreateReminder";
|
||||||
|
|
||||||
export const GuildReminders = () => {
|
export const GuildReminders = () => {
|
||||||
const { guild } = useParams();
|
const { guild } = useParams();
|
||||||
|
|
||||||
const { isSuccess, data } = useQuery(fetchGuildReminders(guild));
|
const { isSuccess, data: guildReminders } = useQuery(fetchGuildReminders(guild));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ margin: "0 12px 12px 12px" }}>
|
<div style={{ margin: "0 12px 12px 12px" }}>
|
||||||
@ -57,7 +56,9 @@ export const GuildReminders = () => {
|
|||||||
|
|
||||||
<div id={"guildReminders"}>
|
<div id={"guildReminders"}>
|
||||||
{isSuccess &&
|
{isSuccess &&
|
||||||
data.map((reminder) => <EditReminder reminder={reminder}></EditReminder>)}
|
guildReminders.map((reminder) => (
|
||||||
|
<EditReminder reminder={reminder}></EditReminder>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</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 { useParams } from "wouter";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { ICON_FLASH_TIME } from "../../../consts";
|
import { ICON_FLASH_TIME } from "../../../consts";
|
||||||
|
import { useFlash } from "../../App/FlashContext";
|
||||||
|
|
||||||
export const CreateButtonRow = () => {
|
export const CreateButtonRow = () => {
|
||||||
const { guild } = useParams();
|
const { guild } = useParams();
|
||||||
@ -12,17 +13,22 @@ export const CreateButtonRow = () => {
|
|||||||
|
|
||||||
const [recentlyCreated, setRecentlyCreated] = useState(false);
|
const [recentlyCreated, setRecentlyCreated] = useState(false);
|
||||||
|
|
||||||
|
const flash = useFlash();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
...postGuildReminder(guild),
|
...postGuildReminder(guild),
|
||||||
onSuccess: () => {
|
onSuccess: (data) => {
|
||||||
queryClient.invalidateQueries({
|
if (data.error) {
|
||||||
queryKey: ["GUILD_REMINDERS", guild],
|
flash(data.error);
|
||||||
});
|
} else {
|
||||||
setRecentlyCreated(true);
|
queryClient.invalidateQueries({
|
||||||
setTimeout(() => {
|
queryKey: ["GUILD_REMINDERS", guild],
|
||||||
setRecentlyCreated(false);
|
});
|
||||||
}, ICON_FLASH_TIME);
|
setRecentlyCreated(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setRecentlyCreated(false);
|
||||||
|
}, ICON_FLASH_TIME);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5,14 +5,17 @@ import { useParams } from "wouter";
|
|||||||
import { ICON_FLASH_TIME } from "../../../consts";
|
import { ICON_FLASH_TIME } from "../../../consts";
|
||||||
import { DeleteButton } from "./DeleteButton";
|
import { DeleteButton } from "./DeleteButton";
|
||||||
import { useReminder } from "../ReminderContext";
|
import { useReminder } from "../ReminderContext";
|
||||||
|
import { useFlash } from "../../App/FlashContext";
|
||||||
|
|
||||||
export const EditButtonRow = () => {
|
export const EditButtonRow = () => {
|
||||||
const { guild } = useParams();
|
const { guild } = useParams();
|
||||||
const [reminder, setReminder] = useReminder();
|
const [reminder] = useReminder();
|
||||||
|
|
||||||
const [recentlySaved, setRecentlySaved] = useState(false);
|
const [recentlySaved, setRecentlySaved] = useState(false);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const flash = useFlash();
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
...patchGuildReminder(guild),
|
...patchGuildReminder(guild),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@ -24,6 +27,9 @@ export const EditButtonRow = () => {
|
|||||||
setRecentlySaved(false);
|
setRecentlySaved(false);
|
||||||
}, ICON_FLASH_TIME);
|
}, ICON_FLASH_TIME);
|
||||||
},
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
flash(error);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -27,7 +27,7 @@ export const EditReminder = ({ reminder: initialReminder }: Props) => {
|
|||||||
<Message />
|
<Message />
|
||||||
<Settings />
|
<Settings />
|
||||||
</div>
|
</div>
|
||||||
<EditButtonRow></EditButtonRow>
|
<EditButtonRow />
|
||||||
</div>
|
</div>
|
||||||
</ReminderContext.Provider>
|
</ReminderContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -36,9 +36,9 @@ export const IntervalSelector = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (seconds || minutes || hours || days || months) {
|
if (seconds || minutes || hours || days || months) {
|
||||||
setInterval({
|
setInterval({
|
||||||
seconds: seconds + minutes * 60 + hours * 3600,
|
seconds: (seconds || 0) + (minutes || 0) * 60 + (hours || 0) * 3600,
|
||||||
days: days,
|
days: days || 0,
|
||||||
months: months,
|
months: months || 0,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
clearInterval();
|
clearInterval();
|
||||||
|
Loading…
Reference in New Issue
Block a user