Add delete functionality
This commit is contained in:
		
							
								
								
									
										17
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| # reminder-dashboard | ||||
|  | ||||
| The re-re-rewrite of the dashboard. | ||||
|  | ||||
| ## Why | ||||
|  | ||||
| The existing beta variant of the dashboard is written using vanilla JavaScript. This is fine,  | ||||
| but annoying to update. This would've been okay if I was more dedicated to updating the vanilla  | ||||
| JavaScript too, but I want to experiment with "new" things. | ||||
|  | ||||
| This also allows me to expand my frontend skills, which is relevant to part of my job. | ||||
|  | ||||
| ## Developing | ||||
|  | ||||
| 1. Download the parent repo: https://gitea.jellypro.xyz/jude/reminder-bot | ||||
| 2. Initialise the submodules | ||||
| 3. Run both `npm start` and `cargo run` | ||||
							
								
								
									
										11
									
								
								src/api.ts
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/api.ts
									
									
									
									
									
								
							| @@ -129,7 +129,7 @@ export const fetchGuildReminders = (guild: string) => ({ | ||||
|     staleTime: OTHER_STALE_TIME, | ||||
| }); | ||||
|  | ||||
| export const mutateGuildReminder = (guild: string) => ({ | ||||
| export const patchGuildReminder = (guild: string) => ({ | ||||
|     mutationFn: (reminder: Reminder) => | ||||
|         axios.patch(`/dashboard/api/guild/${guild}/reminders`, { | ||||
|             ...reminder, | ||||
| @@ -137,6 +137,15 @@ export const mutateGuildReminder = (guild: string) => ({ | ||||
|         }), | ||||
| }); | ||||
|  | ||||
| export const deleteGuildReminder = (guild: string) => ({ | ||||
|     mutationFn: (reminder: Reminder) => | ||||
|         axios.delete(`/dashboard/api/guild/${guild}/reminders`, { | ||||
|             data: { | ||||
|                 uid: reminder.uid, | ||||
|             }, | ||||
|         }), | ||||
| }); | ||||
|  | ||||
| export const fetchGuildTemplates = (guild: string) => ({ | ||||
|     queryKey: ["GUILD_TEMPLATES", guild], | ||||
|     queryFn: () => | ||||
|   | ||||
							
								
								
									
										68
									
								
								src/components/Reminder/ButtonRow/DeleteButton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/components/Reminder/ButtonRow/DeleteButton.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| import { useState } from "preact/hooks"; | ||||
| import { Modal } from "../../Modal"; | ||||
| import { useMutation, useQueryClient } from "react-query"; | ||||
| import { useReminder } from "../ReminderContext"; | ||||
| import { deleteGuildReminder } from "../../../api"; | ||||
| import { useParams } from "wouter"; | ||||
|  | ||||
| export const DeleteButton = () => { | ||||
|     const [modalOpen, setModalOpen] = useState(false); | ||||
|  | ||||
|     return ( | ||||
|         <> | ||||
|             <button | ||||
|                 class="button is-danger delete-reminder" | ||||
|                 onClick={() => { | ||||
|                     setModalOpen(true); | ||||
|                 }} | ||||
|             > | ||||
|                 Delete | ||||
|             </button> | ||||
|             {modalOpen && <DeleteModal setModalOpen={setModalOpen}></DeleteModal>} | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| const DeleteModal = ({ setModalOpen }) => { | ||||
|     const [reminder] = useReminder(); | ||||
|     const { guild } = useParams(); | ||||
|  | ||||
|     const queryClient = useQueryClient(); | ||||
|     const mutation = useMutation({ | ||||
|         ...deleteGuildReminder(guild), | ||||
|         onSuccess: () => { | ||||
|             queryClient.invalidateQueries({ | ||||
|                 queryKey: ["GUILD_REMINDERS", guild], | ||||
|             }); | ||||
|             setModalOpen(false); | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
|     return ( | ||||
|         <Modal setModalOpen={setModalOpen} title={"Delete Reminder"}> | ||||
|             <> | ||||
|                 <p>This reminder will be permanently deleted. Are you sure?</p> | ||||
|                 <br></br> | ||||
|                 <div class="has-text-centered"> | ||||
|                     <button | ||||
|                         class="button is-danger" | ||||
|                         onClick={() => { | ||||
|                             mutation.mutate(reminder); | ||||
|                         }} | ||||
|                         disabled={mutation.isLoading} | ||||
|                     > | ||||
|                         Delete | ||||
|                     </button> | ||||
|                     <button | ||||
|                         class="button is-light close-modal" | ||||
|                         onClick={() => { | ||||
|                             setModalOpen(false); | ||||
|                         }} | ||||
|                     > | ||||
|                         Cancel | ||||
|                     </button> | ||||
|                 </div> | ||||
|             </> | ||||
|         </Modal> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										68
									
								
								src/components/Reminder/ButtonRow/EditButtonRow.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/components/Reminder/ButtonRow/EditButtonRow.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| import { useState } from "preact/hooks"; | ||||
| import { useMutation, useQueryClient } from "react-query"; | ||||
| import { patchGuildReminder } from "../../../api"; | ||||
| import { useParams } from "wouter"; | ||||
| import { ICON_FLASH_TIME } from "../../../consts"; | ||||
| import { DeleteButton } from "./DeleteButton"; | ||||
| import { useReminder } from "../ReminderContext"; | ||||
|  | ||||
| export const EditButtonRow = () => { | ||||
|     const { guild } = useParams(); | ||||
|     const [reminder, setReminder] = useReminder(); | ||||
|  | ||||
|     const [recentlySaved, setRecentlySaved] = useState(false); | ||||
|     const queryClient = useQueryClient(); | ||||
|  | ||||
|     const mutation = useMutation({ | ||||
|         ...patchGuildReminder(guild), | ||||
|         onSuccess: () => { | ||||
|             queryClient.invalidateQueries({ | ||||
|                 queryKey: ["GUILD_REMINDERS", guild], | ||||
|             }); | ||||
|             setRecentlySaved(true); | ||||
|             setTimeout(() => { | ||||
|                 setRecentlySaved(false); | ||||
|             }, ICON_FLASH_TIME); | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
|     return ( | ||||
|         <div class="button-row-edit"> | ||||
|             <button | ||||
|                 class="button is-success save-btn" | ||||
|                 onClick={() => { | ||||
|                     mutation.mutate(reminder); | ||||
|                 }} | ||||
|                 disabled={mutation.isLoading} | ||||
|             > | ||||
|                 <span>Save</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" | ||||
|                 onClick={() => { | ||||
|                     mutation.mutate({ | ||||
|                         ...reminder, | ||||
|                         enabled: !reminder.enabled, | ||||
|                     }); | ||||
|                 }} | ||||
|                 disabled={mutation.isLoading} | ||||
|             > | ||||
|                 {reminder.enabled ? "Disable" : "Enable"} | ||||
|             </button> | ||||
|             <DeleteButton></DeleteButton> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| @@ -1,15 +1,12 @@ | ||||
| import { fetchGuildChannels, fetchUserInfo, mutateGuildReminder, Reminder } from "../../api"; | ||||
| import { useMutation, useQueries, useQueryClient } from "react-query"; | ||||
| import { fetchGuildChannels, Reminder } from "../../api"; | ||||
| import { useQuery } from "react-query"; | ||||
| import { useParams } from "wouter"; | ||||
| import { Name } from "./Name"; | ||||
| import { Username } from "./Username"; | ||||
| import { Content } from "./Content"; | ||||
| import { ChannelSelector } from "./ChannelSelector"; | ||||
| import { useState } from "preact/hooks"; | ||||
| import { IntervalSelector } from "./IntervalSelector"; | ||||
| import { Embed } from "./Embed"; | ||||
| import { DateTime } from "luxon"; | ||||
| import { ICON_FLASH_TIME } from "../../consts"; | ||||
| import { EditButtonRow } from "./ButtonRow/EditButtonRow"; | ||||
| import { Message } from "./Message"; | ||||
| import { Settings } from "./Settings"; | ||||
| import { ReminderContext } from "./ReminderContext"; | ||||
|  | ||||
| type Props = { | ||||
|     reminder: Reminder; | ||||
| @@ -19,229 +16,40 @@ 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 { isSuccess: channelsFetched, data: guildChannels } = useQuery(fetchGuildChannels(guild)); | ||||
|  | ||||
|     const [collapsed, setCollapsed] = useState(false); | ||||
|     const [recentlySaved, setRecentlySaved] = useState(false); | ||||
|  | ||||
|     if (!channelsFetched || !userFetched) { | ||||
|         // todo | ||||
|     if (!channelsFetched) { | ||||
|         return <></>; | ||||
|     } | ||||
|  | ||||
|     const channelInfo = guildChannels.find((c) => c.id === reminder.channel); | ||||
|  | ||||
|     return ( | ||||
|         <div class={collapsed ? "reminderContent is-collapsed" : "reminderContent"}> | ||||
|             <div class="columns is-mobile column reminder-topbar"> | ||||
|                 <div class="invert-collapses channel-bar">#{channelInfo.name}</div> | ||||
|                 <Name value={reminder.name}></Name> | ||||
|                 <div class="hide-button-bar"> | ||||
|                     <button | ||||
|                         class="button hide-box" | ||||
|                         onClick={() => { | ||||
|                             setCollapsed(!collapsed); | ||||
|                         }} | ||||
|                     > | ||||
|                         <span class="is-sr-only">Hide reminder</span> | ||||
|                         <i class="fas fa-chevron-down"></i> | ||||
|                     </button> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="columns reminder-settings"> | ||||
|                 <div class="column discord-frame"> | ||||
|                     <article class="media"> | ||||
|                         <figure class="media-left"> | ||||
|                             <p class="image is-32x32 customizable"> | ||||
|                                 <a> | ||||
|                                     <img | ||||
|                                         class="is-rounded avatar" | ||||
|                                         src="/static/img/bg.webp" | ||||
|                                         alt="Image for discord avatar" | ||||
|                                     ></img> | ||||
|                                 </a> | ||||
|                             </p> | ||||
|                         </figure> | ||||
|                         <div class="media-content"> | ||||
|                             <div class="content"> | ||||
|                                 <Username | ||||
|                                     value={reminder.username} | ||||
|                                     onChange={(username: string) => { | ||||
|                                         setReminder((reminder) => ({ | ||||
|                                             ...reminder, | ||||
|                                             username, | ||||
|                                         })); | ||||
|                                     }} | ||||
|                                 ></Username> | ||||
|                                 <Content | ||||
|                                     value={reminder.content} | ||||
|                                     onChange={(content: string) => { | ||||
|                                         setReminder((reminder) => ({ | ||||
|                                             ...reminder, | ||||
|                                             content, | ||||
|                                         })); | ||||
|                                     }} | ||||
|                                 ></Content> | ||||
|                                 <Embed reminder={reminder} setReminder={setReminder}></Embed> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </article> | ||||
|                 </div> | ||||
|                 <div class="column settings"> | ||||
|                     <div class="field channel-field"> | ||||
|                         <div class="collapses"> | ||||
|                             <label class="label" for="channelOption"> | ||||
|                                 Channel* | ||||
|                             </label> | ||||
|                         </div> | ||||
|                         <ChannelSelector channel={reminder.channel}></ChannelSelector> | ||||
|                     </div> | ||||
|  | ||||
|                     <div class="field"> | ||||
|                         <div class="control"> | ||||
|                             <label class="label collapses"> | ||||
|                                 Time* | ||||
|                                 <input | ||||
|                                     class="input" | ||||
|                                     type="datetime-local" | ||||
|                                     step="1" | ||||
|                                     name="time" | ||||
|                                     value={reminder.utc_time | ||||
|                                         .toLocal() | ||||
|                                         .toFormat("yyyy-LL-dd'T'HH:mm:ss")} | ||||
|                                     onChange={(ev) => { | ||||
|                                         setReminder((reminder) => ({ | ||||
|                                             ...reminder, | ||||
|                                             utc_time: DateTime.fromISO( | ||||
|                                                 ev.currentTarget.value, | ||||
|                                             ).toUTC(), | ||||
|                                         })); | ||||
|                                     }} | ||||
|                                 ></input> | ||||
|                             </label> | ||||
|                         </div> | ||||
|                     </div> | ||||
|  | ||||
|                     <div class="collapses split-controls"> | ||||
|                         <div> | ||||
|                             <div | ||||
|                                 class={userInfo.patreon ? "patreon-only" : "patreon-only is-locked"} | ||||
|                             > | ||||
|                                 <div class="patreon-invert foreground"> | ||||
|                                     Intervals available on{" "} | ||||
|                                     <a href="https://patreon.com/jellywx">Patreon</a> or{" "} | ||||
|                                     <a href="https://gitea.jellypro.xyz/jude/reminder-bot"> | ||||
|                                         self-hosting | ||||
|                                     </a> | ||||
|                                 </div> | ||||
|                                 <div class="field"> | ||||
|                                     <label class="label"> | ||||
|                                         Interval{" "} | ||||
|                                         <a class="foreground" href="/help/intervals"> | ||||
|                                             <i class="fas fa-question-circle"></i> | ||||
|                                         </a> | ||||
|                                     </label> | ||||
|                                     <IntervalSelector | ||||
|                                         months={reminder.interval_months} | ||||
|                                         days={reminder.interval_days} | ||||
|                                         seconds={reminder.interval_seconds} | ||||
|                                     ></IntervalSelector> | ||||
|                                 </div> | ||||
|  | ||||
|                                 <div class="field"> | ||||
|                                     <div class="control"> | ||||
|                                         <label class="label"> | ||||
|                                             Expiration | ||||
|                                             <input | ||||
|                                                 class="input" | ||||
|                                                 type="datetime-local" | ||||
|                                                 step="1" | ||||
|                                                 name="expiration" | ||||
|                                                 value={ | ||||
|                                                     reminder.expires !== null && | ||||
|                                                     reminder.expires.toFormat( | ||||
|                                                         "yyyy-LL-dd'T'HH:mm:ss", | ||||
|                                                     ) | ||||
|                                                 } | ||||
|                                             ></input> | ||||
|                                         </label> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|  | ||||
|                             <div class="columns is-mobile tts-row"> | ||||
|                                 <div class="column has-text-centered"> | ||||
|                                     <div class="is-boxed"> | ||||
|                                         <label class="label"> | ||||
|                                             Enable TTS <input type="checkbox" name="tts"></input> | ||||
|                                         </label> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                                 <div class="column has-text-centered"> | ||||
|                                     <div class="file is-small is-boxed"> | ||||
|                                         <label class="file-label"> | ||||
|                                             <input | ||||
|                                                 class="file-input" | ||||
|                                                 type="file" | ||||
|                                                 name="attachment" | ||||
|                                             ></input> | ||||
|                                             <span class="file-cta"> | ||||
|                                                 <span class="file-label">Add Attachment</span> | ||||
|                                                 <span class="file-icon"> | ||||
|                                                     <i class="fas fa-upload"></i> | ||||
|                                                 </span> | ||||
|                                             </span> | ||||
|                                         </label> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|         <ReminderContext.Provider value={[reminder, setReminder]}> | ||||
|             <div class={collapsed ? "reminderContent is-collapsed" : "reminderContent"}> | ||||
|                 <div class="columns is-mobile column reminder-topbar"> | ||||
|                     <div class="invert-collapses channel-bar">#{channelInfo.name}</div> | ||||
|                     <Name value={reminder.name}></Name> | ||||
|                     <div class="hide-button-bar"> | ||||
|                         <button | ||||
|                             class="button hide-box" | ||||
|                             onClick={() => { | ||||
|                                 setCollapsed(!collapsed); | ||||
|                             }} | ||||
|                         > | ||||
|                             <span class="is-sr-only">Hide reminder</span> | ||||
|                             <i class="fas fa-chevron-down"></i> | ||||
|                         </button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="columns reminder-settings"> | ||||
|                     <Message /> | ||||
|                     <Settings /> | ||||
|                 </div> | ||||
|                 <EditButtonRow></EditButtonRow> | ||||
|             </div> | ||||
|             <div class="button-row-edit"> | ||||
|                 <button | ||||
|                     class="button is-success save-btn" | ||||
|                     onClick={() => { | ||||
|                         mutation.mutate(reminder); | ||||
|                     }} | ||||
|                     disabled={mutation.isLoading} | ||||
|                 > | ||||
|                     <span>Save</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> | ||||
|             </div> | ||||
|         </div> | ||||
|         </ReminderContext.Provider> | ||||
|     ); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										54
									
								
								src/components/Reminder/Message.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/components/Reminder/Message.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| import { ImagePicker } from "./ImagePicker"; | ||||
| import { Username } from "./Username"; | ||||
| import { Content } from "./Content"; | ||||
| import { Embed } from "./Embed"; | ||||
| import { useReminder } from "./ReminderContext"; | ||||
|  | ||||
| export const Message = () => { | ||||
|     const [reminder, setReminder] = useReminder(); | ||||
|  | ||||
|     return ( | ||||
|         <div class="column discord-frame"> | ||||
|             <article class="media"> | ||||
|                 <figure class="media-left"> | ||||
|                     <p class="image is-32x32 customizable"> | ||||
|                         <ImagePicker | ||||
|                             class="is-rounded avatar" | ||||
|                             url={reminder.avatar} | ||||
|                             alt="Image for discord avatar" | ||||
|                             setImage={(url: string) => { | ||||
|                                 setReminder((reminder) => ({ | ||||
|                                     ...reminder, | ||||
|                                     avatar: url, | ||||
|                                 })); | ||||
|                             }} | ||||
|                         ></ImagePicker> | ||||
|                     </p> | ||||
|                 </figure> | ||||
|                 <div class="media-content"> | ||||
|                     <div class="content"> | ||||
|                         <Username | ||||
|                             value={reminder.username} | ||||
|                             onChange={(username: string) => { | ||||
|                                 setReminder((reminder) => ({ | ||||
|                                     ...reminder, | ||||
|                                     username, | ||||
|                                 })); | ||||
|                             }} | ||||
|                         ></Username> | ||||
|                         <Content | ||||
|                             value={reminder.content} | ||||
|                             onChange={(content: string) => { | ||||
|                                 setReminder((reminder) => ({ | ||||
|                                     ...reminder, | ||||
|                                     content, | ||||
|                                 })); | ||||
|                             }} | ||||
|                         ></Content> | ||||
|                         <Embed reminder={reminder} setReminder={setReminder}></Embed> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </article> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										9
									
								
								src/components/Reminder/ReminderContext.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/components/Reminder/ReminderContext.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| import { createContext } from "preact"; | ||||
| import { useContext } from "preact/compat"; | ||||
| import { Reminder } from "../../api"; | ||||
|  | ||||
| export const ReminderContext = createContext< | ||||
|     [Reminder, (r: (reminder: Reminder) => Reminder) => void] | ||||
| >([null, () => {}]); | ||||
|  | ||||
| export const useReminder = () => useContext(ReminderContext); | ||||
							
								
								
									
										116
									
								
								src/components/Reminder/Settings.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/components/Reminder/Settings.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| import { ChannelSelector } from "./ChannelSelector"; | ||||
| import { DateTime } from "luxon"; | ||||
| import { IntervalSelector } from "./IntervalSelector"; | ||||
| import { useQuery } from "react-query"; | ||||
| import { fetchUserInfo } from "../../api"; | ||||
| import { useReminder } from "./ReminderContext"; | ||||
|  | ||||
| export const Settings = () => { | ||||
|     const { isSuccess: userFetched, data: userInfo } = useQuery(fetchUserInfo()); | ||||
|  | ||||
|     const [reminder, setReminder] = useReminder(); | ||||
|  | ||||
|     if (!userFetched) { | ||||
|         return <></>; | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <div class="column settings"> | ||||
|             <div class="field channel-field"> | ||||
|                 <div class="collapses"> | ||||
|                     <label class="label" for="channelOption"> | ||||
|                         Channel* | ||||
|                     </label> | ||||
|                 </div> | ||||
|                 <ChannelSelector channel={reminder.channel}></ChannelSelector> | ||||
|             </div> | ||||
|  | ||||
|             <div class="field"> | ||||
|                 <div class="control"> | ||||
|                     <label class="label collapses"> | ||||
|                         Time* | ||||
|                         <input | ||||
|                             class="input" | ||||
|                             type="datetime-local" | ||||
|                             step="1" | ||||
|                             name="time" | ||||
|                             value={reminder.utc_time.toLocal().toFormat("yyyy-LL-dd'T'HH:mm:ss")} | ||||
|                             onChange={(ev) => { | ||||
|                                 setReminder((reminder) => ({ | ||||
|                                     ...reminder, | ||||
|                                     utc_time: DateTime.fromISO(ev.currentTarget.value).toUTC(), | ||||
|                                 })); | ||||
|                             }} | ||||
|                         ></input> | ||||
|                     </label> | ||||
|                 </div> | ||||
|             </div> | ||||
|  | ||||
|             <div class="collapses split-controls"> | ||||
|                 <div> | ||||
|                     <div class={userInfo.patreon ? "patreon-only" : "patreon-only is-locked"}> | ||||
|                         <div class="patreon-invert foreground"> | ||||
|                             Intervals available on <a href="https://patreon.com/jellywx">Patreon</a>{" "} | ||||
|                             or{" "} | ||||
|                             <a href="https://gitea.jellypro.xyz/jude/reminder-bot">self-hosting</a> | ||||
|                         </div> | ||||
|                         <div class="field"> | ||||
|                             <label class="label"> | ||||
|                                 Interval{" "} | ||||
|                                 <a class="foreground" href="/help/intervals"> | ||||
|                                     <i class="fas fa-question-circle"></i> | ||||
|                                 </a> | ||||
|                             </label> | ||||
|                             <IntervalSelector | ||||
|                                 months={reminder.interval_months} | ||||
|                                 days={reminder.interval_days} | ||||
|                                 seconds={reminder.interval_seconds} | ||||
|                             ></IntervalSelector> | ||||
|                         </div> | ||||
|  | ||||
|                         <div class="field"> | ||||
|                             <div class="control"> | ||||
|                                 <label class="label"> | ||||
|                                     Expiration | ||||
|                                     <input | ||||
|                                         class="input" | ||||
|                                         type="datetime-local" | ||||
|                                         step="1" | ||||
|                                         name="expiration" | ||||
|                                         value={ | ||||
|                                             reminder.expires !== null && | ||||
|                                             reminder.expires.toFormat("yyyy-LL-dd'T'HH:mm:ss") | ||||
|                                         } | ||||
|                                     ></input> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|  | ||||
|                     <div class="columns is-mobile tts-row"> | ||||
|                         <div class="column has-text-centered"> | ||||
|                             <div class="is-boxed"> | ||||
|                                 <label class="label"> | ||||
|                                     Enable TTS <input type="checkbox" name="tts"></input> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="column has-text-centered"> | ||||
|                             <div class="file is-small is-boxed"> | ||||
|                                 <label class="file-label"> | ||||
|                                     <input class="file-input" type="file" name="attachment"></input> | ||||
|                                     <span class="file-cta"> | ||||
|                                         <span class="file-label">Add Attachment</span> | ||||
|                                         <span class="file-icon"> | ||||
|                                             <i class="fas fa-upload"></i> | ||||
|                                         </span> | ||||
|                                     </span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user