Compare commits
	
		
			5 Commits
		
	
	
		
			8ba7a39ce5
			...
			d068782596
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d068782596 | |||
| 5ee1fa60db | |||
| 4aa5b285ac | |||
| a90c0c9232 | |||
|  | 5dde422ee5 | 
| @@ -13,5 +13,7 @@ This also allows me to expand my frontend skills, which is relevant to part of m | ||||
| ## 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` | ||||
| 2. Initialise the submodules: `git pull --recurse-submodules` | ||||
| 3. Run both `npm run dev` and `cargo run` | ||||
| 4. Symlink assets: assuming cloned into `$HOME`, `ln -s $HOME/reminder-bot/reminder-dashboard/dist/index.html $HOME/reminder-bot/web/static/index.html` and  | ||||
| `ln -s $HOME/reminder-bot/reminder-dashboard/dist/static/assets $HOME/reminder-bot/web/static/assets` | ||||
| @@ -3,7 +3,7 @@ | ||||
| 	"private": true, | ||||
| 	"type": "module", | ||||
| 	"scripts": { | ||||
| 		"dev": "vite", | ||||
| 		"dev": "vite build --watch --mode development", | ||||
| 		"build": "vite build", | ||||
| 		"preview": "vite preview" | ||||
| 	}, | ||||
|   | ||||
							
								
								
									
										21
									
								
								src/api.ts
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								src/api.ts
									
									
									
									
									
								
							| @@ -89,7 +89,7 @@ export const fetchUserInfo = () => ({ | ||||
| }); | ||||
|  | ||||
| export const patchUserInfo = () => ({ | ||||
|     mutationFn: (user: UserInfo) => axios.patch(`/dashboard/api/user`, user), | ||||
|     mutationFn: (timezone: string) => axios.patch(`/dashboard/api/user`, { timezone }), | ||||
| }); | ||||
|  | ||||
| export const fetchUserGuilds = () => ({ | ||||
| @@ -178,3 +178,22 @@ export const fetchGuildTemplates = (guild: string) => ({ | ||||
|         >, | ||||
|     staleTime: OTHER_STALE_TIME, | ||||
| }); | ||||
|  | ||||
| export const postGuildTemplate = (guild: string) => ({ | ||||
|     mutationFn: (reminder: Reminder) => | ||||
|         axios | ||||
|             .post(`/dashboard/api/guild/${guild}/templates`, { | ||||
|                 ...reminder, | ||||
|                 utc_time: reminder.utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss"), | ||||
|             }) | ||||
|             .then((resp) => resp.data), | ||||
| }); | ||||
|  | ||||
| export const deleteGuildTemplate = (guild: string) => ({ | ||||
|     mutationFn: (template: Template) => | ||||
|         axios.delete(`/dashboard/api/guild/${guild}/templates`, { | ||||
|             data: { | ||||
|                 id: template.id, | ||||
|             }, | ||||
|         }), | ||||
| }); | ||||
|   | ||||
							
								
								
									
										20
									
								
								src/components/App/TimezoneProvider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/components/App/TimezoneProvider.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| import { createContext } from "preact"; | ||||
| import { useContext } from "preact/compat"; | ||||
| import { useState } from "preact/hooks"; | ||||
| import { SystemZone } from "luxon"; | ||||
|  | ||||
| type TTimezoneContext = [string, (tz: string) => void]; | ||||
|  | ||||
| const TimezoneContext = createContext(["UTC", () => {}] as TTimezoneContext); | ||||
|  | ||||
| export const TimezoneProvider = ({ children }) => { | ||||
|     const [timezone, setTimezone] = useState(SystemZone.name); | ||||
|  | ||||
|     return ( | ||||
|         <TimezoneContext.Provider value={[timezone, setTimezone]}> | ||||
|             {children} | ||||
|         </TimezoneContext.Provider> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| export const useTimezone = () => useContext(TimezoneContext); | ||||
| @@ -4,11 +4,13 @@ import { Route, Router, Switch } from "wouter"; | ||||
| import { Welcome } from "../Welcome"; | ||||
| import { Guild } from "../Guild"; | ||||
| import { FlashProvider } from "./FlashProvider"; | ||||
| import { TimezoneProvider } from "./TimezoneProvider"; | ||||
|  | ||||
| export function App() { | ||||
|     const queryClient = new QueryClient(); | ||||
|  | ||||
|     return ( | ||||
|         <TimezoneProvider> | ||||
|             <FlashProvider> | ||||
|                 <QueryClientProvider client={queryClient}> | ||||
|                     <Router base={"/dashboard"}> | ||||
| @@ -26,5 +28,6 @@ export function App() { | ||||
|                     </Router> | ||||
|                 </QueryClientProvider> | ||||
|             </FlashProvider> | ||||
|         </TimezoneProvider> | ||||
|     ); | ||||
| } | ||||
|   | ||||
							
								
								
									
										19
									
								
								src/components/Reminder/Attachment.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/components/Reminder/Attachment.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| import { useReminder } from "./ReminderContext"; | ||||
|  | ||||
| export const Attachment = () => { | ||||
|     const [{ attachment, attachment_name }, setReminder] = useReminder(); | ||||
|  | ||||
|     return ( | ||||
|         <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">{attachment_name || "Add Attachment"}</span> | ||||
|                     <span class="file-icon"> | ||||
|                         <i class="fas fa-upload"></i> | ||||
|                     </span> | ||||
|                 </span> | ||||
|             </label> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { LoadTemplate } from "../LoadTemplate"; | ||||
| import { useReminder } from "../ReminderContext"; | ||||
| import { useMutation, useQueryClient } from "react-query"; | ||||
| import { postGuildReminder } from "../../../api"; | ||||
| import { postGuildReminder, postGuildTemplate } from "../../../api"; | ||||
| import { useParams } from "wouter"; | ||||
| import { useState } from "preact/hooks"; | ||||
| import { ICON_FLASH_TIME } from "../../../consts"; | ||||
| @@ -12,6 +12,7 @@ export const CreateButtonRow = () => { | ||||
|     const [reminder] = useReminder(); | ||||
|  | ||||
|     const [recentlyCreated, setRecentlyCreated] = useState(false); | ||||
|     const [templateRecentlyCreated, setTemplateRecentlyCreated] = useState(false); | ||||
|  | ||||
|     const flash = useFlash(); | ||||
|     const queryClient = useQueryClient(); | ||||
| @@ -39,6 +40,30 @@ export const CreateButtonRow = () => { | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
|     const templateMutation = useMutation({ | ||||
|         ...postGuildTemplate(guild), | ||||
|         onSuccess: (data) => { | ||||
|             if (data.error) { | ||||
|                 flash({ | ||||
|                     message: data.error, | ||||
|                     type: "error", | ||||
|                 }); | ||||
|             } else { | ||||
|                 flash({ | ||||
|                     message: "Template created", | ||||
|                     type: "success", | ||||
|                 }); | ||||
|                 queryClient.invalidateQueries({ | ||||
|                     queryKey: ["GUILD_TEMPLATES", guild], | ||||
|                 }); | ||||
|                 setTemplateRecentlyCreated(true); | ||||
|                 setTimeout(() => { | ||||
|                     setTemplateRecentlyCreated(false); | ||||
|                 }, ICON_FLASH_TIME); | ||||
|             } | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
|     return ( | ||||
|         <div class="button-row"> | ||||
|             <div class="button-row-reminder"> | ||||
| @@ -66,11 +91,26 @@ export const CreateButtonRow = () => { | ||||
|             </div> | ||||
|             <div class="button-row-template"> | ||||
|                 <div> | ||||
|                     <button class="button is-success is-outlined"> | ||||
|                     <button | ||||
|                         class="button is-success is-outlined" | ||||
|                         onClick={() => { | ||||
|                             templateMutation.mutate(reminder); | ||||
|                         }} | ||||
|                     > | ||||
|                         <span>Create Template</span>{" "} | ||||
|                         {templateMutation.isLoading ? ( | ||||
|                             <span class="icon"> | ||||
|                                 <i class="fas fa-spin fa-cog"></i> | ||||
|                             </span> | ||||
|                         ) : templateRecentlyCreated ? ( | ||||
|                             <span class="icon"> | ||||
|                                 <i class="fas fa-check"></i> | ||||
|                             </span> | ||||
|                         ) : ( | ||||
|                             <span class="icon"> | ||||
|                                 <i class="fas fa-file-spreadsheet"></i> | ||||
|                             </span> | ||||
|                         )} | ||||
|                     </button> | ||||
|                 </div> | ||||
|                 <div> | ||||
|   | ||||
							
								
								
									
										55
									
								
								src/components/Reminder/Embed/Fields/Field.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/components/Reminder/Embed/Fields/Field.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| export const Field = ({ title, value, inlined, index, onUpdate }) => { | ||||
|     return ( | ||||
|         <div data-inlined={inlined ? "1" : "0"} class="embed-field-box" key={index}> | ||||
|             <label class="is-sr-only" for="embedFieldTitle"> | ||||
|                 Field Title | ||||
|             </label> | ||||
|             <div class="is-flex"> | ||||
|                 <textarea | ||||
|                     class="discord-field-title field-input message-input autoresize" | ||||
|                     placeholder="Field Title..." | ||||
|                     rows={1} | ||||
|                     maxlength={256} | ||||
|                     name="embed_field_title[]" | ||||
|                     value={title} | ||||
|                     onInput={(ev) => | ||||
|                         onUpdate({ | ||||
|                             index, | ||||
|                             title: ev.currentTarget.value, | ||||
|                         }) | ||||
|                     } | ||||
|                 ></textarea> | ||||
|                 <button | ||||
|                     class="button is-small inline-btn" | ||||
|                     onClick={() => { | ||||
|                         onUpdate({ | ||||
|                             index, | ||||
|                             inlined: !inlined, | ||||
|                         }); | ||||
|                     }} | ||||
|                 > | ||||
|                     <span class="is-sr-only">Toggle field inline</span> | ||||
|                     <i class="fas fa-arrows-h"></i> | ||||
|                 </button> | ||||
|             </div> | ||||
|  | ||||
|             <label class="is-sr-only" for="embedFieldValue"> | ||||
|                 Field Value | ||||
|             </label> | ||||
|             <textarea | ||||
|                 class="discord-field-value field-input message-input autoresize " | ||||
|                 placeholder="Field Value..." | ||||
|                 maxlength={1024} | ||||
|                 name="embed_field_value[]" | ||||
|                 rows={1} | ||||
|                 value={value} | ||||
|                 onInput={(ev) => | ||||
|                     onUpdate({ | ||||
|                         index, | ||||
|                         value: ev.currentTarget.value, | ||||
|                     }) | ||||
|                 } | ||||
|             ></textarea> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
							
								
								
									
										37
									
								
								src/components/Reminder/Embed/Fields/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/components/Reminder/Embed/Fields/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| import { useReminder } from "../../ReminderContext"; | ||||
| import { Field } from "./Field"; | ||||
|  | ||||
| export const Fields = () => { | ||||
|     const [{ embed_fields }, setReminder] = useReminder(); | ||||
|  | ||||
|     return ( | ||||
|         <div class={"embed-multifield-box"}> | ||||
|             {[...embed_fields, { value: "", title: "", inlined: true }].map((field, index) => ( | ||||
|                 <Field | ||||
|                     {...field} | ||||
|                     index={index} | ||||
|                     onUpdate={({ index, ...props }) => { | ||||
|                         setReminder((reminder) => ({ | ||||
|                             ...reminder, | ||||
|                             embed_fields: [ | ||||
|                                 ...reminder.embed_fields, | ||||
|                                 { value: "", title: "", inlined: true }, | ||||
|                             ] | ||||
|                                 .map((f, i) => { | ||||
|                                     if (i === index) { | ||||
|                                         return { | ||||
|                                             ...f, | ||||
|                                             ...props, | ||||
|                                         }; | ||||
|                                     } else { | ||||
|                                         return f; | ||||
|                                     } | ||||
|                                 }) | ||||
|                                 .filter((f) => f.value || f.title), | ||||
|                         })); | ||||
|                     }} | ||||
|                 ></Field> | ||||
|             ))} | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| @@ -3,6 +3,7 @@ import { Title } from "./Title"; | ||||
| import { Description } from "./Description"; | ||||
| import { Footer } from "./Footer"; | ||||
| import { Color } from "./Color"; | ||||
| import { Fields } from "./Fields"; | ||||
| import { Reminder } from "../../../api"; | ||||
| import { ImagePicker } from "../ImagePicker"; | ||||
| import { useReminder } from "../ReminderContext"; | ||||
| @@ -55,37 +56,7 @@ export const Embed = () => { | ||||
|                     ></Description> | ||||
|                     <br></br> | ||||
|  | ||||
|                     <div class="embed-multifield-box"> | ||||
|                         <div data-inlined="1" class="embed-field-box"> | ||||
|                             <label class="is-sr-only" for="embedFieldTitle"> | ||||
|                                 Field Title | ||||
|                             </label> | ||||
|                             <div class="is-flex"> | ||||
|                                 <textarea | ||||
|                                     class="discord-field-title field-input message-input autoresize" | ||||
|                                     placeholder="Field Title..." | ||||
|                                     rows={1} | ||||
|                                     maxlength={256} | ||||
|                                     name="embed_field_title[]" | ||||
|                                 ></textarea> | ||||
|                                 <button class="button is-small inline-btn"> | ||||
|                                     <span class="is-sr-only">Toggle field inline</span> | ||||
|                                     <i class="fas fa-arrows-h"></i> | ||||
|                                 </button> | ||||
|                             </div> | ||||
|  | ||||
|                             <label class="is-sr-only" for="embedFieldValue"> | ||||
|                                 Field Value | ||||
|                             </label> | ||||
|                             <textarea | ||||
|                                 class="discord-field-value field-input message-input autoresize " | ||||
|                                 placeholder="Field Value..." | ||||
|                                 maxlength={1024} | ||||
|                                 name="embed_field_value[]" | ||||
|                                 rows={1} | ||||
|                             ></textarea> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <Fields /> | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="b"> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { useEffect, useState } from "preact/hooks"; | ||||
| import { useCallback, useEffect, useState } from "preact/hooks"; | ||||
| import { useReminder } from "./ReminderContext"; | ||||
|  | ||||
| function divmod(a: number, b: number) { | ||||
| @@ -45,6 +45,10 @@ export const IntervalSelector = ({ | ||||
|         } | ||||
|     }, [seconds, minutes, hours, days, months]); | ||||
|  | ||||
|     const placeholder = useCallback(() => { | ||||
|         return seconds || minutes || hours || days || months ? "0" : ""; | ||||
|     }, [seconds, minutes, hours, days, months]); | ||||
|  | ||||
|     return ( | ||||
|         <div class="control intervalSelector"> | ||||
|             <div class="input interval-group"> | ||||
| @@ -59,7 +63,7 @@ export const IntervalSelector = ({ | ||||
|                                 name="interval_months" | ||||
|                                 maxlength={2} | ||||
|                                 placeholder="" | ||||
|                                 value={months || ""} | ||||
|                                 value={months || placeholder()} | ||||
|                                 onInput={(ev) => { | ||||
|                                     setMonths(parseInt(ev.currentTarget.value)); | ||||
|                                 }} | ||||
| @@ -75,7 +79,7 @@ export const IntervalSelector = ({ | ||||
|                                 name="interval_days" | ||||
|                                 maxlength={4} | ||||
|                                 placeholder="" | ||||
|                                 value={days || ""} | ||||
|                                 value={days || placeholder()} | ||||
|                                 onInput={(ev) => { | ||||
|                                     setDays(parseInt(ev.currentTarget.value)); | ||||
|                                 }} | ||||
| @@ -93,7 +97,7 @@ export const IntervalSelector = ({ | ||||
|                                 name="interval_hours" | ||||
|                                 maxlength={2} | ||||
|                                 placeholder="HH" | ||||
|                                 value={hours || ""} | ||||
|                                 value={hours || placeholder()} | ||||
|                                 onInput={(ev) => { | ||||
|                                     setHours(parseInt(ev.currentTarget.value)); | ||||
|                                 }} | ||||
| @@ -109,7 +113,7 @@ export const IntervalSelector = ({ | ||||
|                                 name="interval_minutes" | ||||
|                                 maxlength={2} | ||||
|                                 placeholder="MM" | ||||
|                                 value={minutes || ""} | ||||
|                                 value={minutes || placeholder()} | ||||
|                                 onInput={(ev) => { | ||||
|                                     setMinutes(parseInt(ev.currentTarget.value)); | ||||
|                                 }} | ||||
| @@ -125,7 +129,7 @@ export const IntervalSelector = ({ | ||||
|                                 name="interval_seconds" | ||||
|                                 maxlength={2} | ||||
|                                 placeholder="SS" | ||||
|                                 value={seconds || ""} | ||||
|                                 value={seconds || placeholder()} | ||||
|                                 onInput={(ev) => { | ||||
|                                     setSeconds(parseInt(ev.currentTarget.value)); | ||||
|                                 }} | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| import { useState } from "preact/hooks"; | ||||
| import { Modal } from "../Modal"; | ||||
| import { useQuery } from "react-query"; | ||||
| import { fetchGuildTemplates } from "../../api"; | ||||
| import { useMutation, useQuery, useQueryClient } from "react-query"; | ||||
| import { deleteGuildTemplate, fetchGuildTemplates } from "../../api"; | ||||
| import { useParams } from "wouter"; | ||||
| import { useReminder } from "./ReminderContext"; | ||||
| import { useFlash } from "../App/FlashContext"; | ||||
| import { ICON_FLASH_TIME } from "../../consts"; | ||||
|  | ||||
| export const LoadTemplate = () => { | ||||
|     const [modalOpen, setModalOpen] = useState(false); | ||||
| @@ -26,9 +28,21 @@ export const LoadTemplate = () => { | ||||
| const LoadTemplateModal = ({ setModalOpen }) => { | ||||
|     const { guild } = useParams(); | ||||
|     const [reminder, setReminder] = useReminder(); | ||||
|     const { isSuccess, data: templates } = useQuery(fetchGuildTemplates(guild)); | ||||
|  | ||||
|     const [selected, setSelected] = useState(null); | ||||
|     const flash = useFlash(); | ||||
|  | ||||
|     const queryClient = useQueryClient(); | ||||
|     const { isSuccess, data: templates } = useQuery(fetchGuildTemplates(guild)); | ||||
|     const mutation = useMutation({ | ||||
|         ...deleteGuildTemplate(guild), | ||||
|         onSuccess: () => { | ||||
|             flash({ message: "Template deleted", type: "success" }); | ||||
|             queryClient.invalidateQueries({ | ||||
|                 queryKey: ["GUILD_TEMPLATES", guild], | ||||
|             }); | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
|     return ( | ||||
|         <Modal setModalOpen={setModalOpen} title={"Load Template"}> | ||||
| @@ -39,6 +53,7 @@ const LoadTemplateModal = ({ setModalOpen }) => { | ||||
|                         onChange={(ev) => { | ||||
|                             setSelected(ev.currentTarget.value); | ||||
|                         }} | ||||
|                         disabled={mutation.isLoading} | ||||
|                     > | ||||
|                         <option disabled={true} selected={true}> | ||||
|                             Choose template... | ||||
| @@ -58,17 +73,39 @@ const LoadTemplateModal = ({ setModalOpen }) => { | ||||
|                 <button | ||||
|                     class="button is-success close-modal" | ||||
|                     id="load-template" | ||||
|                     style={{ margin: "2px" }} | ||||
|                     onClick={() => { | ||||
|                         const template = templates.find( | ||||
|                             (template) => template.id.toString() === selected, | ||||
|                         ); | ||||
|  | ||||
|                         setReminder((reminder) => ({ | ||||
|                             ...reminder, | ||||
|                             // todo this probably needs to exclude some things | ||||
|                             ...templates.find((template) => template.id === selected), | ||||
|                             ...template, | ||||
|                             // drop the template's ID | ||||
|                             id: undefined, | ||||
|                         })); | ||||
|  | ||||
|                         flash({ message: "Template loaded", type: "success" }); | ||||
|                         setModalOpen(false); | ||||
|                     }} | ||||
|                     disabled={mutation.isLoading} | ||||
|                 > | ||||
|                     Load Template | ||||
|                 </button> | ||||
|                 <button class="button is-danger" id="delete-template"> | ||||
|                 <button | ||||
|                     class="button is-danger" | ||||
|                     id="delete-template" | ||||
|                     style={{ margin: "2px" }} | ||||
|                     onClick={() => { | ||||
|                         const template = templates.find( | ||||
|                             (template) => template.id.toString() === selected, | ||||
|                         ); | ||||
|  | ||||
|                         mutation.mutate(template); | ||||
|                     }} | ||||
|                     disabled={mutation.isLoading} | ||||
|                 > | ||||
|                     Delete | ||||
|                 </button> | ||||
|             </div> | ||||
|   | ||||
| @@ -4,11 +4,15 @@ import { IntervalSelector } from "./IntervalSelector"; | ||||
| import { useQuery } from "react-query"; | ||||
| import { fetchUserInfo } from "../../api"; | ||||
| import { useReminder } from "./ReminderContext"; | ||||
| import { Attachment } from "./Attachment"; | ||||
| import { TTS } from "./TTS"; | ||||
| import { useTimezone } from "../App/TimezoneProvider"; | ||||
|  | ||||
| export const Settings = () => { | ||||
|     const { isSuccess: userFetched, data: userInfo } = useQuery(fetchUserInfo()); | ||||
|  | ||||
|     const [reminder, setReminder] = useReminder(); | ||||
|     const [timezone] = useTimezone(); | ||||
|  | ||||
|     if (!userFetched) { | ||||
|         return <></>; | ||||
| @@ -42,7 +46,9 @@ export const Settings = () => { | ||||
|                             type="datetime-local" | ||||
|                             step="1" | ||||
|                             name="time" | ||||
|                             value={reminder.utc_time.toLocal().toFormat("yyyy-LL-dd'T'HH:mm:ss")} | ||||
|                             value={reminder.utc_time | ||||
|                                 .setZone(timezone) | ||||
|                                 .toFormat("yyyy-LL-dd'T'HH:mm:ss")} | ||||
|                             onInput={(ev) => { | ||||
|                                 setReminder((reminder) => ({ | ||||
|                                     ...reminder, | ||||
| @@ -103,8 +109,20 @@ export const Settings = () => { | ||||
|                                         name="expiration" | ||||
|                                         value={ | ||||
|                                             reminder.expires !== null && | ||||
|                                             reminder.expires.toFormat("yyyy-LL-dd'T'HH:mm:ss") | ||||
|                                             reminder.expires | ||||
|                                                 .setZone(timezone) | ||||
|                                                 .toFormat("yyyy-LL-dd'T'HH:mm:ss") | ||||
|                                         } | ||||
|                                         onInput={(ev) => { | ||||
|                                             setReminder((reminder) => ({ | ||||
|                                                 ...reminder, | ||||
|                                                 expires: ev.currentTarget.value | ||||
|                                                     ? DateTime.fromISO( | ||||
|                                                           ev.currentTarget.value, | ||||
|                                                       ).toUTC() | ||||
|                                                     : null, | ||||
|                                             })); | ||||
|                                         }} | ||||
|                                     ></input> | ||||
|                                 </label> | ||||
|                             </div> | ||||
| @@ -113,24 +131,10 @@ export const Settings = () => { | ||||
|  | ||||
|                     <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> | ||||
|                             <TTS /> | ||||
|                         </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> | ||||
|                             <Attachment /> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|   | ||||
							
								
								
									
										24
									
								
								src/components/Reminder/TTS.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/components/Reminder/TTS.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| import { useReminder } from "./ReminderContext"; | ||||
|  | ||||
| export const TTS = () => { | ||||
|     const [{ tts }, setReminder] = useReminder(); | ||||
|  | ||||
|     return ( | ||||
|         <div class="is-boxed"> | ||||
|             <label class="label"> | ||||
|                 Enable TTS{" "} | ||||
|                 <input | ||||
|                     type="checkbox" | ||||
|                     name="tts" | ||||
|                     checked={tts} | ||||
|                     onInput={(ev) => { | ||||
|                         setReminder((reminder) => ({ | ||||
|                             ...reminder, | ||||
|                             tts: ev.currentTarget.checked, | ||||
|                         })); | ||||
|                     }} | ||||
|                 ></input> | ||||
|             </label> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
| @@ -1,8 +1,9 @@ | ||||
| import { DateTime } from "luxon"; | ||||
| import { useQuery } from "react-query"; | ||||
| import { fetchUserInfo } from "../../api"; | ||||
| import { DateTime, SystemZone } from "luxon"; | ||||
| import { useMutation, useQuery, useQueryClient } from "react-query"; | ||||
| import { fetchUserInfo, patchUserInfo } from "../../api"; | ||||
| import { Modal } from "../Modal"; | ||||
| import { useState } from "preact/hooks"; | ||||
| import { useTimezone } from "../App/TimezoneProvider"; | ||||
|  | ||||
| type DisplayProps = { | ||||
|     timezone: string; | ||||
| @@ -51,15 +52,23 @@ export const TimezonePicker = () => { | ||||
| }; | ||||
|  | ||||
| const TimezoneModal = ({ setModalOpen }) => { | ||||
|     const browserTimezone = DateTime.now().zone.name; | ||||
|     const browserTimezone = SystemZone.name; | ||||
|     const [selectedZone, setSelectedZone] = useTimezone(); | ||||
|  | ||||
|     const queryClient = useQueryClient(); | ||||
|     const { isLoading, isError, data } = useQuery(fetchUserInfo()); | ||||
|     const userInfoMutation = useMutation({ | ||||
|         ...patchUserInfo(), | ||||
|         onSuccess: () => { | ||||
|             queryClient.invalidateQueries(["USER_INFO"]); | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
|     return ( | ||||
|         <Modal title={"Timezone"} setModalOpen={setModalOpen}> | ||||
|             <p> | ||||
|                 Your configured timezone is:{" "} | ||||
|                 <TimezoneDisplay timezone={browserTimezone}></TimezoneDisplay> | ||||
|                 <TimezoneDisplay timezone={selectedZone}></TimezoneDisplay> | ||||
|                 <br></br> | ||||
|                 <br></br> | ||||
|                 Your browser timezone is:{" "} | ||||
| @@ -78,19 +87,37 @@ const TimezoneModal = ({ setModalOpen }) => { | ||||
|             </p> | ||||
|             <br></br> | ||||
|             <div class="has-text-centered"> | ||||
|                 <button class="button is-success close-modal" id="set-browser-timezone"> | ||||
|                 <button | ||||
|                     class="button is-success" | ||||
|                     id="set-browser-timezone" | ||||
|                     onClick={() => { | ||||
|                         setSelectedZone(browserTimezone); | ||||
|                     }} | ||||
|                 > | ||||
|                     <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"> | ||||
|                 <button | ||||
|                     class="button is-success" | ||||
|                     id="set-bot-timezone" | ||||
|                     onClick={() => { | ||||
|                         setSelectedZone(data.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"> | ||||
|                 <button | ||||
|                     class="button is-warning" | ||||
|                     id="update-bot-timezone" | ||||
|                     onClick={() => { | ||||
|                         userInfoMutation.mutate(browserTimezone); | ||||
|                     }} | ||||
|                 > | ||||
|                     Set Bot Timezone | ||||
|                 </button> | ||||
|             </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user