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 { DateTime } from "luxon";
import { QueryClient } from "react-query";
type UserInfo = {
name: string;
@ -89,6 +88,10 @@ export const fetchUserInfo = () => ({
staleTime: USER_INFO_STALE_TIME,
});
export const patchUserInfo = () => ({
mutationFn: (user: UserInfo) => axios.patch(`/dashboard/api/user`, user),
});
export const fetchUserGuilds = () => ({
queryKey: ["USER_GUILDS"],
queryFn: () =>

View File

@ -1,6 +1,7 @@
import { createContext } from "preact";
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);

View File

@ -2,13 +2,18 @@ import { FlashContext } from "./FlashContext";
import { useState } from "preact/hooks";
import { MESSAGE_FLASH_TIME } from "../../consts";
export type Message = {
message: string;
type: "error" | "success";
};
export const FlashProvider = ({ children }) => {
const [messages, setMessages] = useState([] as string[]);
const [messages, setMessages] = useState([] as Message[]);
return (
<FlashContext.Provider
value={(message: string) => {
setMessages((messages: string[]) => [...messages, message]);
value={(message: Message) => {
setMessages((messages: Message[]) => [...messages, message]);
setTimeout(() => {
setMessages((messages) => [...messages].splice(1));
}, MESSAGE_FLASH_TIME);
@ -16,14 +21,22 @@ export const FlashProvider = ({ children }) => {
>
<>
{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>
))}
<div class="flash-container">
{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">
<i class={`far ${icon}`}></i>
</span>{" "}
<span class="error-message">{message.message}</span>
</div>
);
})}
</div>
</>
</FlashContext.Provider>
);

View File

@ -13,7 +13,7 @@ export function App() {
<QueryClientProvider client={queryClient}>
<Router base={"/dashboard"}>
<div class="columns is-gapless dashboard-frame">
<Sidebar></Sidebar>
<Sidebar />
<div class="column is-main-content">
<Switch>
<Route path={"/:guild/reminders"} component={Guild}></Route>

View File

@ -19,8 +19,15 @@ export const CreateButtonRow = () => {
...postGuildReminder(guild),
onSuccess: (data) => {
if (data.error) {
flash(data.error);
flash({
message: data.error,
type: "error",
});
} else {
flash({
message: "Reminder created",
type: "success",
});
queryClient.invalidateQueries({
queryKey: ["GUILD_REMINDERS", guild],
});

View File

@ -4,6 +4,7 @@ import { useMutation, useQueryClient } from "react-query";
import { useReminder } from "../ReminderContext";
import { deleteGuildReminder } from "../../../api";
import { useParams } from "wouter";
import { useFlash } from "../../App/FlashContext";
export const DeleteButton = () => {
const [modalOpen, setModalOpen] = useState(false);
@ -27,10 +28,15 @@ const DeleteModal = ({ setModalOpen }) => {
const [reminder] = useReminder();
const { guild } = useParams();
const flash = useFlash();
const queryClient = useQueryClient();
const mutation = useMutation({
...deleteGuildReminder(guild),
onSuccess: () => {
flash({
message: "Reminder deleted",
type: "success",
});
queryClient.invalidateQueries({
queryKey: ["GUILD_REMINDERS", guild],
});

View File

@ -14,8 +14,6 @@ export const EditButtonRow = () => {
const [recentlySaved, setRecentlySaved] = useState(false);
const queryClient = useQueryClient();
const flash = useFlash();
const mutation = useMutation({
...patchGuildReminder(guild),
onSuccess: () => {
@ -27,9 +25,6 @@ export const EditButtonRow = () => {
setRecentlySaved(false);
}, ICON_FLASH_TIME);
},
onError: (error) => {
flash(error);
},
});
return (

View File

@ -25,14 +25,25 @@ export const LoadTemplate = () => {
const LoadTemplateModal = ({ setModalOpen }) => {
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 (
<Modal setModalOpen={setModalOpen} title={"Load Template"}>
<div class="control has-icons-left">
<div class="select is-fullwidth">
<select id="templateSelect">
{templates !== undefined &&
<select
id="templateSelect"
onChange={(ev) => {
setSelected(ev.currentTarget.value);
}}
>
<option disabled={true} selected={true}>
Choose template...
</option>
{isSuccess &&
templates.map((template) => (
<option value={template.id}>{template.name}</option>
))}
@ -44,7 +55,17 @@ const LoadTemplateModal = ({ setModalOpen }) => {
</div>
<br></br>
<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
</button>
<button class="button is-danger" id="delete-template">

View File

@ -5,6 +5,8 @@ import { Brand } from "./Brand";
import { Wave } from "./Wave";
import { GuildEntry } from "./GuildEntry";
import { fetchUserGuilds, GuildInfo } from "../../api";
import { useState } from "preact/hooks";
import { TimezonePicker } from "../TimezonePicker";
type ContentProps = {
guilds: GuildInfo[];
@ -32,12 +34,7 @@ const SidebarContent = ({ guilds }: ContentProps) => {
</span>{" "}
Import/Export
</a>
<a class="show-modal" data-modal="chooseTimezoneModal">
<span class="icon">
<i class="fas fa-map-marked"></i>
</span>{" "}
Timezone
</a>
<TimezonePicker />
<a href="/login/discord/logout">
<span class="icon">
<i class="fas fa-sign-out"></i>

View File

@ -1,7 +1,8 @@
import { DateTime } from "luxon";
import { useQuery } from "react-query";
import { QueryKeys } from "../../consts";
import { fetchUserInfo } from "../../api";
import { Modal } from "../Modal";
import { useState } from "preact/hooks";
type DisplayProps = {
timezone: string;
@ -27,73 +28,72 @@ const TimezoneDisplay = ({ timezone }: DisplayProps) => {
);
};
const TimezonePicker = () => {
const browserTimezone = DateTime.now().zone.name;
const { isLoading, isError, data } = useQuery({
queryKey: QueryKeys.USER_DATA,
queryFn: fetchUserInfo,
});
export const TimezonePicker = () => {
const [modalOpen, setModalOpen] = useState(false);
return (
<div class="modal" id="chooseTimezoneModal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<label class="modal-card-title" for="urlInput">
Update Timezone{" "}
<a href="/help/timezone">
<span>
<i class="fa fa-question-circle"></i>
</span>
</a>
</label>
<button class="delete close-modal" aria-label="close"></button>
</header>
<section class="modal-card-body">
<p>
Your configured timezone is:{" "}
<TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay>
<br></br>
<br></br>
Your browser timezone is:{" "}
<TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay>
<br></br>
{!isError && (
<>
Your bot timezone is:{" "}
{isLoading ? (
<i className="fas fa-cog fa-spin"></i>
) : (
<TimezoneDisplay
timezone={data.timezone || "UTC"}
></TimezoneDisplay>
)}
</>
)}
</p>
<br></br>
<div class="has-text-centered">
<button class="button is-success close-modal" id="set-browser-timezone">
<span>Use Browser Timezone</span>{" "}
<span class="icon">
<i class="fab fa-firefox-browser"></i>
</span>
</button>
<button class="button is-link close-modal" id="set-bot-timezone">
<span>Use Bot Timezone</span>{" "}
<span class="icon">
<i class="fab fa-discord"></i>
</span>
</button>
<button class="button is-warning close-modal" id="update-bot-timezone">
Set Bot Timezone
</button>
</div>
</section>
</div>
<button class="modal-close is-large close-modal" aria-label="close"></button>
</div>
<>
<a
class="show-modal"
data-modal="chooseTimezoneModal"
onClick={() => {
setModalOpen(true);
}}
>
<span class="icon">
<i class="fas fa-map-marked"></i>
</span>{" "}
Timezone
</a>
{modalOpen && <TimezoneModal setModalOpen={setModalOpen} />}
</>
);
};
const TimezoneModal = ({ setModalOpen }) => {
const browserTimezone = DateTime.now().zone.name;
const { isLoading, isError, data } = useQuery(fetchUserInfo());
return (
<Modal title={"Timezone"} setModalOpen={setModalOpen}>
<p>
Your configured timezone is:{" "}
<TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay>
<br></br>
<br></br>
Your browser timezone is:{" "}
<TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay>
<br></br>
{!isError && (
<>
Your bot timezone is:{" "}
{isLoading ? (
<i className="fas fa-cog fa-spin"></i>
) : (
<TimezoneDisplay timezone={data.timezone || "UTC"}></TimezoneDisplay>
)}
</>
)}
</p>
<br></br>
<div class="has-text-centered">
<button class="button is-success close-modal" id="set-browser-timezone">
<span>Use Browser Timezone</span>{" "}
<span class="icon">
<i class="fab fa-firefox-browser"></i>
</span>
</button>
<button class="button is-link close-modal" id="set-bot-timezone">
<span>Use Bot Timezone</span>{" "}
<span class="icon">
<i class="fab fa-discord"></i>
</span>
</button>
<button class="button is-warning close-modal" id="update-bot-timezone">
Set Bot Timezone
</button>
</div>
</Modal>
);
};