Working to feature parity
This commit is contained in:
parent
30dfaa17af
commit
8ba7a39ce5
@ -1,6 +1,5 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import { QueryClient } from "react-query";
|
|
||||||
|
|
||||||
type UserInfo = {
|
type UserInfo = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -89,6 +88,10 @@ export const fetchUserInfo = () => ({
|
|||||||
staleTime: USER_INFO_STALE_TIME,
|
staleTime: USER_INFO_STALE_TIME,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const patchUserInfo = () => ({
|
||||||
|
mutationFn: (user: UserInfo) => axios.patch(`/dashboard/api/user`, user),
|
||||||
|
});
|
||||||
|
|
||||||
export const fetchUserGuilds = () => ({
|
export const fetchUserGuilds = () => ({
|
||||||
queryKey: ["USER_GUILDS"],
|
queryKey: ["USER_GUILDS"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createContext } from "preact";
|
import { createContext } from "preact";
|
||||||
import { useContext } from "preact/compat";
|
import { useContext } from "preact/compat";
|
||||||
|
import { Message } from "./FlashProvider";
|
||||||
|
|
||||||
export const FlashContext = createContext(null);
|
export const FlashContext = createContext(null as (message: Message) => void);
|
||||||
|
|
||||||
export const useFlash = () => useContext(FlashContext);
|
export const useFlash = () => useContext(FlashContext);
|
||||||
|
@ -2,13 +2,18 @@ import { FlashContext } from "./FlashContext";
|
|||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { MESSAGE_FLASH_TIME } from "../../consts";
|
import { MESSAGE_FLASH_TIME } from "../../consts";
|
||||||
|
|
||||||
|
export type Message = {
|
||||||
|
message: string;
|
||||||
|
type: "error" | "success";
|
||||||
|
};
|
||||||
|
|
||||||
export const FlashProvider = ({ children }) => {
|
export const FlashProvider = ({ children }) => {
|
||||||
const [messages, setMessages] = useState([] as string[]);
|
const [messages, setMessages] = useState([] as Message[]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlashContext.Provider
|
<FlashContext.Provider
|
||||||
value={(message: string) => {
|
value={(message: Message) => {
|
||||||
setMessages((messages: string[]) => [...messages, message]);
|
setMessages((messages: Message[]) => [...messages, message]);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setMessages((messages) => [...messages].splice(1));
|
setMessages((messages) => [...messages].splice(1));
|
||||||
}, MESSAGE_FLASH_TIME);
|
}, MESSAGE_FLASH_TIME);
|
||||||
@ -16,14 +21,22 @@ export const FlashProvider = ({ children }) => {
|
|||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
{messages.map((message) => (
|
<div class="flash-container">
|
||||||
<div class="notification is-danger flash-message is-active">
|
{messages.map((message) => {
|
||||||
<span class="icon">
|
const className = message.type === "error" ? "is-danger" : "is-success";
|
||||||
<i class="far fa-exclamation-circle"></i>
|
const icon =
|
||||||
</span>{" "}
|
message.type === "error" ? "fa-exclamation-circle" : "fa-check";
|
||||||
<span class="error-message">{message}</span>
|
|
||||||
</div>
|
return (
|
||||||
))}
|
<div class={`notification flash-message is-active ${className}`}>
|
||||||
|
<span class="icon">
|
||||||
|
<i class={`far ${icon}`}></i>
|
||||||
|
</span>{" "}
|
||||||
|
<span class="error-message">{message.message}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
</FlashContext.Provider>
|
</FlashContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,7 @@ export function App() {
|
|||||||
<QueryClientProvider client={queryClient}>
|
<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 />
|
||||||
<div class="column is-main-content">
|
<div class="column is-main-content">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={"/:guild/reminders"} component={Guild}></Route>
|
<Route path={"/:guild/reminders"} component={Guild}></Route>
|
||||||
|
@ -19,8 +19,15 @@ export const CreateButtonRow = () => {
|
|||||||
...postGuildReminder(guild),
|
...postGuildReminder(guild),
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
flash(data.error);
|
flash({
|
||||||
|
message: data.error,
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
flash({
|
||||||
|
message: "Reminder created",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["GUILD_REMINDERS", guild],
|
queryKey: ["GUILD_REMINDERS", guild],
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import { useMutation, useQueryClient } from "react-query";
|
|||||||
import { useReminder } from "../ReminderContext";
|
import { useReminder } from "../ReminderContext";
|
||||||
import { deleteGuildReminder } from "../../../api";
|
import { deleteGuildReminder } from "../../../api";
|
||||||
import { useParams } from "wouter";
|
import { useParams } from "wouter";
|
||||||
|
import { useFlash } from "../../App/FlashContext";
|
||||||
|
|
||||||
export const DeleteButton = () => {
|
export const DeleteButton = () => {
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
@ -27,10 +28,15 @@ const DeleteModal = ({ setModalOpen }) => {
|
|||||||
const [reminder] = useReminder();
|
const [reminder] = useReminder();
|
||||||
const { guild } = useParams();
|
const { guild } = useParams();
|
||||||
|
|
||||||
|
const flash = useFlash();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
...deleteGuildReminder(guild),
|
...deleteGuildReminder(guild),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
flash({
|
||||||
|
message: "Reminder deleted",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["GUILD_REMINDERS", guild],
|
queryKey: ["GUILD_REMINDERS", guild],
|
||||||
});
|
});
|
||||||
|
@ -14,8 +14,6 @@ export const EditButtonRow = () => {
|
|||||||
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: () => {
|
||||||
@ -27,9 +25,6 @@ export const EditButtonRow = () => {
|
|||||||
setRecentlySaved(false);
|
setRecentlySaved(false);
|
||||||
}, ICON_FLASH_TIME);
|
}, ICON_FLASH_TIME);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
|
||||||
flash(error);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -25,14 +25,25 @@ export const LoadTemplate = () => {
|
|||||||
|
|
||||||
const LoadTemplateModal = ({ setModalOpen }) => {
|
const LoadTemplateModal = ({ setModalOpen }) => {
|
||||||
const { guild } = useParams();
|
const { guild } = useParams();
|
||||||
const { status, data: templates } = useQuery(fetchGuildTemplates(guild));
|
const [reminder, setReminder] = useReminder();
|
||||||
|
const { isSuccess, data: templates } = useQuery(fetchGuildTemplates(guild));
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal setModalOpen={setModalOpen} title={"Load Template"}>
|
<Modal setModalOpen={setModalOpen} title={"Load Template"}>
|
||||||
<div class="control has-icons-left">
|
<div class="control has-icons-left">
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
<select id="templateSelect">
|
<select
|
||||||
{templates !== undefined &&
|
id="templateSelect"
|
||||||
|
onChange={(ev) => {
|
||||||
|
setSelected(ev.currentTarget.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option disabled={true} selected={true}>
|
||||||
|
Choose template...
|
||||||
|
</option>
|
||||||
|
{isSuccess &&
|
||||||
templates.map((template) => (
|
templates.map((template) => (
|
||||||
<option value={template.id}>{template.name}</option>
|
<option value={template.id}>{template.name}</option>
|
||||||
))}
|
))}
|
||||||
@ -44,7 +55,17 @@ const LoadTemplateModal = ({ setModalOpen }) => {
|
|||||||
</div>
|
</div>
|
||||||
<br></br>
|
<br></br>
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<button class="button is-success close-modal" id="load-template">
|
<button
|
||||||
|
class="button is-success close-modal"
|
||||||
|
id="load-template"
|
||||||
|
onClick={() => {
|
||||||
|
setReminder((reminder) => ({
|
||||||
|
...reminder,
|
||||||
|
// todo this probably needs to exclude some things
|
||||||
|
...templates.find((template) => template.id === selected),
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
>
|
||||||
Load Template
|
Load Template
|
||||||
</button>
|
</button>
|
||||||
<button class="button is-danger" id="delete-template">
|
<button class="button is-danger" id="delete-template">
|
||||||
|
@ -5,6 +5,8 @@ import { Brand } from "./Brand";
|
|||||||
import { Wave } from "./Wave";
|
import { Wave } from "./Wave";
|
||||||
import { GuildEntry } from "./GuildEntry";
|
import { GuildEntry } from "./GuildEntry";
|
||||||
import { fetchUserGuilds, GuildInfo } from "../../api";
|
import { fetchUserGuilds, GuildInfo } from "../../api";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { TimezonePicker } from "../TimezonePicker";
|
||||||
|
|
||||||
type ContentProps = {
|
type ContentProps = {
|
||||||
guilds: GuildInfo[];
|
guilds: GuildInfo[];
|
||||||
@ -32,12 +34,7 @@ const SidebarContent = ({ guilds }: ContentProps) => {
|
|||||||
</span>{" "}
|
</span>{" "}
|
||||||
Import/Export
|
Import/Export
|
||||||
</a>
|
</a>
|
||||||
<a class="show-modal" data-modal="chooseTimezoneModal">
|
<TimezonePicker />
|
||||||
<span class="icon">
|
|
||||||
<i class="fas fa-map-marked"></i>
|
|
||||||
</span>{" "}
|
|
||||||
Timezone
|
|
||||||
</a>
|
|
||||||
<a href="/login/discord/logout">
|
<a href="/login/discord/logout">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="fas fa-sign-out"></i>
|
<i class="fas fa-sign-out"></i>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { QueryKeys } from "../../consts";
|
|
||||||
import { fetchUserInfo } from "../../api";
|
import { fetchUserInfo } from "../../api";
|
||||||
|
import { Modal } from "../Modal";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
|
||||||
type DisplayProps = {
|
type DisplayProps = {
|
||||||
timezone: string;
|
timezone: string;
|
||||||
@ -27,73 +28,72 @@ const TimezoneDisplay = ({ timezone }: DisplayProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TimezonePicker = () => {
|
export const TimezonePicker = () => {
|
||||||
const browserTimezone = DateTime.now().zone.name;
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
|
||||||
const { isLoading, isError, data } = useQuery({
|
|
||||||
queryKey: QueryKeys.USER_DATA,
|
|
||||||
queryFn: fetchUserInfo,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="modal" id="chooseTimezoneModal">
|
<>
|
||||||
<div class="modal-background"></div>
|
<a
|
||||||
<div class="modal-card">
|
class="show-modal"
|
||||||
<header class="modal-card-head">
|
data-modal="chooseTimezoneModal"
|
||||||
<label class="modal-card-title" for="urlInput">
|
onClick={() => {
|
||||||
Update Timezone{" "}
|
setModalOpen(true);
|
||||||
<a href="/help/timezone">
|
}}
|
||||||
<span>
|
>
|
||||||
<i class="fa fa-question-circle"></i>
|
<span class="icon">
|
||||||
</span>
|
<i class="fas fa-map-marked"></i>
|
||||||
</a>
|
</span>{" "}
|
||||||
</label>
|
Timezone
|
||||||
<button class="delete close-modal" aria-label="close"></button>
|
</a>
|
||||||
</header>
|
{modalOpen && <TimezoneModal setModalOpen={setModalOpen} />}
|
||||||
<section class="modal-card-body">
|
</>
|
||||||
<p>
|
);
|
||||||
Your configured timezone is:{" "}
|
};
|
||||||
<TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay>
|
|
||||||
<br></br>
|
const TimezoneModal = ({ setModalOpen }) => {
|
||||||
<br></br>
|
const browserTimezone = DateTime.now().zone.name;
|
||||||
Your browser timezone is:{" "}
|
|
||||||
<TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay>
|
const { isLoading, isError, data } = useQuery(fetchUserInfo());
|
||||||
<br></br>
|
|
||||||
{!isError && (
|
return (
|
||||||
<>
|
<Modal title={"Timezone"} setModalOpen={setModalOpen}>
|
||||||
Your bot timezone is:{" "}
|
<p>
|
||||||
{isLoading ? (
|
Your configured timezone is:{" "}
|
||||||
<i className="fas fa-cog fa-spin"></i>
|
<TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay>
|
||||||
) : (
|
<br></br>
|
||||||
<TimezoneDisplay
|
<br></br>
|
||||||
timezone={data.timezone || "UTC"}
|
Your browser timezone is:{" "}
|
||||||
></TimezoneDisplay>
|
<TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay>
|
||||||
)}
|
<br></br>
|
||||||
</>
|
{!isError && (
|
||||||
)}
|
<>
|
||||||
</p>
|
Your bot timezone is:{" "}
|
||||||
|
{isLoading ? (
|
||||||
<br></br>
|
<i className="fas fa-cog fa-spin"></i>
|
||||||
<div class="has-text-centered">
|
) : (
|
||||||
<button class="button is-success close-modal" id="set-browser-timezone">
|
<TimezoneDisplay timezone={data.timezone || "UTC"}></TimezoneDisplay>
|
||||||
<span>Use Browser Timezone</span>{" "}
|
)}
|
||||||
<span class="icon">
|
</>
|
||||||
<i class="fab fa-firefox-browser"></i>
|
)}
|
||||||
</span>
|
</p>
|
||||||
</button>
|
<br></br>
|
||||||
<button class="button is-link close-modal" id="set-bot-timezone">
|
<div class="has-text-centered">
|
||||||
<span>Use Bot Timezone</span>{" "}
|
<button class="button is-success close-modal" id="set-browser-timezone">
|
||||||
<span class="icon">
|
<span>Use Browser Timezone</span>{" "}
|
||||||
<i class="fab fa-discord"></i>
|
<span class="icon">
|
||||||
</span>
|
<i class="fab fa-firefox-browser"></i>
|
||||||
</button>
|
</span>
|
||||||
<button class="button is-warning close-modal" id="update-bot-timezone">
|
</button>
|
||||||
Set Bot Timezone
|
<button class="button is-link close-modal" id="set-bot-timezone">
|
||||||
</button>
|
<span>Use Bot Timezone</span>{" "}
|
||||||
</div>
|
<span class="icon">
|
||||||
</section>
|
<i class="fab fa-discord"></i>
|
||||||
</div>
|
</span>
|
||||||
<button class="modal-close is-large close-modal" aria-label="close"></button>
|
</button>
|
||||||
</div>
|
<button class="button is-warning close-modal" id="update-bot-timezone">
|
||||||
|
Set Bot Timezone
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user