Working to feature parity

This commit is contained in:
jude 2023-11-05 17:01:47 +00:00
parent 30dfaa17af
commit 8ba7a39ce5
10 changed files with 140 additions and 97 deletions

View File

@ -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: () =>

View File

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

View File

@ -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) => {
const className = message.type === "error" ? "is-danger" : "is-success";
const icon =
message.type === "error" ? "fa-exclamation-circle" : "fa-check";
return (
<div class={`notification flash-message is-active ${className}`}>
<span class="icon"> <span class="icon">
<i class="far fa-exclamation-circle"></i> <i class={`far ${icon}`}></i>
</span>{" "} </span>{" "}
<span class="error-message">{message}</span> <span class="error-message">{message.message}</span>
</div>
);
})}
</div> </div>
))}
</> </>
</FlashContext.Provider> </FlashContext.Provider>
); );

View File

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

View File

@ -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],
}); });

View File

@ -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],
}); });

View File

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

View File

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

View File

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

View File

@ -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,30 +28,35 @@ 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>
</span>{" "}
Timezone
</a> </a>
</label> {modalOpen && <TimezoneModal setModalOpen={setModalOpen} />}
<button class="delete close-modal" aria-label="close"></button> </>
</header> );
<section class="modal-card-body"> };
const TimezoneModal = ({ setModalOpen }) => {
const browserTimezone = DateTime.now().zone.name;
const { isLoading, isError, data } = useQuery(fetchUserInfo());
return (
<Modal title={"Timezone"} setModalOpen={setModalOpen}>
<p> <p>
Your configured timezone is:{" "} Your configured timezone is:{" "}
<TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay> <TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay>
@ -65,14 +71,11 @@ const TimezonePicker = () => {
{isLoading ? ( {isLoading ? (
<i className="fas fa-cog fa-spin"></i> <i className="fas fa-cog fa-spin"></i>
) : ( ) : (
<TimezoneDisplay <TimezoneDisplay timezone={data.timezone || "UTC"}></TimezoneDisplay>
timezone={data.timezone || "UTC"}
></TimezoneDisplay>
)} )}
</> </>
)} )}
</p> </p>
<br></br> <br></br>
<div class="has-text-centered"> <div class="has-text-centered">
<button class="button is-success close-modal" id="set-browser-timezone"> <button class="button is-success close-modal" id="set-browser-timezone">
@ -91,9 +94,6 @@ const TimezonePicker = () => {
Set Bot Timezone Set Bot Timezone
</button> </button>
</div> </div>
</section> </Modal>
</div>
<button class="modal-close is-large close-modal" aria-label="close"></button>
</div>
); );
}; };