Message flash provider
Allows errors to be displayed neatly in ephemeral boxes at bottom of screen
This commit is contained in:
		
							
								
								
									
										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();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user