Update reminders
This commit is contained in:
parent
5dc7ceb8aa
commit
31b25e3533
53
src/api.ts
53
src/api.ts
@ -1,5 +1,6 @@
|
||||
import axios from "axios";
|
||||
import { DateTime } from "luxon";
|
||||
import { QueryClient } from "react-query";
|
||||
|
||||
type UserInfo = {
|
||||
name: string;
|
||||
@ -77,15 +78,39 @@ type Template = {
|
||||
embed_fields: EmbedField[] | null;
|
||||
};
|
||||
|
||||
const USER_INFO_STALE_TIME = 120_000;
|
||||
const GUILD_INFO_STALE_TIME = 300_000;
|
||||
const OTHER_STALE_TIME = 15_000;
|
||||
|
||||
export const fetchUserInfo = () => ({
|
||||
queryKey: ["USER_INFO"],
|
||||
queryFn: () => axios.get("/dashboard/api/user").then((resp) => resp.data) as Promise<UserInfo>,
|
||||
staleTime: USER_INFO_STALE_TIME,
|
||||
});
|
||||
|
||||
export const fetchUserGuilds = () => ({
|
||||
queryKey: ["USER_GUILDS"],
|
||||
queryFn: () =>
|
||||
axios.get("/dashboard/api/user/guilds").then((resp) => resp.data) as Promise<GuildInfo[]>,
|
||||
staleTime: USER_INFO_STALE_TIME,
|
||||
});
|
||||
|
||||
export const fetchGuildChannels = (guild: string) => ({
|
||||
queryKey: ["GUILD_CHANNELS", guild],
|
||||
queryFn: () =>
|
||||
axios.get(`/dashboard/api/guild/${guild}/channels`).then((resp) => resp.data) as Promise<
|
||||
ChannelInfo[]
|
||||
>,
|
||||
staleTime: GUILD_INFO_STALE_TIME,
|
||||
});
|
||||
|
||||
export const fetchGuildRoles = (guild: string) => ({
|
||||
queryKey: ["GUILD_ROLES", guild],
|
||||
queryFn: () =>
|
||||
axios.get(`/dashboard/api/guild/${guild}/roles`).then((resp) => resp.data) as Promise<
|
||||
RoleInfo[]
|
||||
>,
|
||||
staleTime: GUILD_INFO_STALE_TIME,
|
||||
});
|
||||
|
||||
export const fetchGuildReminders = (guild: string) => ({
|
||||
@ -101,30 +126,22 @@ export const fetchGuildReminders = (guild: string) => ({
|
||||
expires: reminder.expires === null ? null : DateTime.fromISO(reminder.expires),
|
||||
})),
|
||||
) as Promise<Reminder[]>,
|
||||
staleTime: OTHER_STALE_TIME,
|
||||
});
|
||||
|
||||
export const fetchGuildChannels = (guild: string) => ({
|
||||
queryKey: ["GUILD_CHANNELS", guild],
|
||||
queryFn: () =>
|
||||
axios.get(`/dashboard/api/guild/${guild}/channels`).then((resp) => resp.data) as Promise<
|
||||
ChannelInfo[]
|
||||
>,
|
||||
staleTime: 300,
|
||||
export const mutateGuildReminder = (guild: string) => ({
|
||||
mutationFn: (reminder: Reminder) =>
|
||||
axios.patch(`/dashboard/api/guild/${guild}/reminders`, {
|
||||
...reminder,
|
||||
utc_time: reminder.utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const fetchGuildRoles = (guild: string) => ({
|
||||
queryKey: ["GUILD_ROLES", guild],
|
||||
queryFn: () =>
|
||||
axios.get(`/dashboard/api/guild/${guild}/roles`).then((resp) => resp.data) as Promise<
|
||||
RoleInfo[]
|
||||
>,
|
||||
staleTime: 300,
|
||||
});
|
||||
|
||||
export const guildTemplatesQuery = (guild: string) => ({
|
||||
export const fetchGuildTemplates = (guild: string) => ({
|
||||
queryKey: ["GUILD_TEMPLATES", guild],
|
||||
queryFn: () =>
|
||||
axios.get(`/dashboard/api/guild/${guild}/channels`).then((resp) => resp.data) as Promise<
|
||||
axios.get(`/dashboard/api/guild/${guild}/templates`).then((resp) => resp.data) as Promise<
|
||||
Template[]
|
||||
>,
|
||||
staleTime: OTHER_STALE_TIME,
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ export const Content = ({ value, onChange }) => (
|
||||
<label class="is-sr-only">Content</label>
|
||||
<textarea
|
||||
class="message-input autoresize discord-content"
|
||||
placeholder="Content Content..."
|
||||
placeholder="Content..."
|
||||
maxlength={2000}
|
||||
name="content"
|
||||
rows={1}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { fetchGuildChannels, fetchUserInfo, Reminder } from "../../api";
|
||||
import { useQueries } from "react-query";
|
||||
import { QueryKeys } from "../../consts";
|
||||
import { fetchGuildChannels, fetchUserInfo, mutateGuildReminder, Reminder } from "../../api";
|
||||
import { useMutation, useQueries, useQueryClient } from "react-query";
|
||||
import { useParams } from "wouter";
|
||||
import { Name } from "./Name";
|
||||
import { Username } from "./Username";
|
||||
@ -10,6 +9,7 @@ import { useState } from "preact/hooks";
|
||||
import { IntervalSelector } from "./IntervalSelector";
|
||||
import { Embed } from "./Embed";
|
||||
import { DateTime } from "luxon";
|
||||
import { ICON_FLASH_TIME } from "../../consts";
|
||||
|
||||
type Props = {
|
||||
reminder: Reminder;
|
||||
@ -19,12 +19,27 @@ export const EditReminder = ({ reminder: initialReminder }: Props) => {
|
||||
const { guild } = useParams();
|
||||
const [reminder, setReminder] = useState(initialReminder);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const mutation = useMutation({
|
||||
...mutateGuildReminder(guild),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["GUILD_REMINDERS", guild],
|
||||
});
|
||||
setRecentlySaved(true);
|
||||
setTimeout(() => {
|
||||
setRecentlySaved(false);
|
||||
}, ICON_FLASH_TIME);
|
||||
},
|
||||
});
|
||||
|
||||
const [
|
||||
{ isSuccess: channelsFetched, data: guildChannels },
|
||||
{ isSuccess: userFetched, data: userInfo },
|
||||
] = useQueries([fetchGuildChannels(guild), fetchUserInfo()]);
|
||||
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [recentlySaved, setRecentlySaved] = useState(false);
|
||||
|
||||
if (!channelsFetched || !userFetched) {
|
||||
// todo
|
||||
@ -202,11 +217,27 @@ export const EditReminder = ({ reminder: initialReminder }: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row-edit">
|
||||
<button class="button is-success save-btn">
|
||||
<button
|
||||
class="button is-success save-btn"
|
||||
onClick={() => {
|
||||
mutation.mutate(reminder);
|
||||
}}
|
||||
disabled={mutation.isLoading}
|
||||
>
|
||||
<span>Save</span>{" "}
|
||||
<span class="icon">
|
||||
<i class="fas fa-save"></i>
|
||||
</span>
|
||||
{mutation.isLoading ? (
|
||||
<span class="icon">
|
||||
<i class="fas fa-spin fa-cog"></i>
|
||||
</span>
|
||||
) : recentlySaved ? (
|
||||
<span class="icon">
|
||||
<i class="fas fa-check"></i>
|
||||
</span>
|
||||
) : (
|
||||
<span class="icon">
|
||||
<i class="fas fa-save"></i>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button class="button is-warning">{reminder.enabled ? "Disable" : "Enable"}</button>
|
||||
<button class="button is-danger delete-reminder">Delete</button>
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { ImagePicker } from "../ImagePicker";
|
||||
import { Reminder } from "../../../api";
|
||||
|
||||
export const Author = ({ name, icon }) => {
|
||||
type Props = {
|
||||
name: string;
|
||||
icon: string;
|
||||
setReminder: (r: (reminder: Reminder) => void) => void;
|
||||
};
|
||||
|
||||
export const Author = ({ name, icon, setReminder }: Props) => {
|
||||
return (
|
||||
<div class="embed-author-box">
|
||||
<div class="a">
|
||||
@ -9,7 +16,12 @@ export const Author = ({ name, icon }) => {
|
||||
class="is-rounded embed_author_url"
|
||||
url={icon}
|
||||
alt="Image for embed author"
|
||||
setImage={() => {}}
|
||||
setImage={(url) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
embed_author_url: url,
|
||||
}));
|
||||
}}
|
||||
></ImagePicker>
|
||||
</p>
|
||||
</div>
|
||||
@ -25,6 +37,12 @@ export const Author = ({ name, icon }) => {
|
||||
maxlength={256}
|
||||
name="embed_author"
|
||||
value={name}
|
||||
onChange={(ev) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
embed_author: ev.currentTarget.value,
|
||||
}));
|
||||
}}
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,19 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { HexColorPicker } from "react-colorful";
|
||||
import { Modal } from "../../Modal";
|
||||
import { Reminder } from "../../../api";
|
||||
|
||||
export const Color = ({ color: colorProp, onChange }) => {
|
||||
type Props = {
|
||||
color: string;
|
||||
setReminder: (r: (reminder: Reminder) => void) => void;
|
||||
};
|
||||
|
||||
function colorToInt(hex: string) {
|
||||
return parseInt(hex.substring(1), 16);
|
||||
}
|
||||
|
||||
export const Color = ({ color, setReminder }: Props) => {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [color, setColor] = useState(colorProp);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -12,7 +21,7 @@ export const Color = ({ color: colorProp, onChange }) => {
|
||||
<ColorModal
|
||||
color={color}
|
||||
setModalOpen={setModalOpen}
|
||||
onSave={setColor}
|
||||
setReminder={setReminder}
|
||||
></ColorModal>
|
||||
)}
|
||||
<button
|
||||
@ -28,13 +37,19 @@ export const Color = ({ color: colorProp, onChange }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ColorModal = ({ setModalOpen, color: colorProp, onSave }) => {
|
||||
const [color, setColor] = useState(colorProp);
|
||||
|
||||
const ColorModal = ({ setModalOpen, color, setReminder }) => {
|
||||
return (
|
||||
<Modal setModalOpen={setModalOpen} title={"Select Color"} onSubmit={onSave}>
|
||||
<Modal setModalOpen={setModalOpen} title={"Select Color"}>
|
||||
<div class="colorpicker-container">
|
||||
<HexColorPicker color={color} onChange={setColor}></HexColorPicker>
|
||||
<HexColorPicker
|
||||
color={color}
|
||||
onChange={(color) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
embed_color: colorToInt(color),
|
||||
}));
|
||||
}}
|
||||
></HexColorPicker>
|
||||
</div>
|
||||
<br></br>
|
||||
<input
|
||||
@ -42,7 +57,10 @@ const ColorModal = ({ setModalOpen, color: colorProp, onSave }) => {
|
||||
id="colorInput"
|
||||
value={color}
|
||||
onChange={(ev) => {
|
||||
setColor(ev.currentTarget.value);
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
embed_color: colorToInt(ev.currentTarget.value),
|
||||
}));
|
||||
}}
|
||||
></input>
|
||||
</Modal>
|
||||
|
@ -1,13 +1,26 @@
|
||||
export const Footer = ({ footer, icon }) => (
|
||||
import { Reminder } from "../../../api";
|
||||
import { ImagePicker } from "../ImagePicker";
|
||||
|
||||
type Props = {
|
||||
footer: string;
|
||||
icon: string;
|
||||
setReminder: (r: (reminder: Reminder) => void) => void;
|
||||
};
|
||||
|
||||
export const Footer = ({ footer, icon, setReminder }: Props) => (
|
||||
<div class="embed-footer-box">
|
||||
<p class="image is-20x20 customizable">
|
||||
<a>
|
||||
<img
|
||||
class="is-rounded embed_footer_url"
|
||||
src={icon || "/static/img/bg.webp"}
|
||||
alt="Footer profile-like image"
|
||||
></img>
|
||||
</a>
|
||||
<ImagePicker
|
||||
class="is-rounded embed_footer_url"
|
||||
url={icon}
|
||||
alt="Footer profile-like image"
|
||||
setImage={(url: string) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
embed_footer_url: url,
|
||||
}));
|
||||
}}
|
||||
></ImagePicker>
|
||||
</p>
|
||||
<label class="is-sr-only" for="embedFooter">
|
||||
Embed Footer text
|
||||
@ -19,6 +32,12 @@ export const Footer = ({ footer, icon }) => (
|
||||
name="embed_footer"
|
||||
rows={1}
|
||||
value={footer}
|
||||
onChange={(ev) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
embed_footer: ev.currentTarget.value,
|
||||
}));
|
||||
}}
|
||||
></textarea>
|
||||
</div>
|
||||
);
|
||||
|
@ -6,25 +6,31 @@ import { Color } from "./Color";
|
||||
import { Reminder } from "../../../api";
|
||||
import { ImagePicker } from "../ImagePicker";
|
||||
|
||||
function colorToInt(hex: string) {
|
||||
return parseInt(hex.substring(1), 16);
|
||||
function intToColor(num: number) {
|
||||
return `#${num.toString(16).padStart(6, "0")}`;
|
||||
}
|
||||
|
||||
const DEFAULT_COLOR = 9418359;
|
||||
|
||||
export const Embed = ({ reminder, setReminder }) => {
|
||||
return (
|
||||
<div class="discord-embed">
|
||||
<div
|
||||
class="discord-embed"
|
||||
style={{
|
||||
borderLeftColor: intToColor(reminder.embed_color || DEFAULT_COLOR),
|
||||
}}
|
||||
>
|
||||
<div class="embed-body">
|
||||
<Color
|
||||
color={reminder.embed_color}
|
||||
onChange={(color: string) => {
|
||||
setReminder((reminder: Reminder) => ({
|
||||
...reminder,
|
||||
embed_color: colorToInt(color),
|
||||
}));
|
||||
}}
|
||||
color={intToColor(reminder.embed_color || DEFAULT_COLOR)}
|
||||
setReminder={setReminder}
|
||||
></Color>
|
||||
<div class="a">
|
||||
<Author name={reminder.embed_author} icon={reminder.embed_author_url}></Author>
|
||||
<Author
|
||||
name={reminder.embed_author}
|
||||
icon={reminder.embed_author_url}
|
||||
setReminder={setReminder}
|
||||
></Author>
|
||||
<Title
|
||||
title={reminder.embed_title}
|
||||
onChange={(title: string) =>
|
||||
@ -83,6 +89,7 @@ export const Embed = ({ reminder, setReminder }) => {
|
||||
<p class="image thumbnail customizable">
|
||||
<ImagePicker
|
||||
class="embed_thumbnail_url"
|
||||
url={reminder.embed_thumbnail_url}
|
||||
alt="Square thumbnail embedded image"
|
||||
setImage={() => {}}
|
||||
></ImagePicker>
|
||||
@ -93,12 +100,17 @@ export const Embed = ({ reminder, setReminder }) => {
|
||||
<p class="image is-400x300 customizable">
|
||||
<ImagePicker
|
||||
class="embed_image_url"
|
||||
url={reminder.embed_image_url}
|
||||
alt="Large embedded image"
|
||||
setImage={() => {}}
|
||||
></ImagePicker>
|
||||
</p>
|
||||
|
||||
<Footer footer={reminder.embed_footer} icon={reminder.embed_footer_url}></Footer>
|
||||
<Footer
|
||||
footer={reminder.embed_footer}
|
||||
icon={reminder.embed_footer_url}
|
||||
setReminder={setReminder}
|
||||
></Footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { Modal } from "../Modal";
|
||||
import { useQuery } from "react-query";
|
||||
import { guildTemplatesQuery } from "../../api";
|
||||
import { fetchGuildTemplates } from "../../api";
|
||||
import { useParams } from "wouter";
|
||||
|
||||
export const LoadTemplate = ({ setReminder }) => {
|
||||
@ -24,16 +24,17 @@ export const LoadTemplate = ({ setReminder }) => {
|
||||
|
||||
const LoadTemplateModal = ({ setModalOpen }) => {
|
||||
const { guild } = useParams();
|
||||
const { status, data: templates } = useQuery(guildTemplatesQuery(guild));
|
||||
const { status, data: templates } = useQuery(fetchGuildTemplates(guild));
|
||||
|
||||
return (
|
||||
<Modal setModalOpen={setModalOpen} title={"Load Template"}>
|
||||
<div class="control has-icons-left">
|
||||
<div class="select is-fullwidth">
|
||||
<select id="templateSelect">
|
||||
{templates.map((template) => (
|
||||
<option value={template.id}>{template.name}</option>
|
||||
))}
|
||||
{templates !== undefined &&
|
||||
templates.map((template) => (
|
||||
<option value={template.id}>{template.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-small is-left">
|
||||
|
@ -5,7 +5,6 @@ import { Brand } from "./Brand";
|
||||
import { Wave } from "./Wave";
|
||||
import { GuildEntry } from "./GuildEntry";
|
||||
import { fetchUserGuilds, GuildInfo } from "../../api";
|
||||
import { QueryKeys } from "../../consts";
|
||||
|
||||
type ContentProps = {
|
||||
guilds: GuildInfo[];
|
||||
@ -60,11 +59,7 @@ const SidebarContent = ({ guilds }: ContentProps) => {
|
||||
};
|
||||
|
||||
export const Sidebar = () => {
|
||||
const { status, data } = useQuery({
|
||||
queryKey: [QueryKeys.USER_GUILDS],
|
||||
queryFn: fetchUserGuilds,
|
||||
staleTime: Infinity,
|
||||
});
|
||||
const { status, data } = useQuery(fetchUserGuilds());
|
||||
|
||||
let content = <SidebarContent guilds={[]}></SidebarContent>;
|
||||
if (status === "success") {
|
||||
|
@ -1,4 +1,2 @@
|
||||
export enum QueryKeys {
|
||||
USER_GUILDS = "userGuilds",
|
||||
GUILD_REMINDERS = "guildReminders",
|
||||
}
|
||||
export const ICON_FLASH_TIME = 2_500;
|
||||
export const MESSAGE_FLASH_TIME = 5_000;
|
||||
|
Loading…
Reference in New Issue
Block a user