More work on todo list support
This commit is contained in:
		@@ -1,5 +1,4 @@
 | 
				
			|||||||
import axios from "axios";
 | 
					import axios from "axios";
 | 
				
			||||||
import { DateTime } from "luxon";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UserInfo = {
 | 
					type UserInfo = {
 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ export function App() {
 | 
				
			|||||||
                        <div class="columns is-gapless dashboard-frame">
 | 
					                        <div class="columns is-gapless dashboard-frame">
 | 
				
			||||||
                            <Sidebar />
 | 
					                            <Sidebar />
 | 
				
			||||||
                            <div class="column is-main-content">
 | 
					                            <div class="column is-main-content">
 | 
				
			||||||
 | 
					                                <div style={{ margin: "0 12px 12px 12px" }}>
 | 
				
			||||||
                                    <Switch>
 | 
					                                    <Switch>
 | 
				
			||||||
                                        <Route path={"/@me/reminders"} component={User}></Route>
 | 
					                                        <Route path={"/@me/reminders"} component={User}></Route>
 | 
				
			||||||
                                        <Route
 | 
					                                        <Route
 | 
				
			||||||
@@ -32,11 +33,11 @@ export function App() {
 | 
				
			|||||||
                                        ></Route>
 | 
					                                        ></Route>
 | 
				
			||||||
                                        <Route
 | 
					                                        <Route
 | 
				
			||||||
                                            path={"/:guild/todos"}
 | 
					                                            path={"/:guild/todos"}
 | 
				
			||||||
                                        component={
 | 
					                                            component={() => (
 | 
				
			||||||
                                                <Guild>
 | 
					                                                <Guild>
 | 
				
			||||||
                                                    <GuildTodos />
 | 
					                                                    <GuildTodos />
 | 
				
			||||||
                                                </Guild>
 | 
					                                                </Guild>
 | 
				
			||||||
                                        }
 | 
					                                            )}
 | 
				
			||||||
                                        ></Route>
 | 
					                                        ></Route>
 | 
				
			||||||
                                        <Route>
 | 
					                                        <Route>
 | 
				
			||||||
                                            <Welcome />
 | 
					                                            <Welcome />
 | 
				
			||||||
@@ -44,6 +45,7 @@ export function App() {
 | 
				
			|||||||
                                    </Switch>
 | 
					                                    </Switch>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
                    </Router>
 | 
					                    </Router>
 | 
				
			||||||
                </QueryClientProvider>
 | 
					                </QueryClientProvider>
 | 
				
			||||||
            </FlashProvider>
 | 
					            </FlashProvider>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,12 +37,12 @@ export const GuildReminders = () => {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            {!isFetched && <Loader />}
 | 
					            {!isFetched && <Loader />}
 | 
				
			||||||
            <div style={{ margin: "0 12px 12px 12px" }}>
 | 
					
 | 
				
			||||||
            <strong>Create Reminder</strong>
 | 
					            <strong>Create Reminder</strong>
 | 
				
			||||||
            <div id={"reminderCreator"}>
 | 
					            <div id={"reminderCreator"}>
 | 
				
			||||||
                <CreateReminder />
 | 
					                <CreateReminder />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
                <br></br>
 | 
					            <br />
 | 
				
			||||||
            <div class={"field"}>
 | 
					            <div class={"field"}>
 | 
				
			||||||
                <div class={"columns is-mobile"}>
 | 
					                <div class={"columns is-mobile"}>
 | 
				
			||||||
                    <div class={"column"}>
 | 
					                    <div class={"column"}>
 | 
				
			||||||
@@ -63,10 +63,7 @@ export const GuildReminders = () => {
 | 
				
			|||||||
                                    <option value={Sort.Name} selected={sort == Sort.Name}>
 | 
					                                    <option value={Sort.Name} selected={sort == Sort.Name}>
 | 
				
			||||||
                                        Name
 | 
					                                        Name
 | 
				
			||||||
                                    </option>
 | 
					                                    </option>
 | 
				
			||||||
                                        <option
 | 
					                                    <option value={Sort.Channel} selected={sort == Sort.Channel}>
 | 
				
			||||||
                                            value={Sort.Channel}
 | 
					 | 
				
			||||||
                                            selected={sort == Sort.Channel}
 | 
					 | 
				
			||||||
                                        >
 | 
					 | 
				
			||||||
                                        Channel
 | 
					                                        Channel
 | 
				
			||||||
                                    </option>
 | 
					                                    </option>
 | 
				
			||||||
                                </select>
 | 
					                                </select>
 | 
				
			||||||
@@ -142,7 +139,6 @@ export const GuildReminders = () => {
 | 
				
			|||||||
                            );
 | 
					                            );
 | 
				
			||||||
                        })}
 | 
					                        })}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,21 +1,52 @@
 | 
				
			|||||||
import { useQuery, useQueryClient } from "react-query";
 | 
					import { useQuery } from "react-query";
 | 
				
			||||||
import { fetchGuildChannels, fetchGuildTodos } from "../../api";
 | 
					import { fetchGuildChannels, fetchGuildTodos } from "../../api";
 | 
				
			||||||
import { useState } from "preact/hooks";
 | 
					 | 
				
			||||||
import { useGuild } from "../App/useGuild";
 | 
					import { useGuild } from "../App/useGuild";
 | 
				
			||||||
 | 
					import { Todo } from "../Todo";
 | 
				
			||||||
 | 
					import { Loader } from "../Loader";
 | 
				
			||||||
 | 
					import { CreateTodo } from "../Todo/CreateTodo";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const GuildTodos = () => {
 | 
					export const GuildTodos = () => {
 | 
				
			||||||
    const guild = useGuild();
 | 
					    const guild = useGuild();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const {
 | 
					    const { isFetched, data: guildTodos } = useQuery(fetchGuildTodos(guild));
 | 
				
			||||||
        isSuccess,
 | 
					 | 
				
			||||||
        isFetching,
 | 
					 | 
				
			||||||
        isFetched,
 | 
					 | 
				
			||||||
        data: guildReminders,
 | 
					 | 
				
			||||||
    } = useQuery(fetchGuildTodos(guild));
 | 
					 | 
				
			||||||
    const { data: channels } = useQuery(fetchGuildChannels(guild));
 | 
					    const { data: channels } = useQuery(fetchGuildChannels(guild));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [collapsed, setCollapsed] = useState(false);
 | 
					    if (!isFetched || !channels) {
 | 
				
			||||||
    const queryClient = useQueryClient();
 | 
					        return <Loader />;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return <></>;
 | 
					    const sortedTodos = guildTodos.sort((a, b) => (a.channel_id < b.channel_id ? 0 : 1));
 | 
				
			||||||
 | 
					    let prevChannel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					            <strong>Create Todo</strong>
 | 
				
			||||||
 | 
					            <CreateTodo />
 | 
				
			||||||
 | 
					            <br />
 | 
				
			||||||
 | 
					            <strong>Todo list</strong>
 | 
				
			||||||
 | 
					            {sortedTodos.map((todo) => {
 | 
				
			||||||
 | 
					                if (prevChannel !== todo.channel_id) {
 | 
				
			||||||
 | 
					                    prevChannel = todo.channel_id;
 | 
				
			||||||
 | 
					                    if (todo.channel_id === null) {
 | 
				
			||||||
 | 
					                        return (
 | 
				
			||||||
 | 
					                            <>
 | 
				
			||||||
 | 
					                                <h2>Server Todos</h2>
 | 
				
			||||||
 | 
					                                <Todo todo={todo} key={todo.id} />
 | 
				
			||||||
 | 
					                            </>
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        const channel = channels.find((ch) => ch.id === todo.channel_id);
 | 
				
			||||||
 | 
					                        return (
 | 
				
			||||||
 | 
					                            <>
 | 
				
			||||||
 | 
					                                <h2>#{channel.name} Todos</h2>
 | 
				
			||||||
 | 
					                                <Todo todo={todo} key={todo.id} />
 | 
				
			||||||
 | 
					                            </>
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return <Todo todo={todo} key={todo.id} />;
 | 
				
			||||||
 | 
					            })}
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								reminder-dashboard/src/components/Guild/index.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								reminder-dashboard/src/components/Guild/index.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					.page-links {
 | 
				
			||||||
 | 
					    > * {
 | 
				
			||||||
 | 
					        margin: 2px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,6 +4,9 @@ import { GuildError } from "./GuildError";
 | 
				
			|||||||
import { createPortal, PropsWithChildren } from "preact/compat";
 | 
					import { createPortal, PropsWithChildren } from "preact/compat";
 | 
				
			||||||
import { Import } from "../Import";
 | 
					import { Import } from "../Import";
 | 
				
			||||||
import { useGuild } from "../App/useGuild";
 | 
					import { useGuild } from "../App/useGuild";
 | 
				
			||||||
 | 
					import { Link } from "wouter";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./index.scss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Guild = ({ children }: PropsWithChildren) => {
 | 
					export const Guild = ({ children }: PropsWithChildren) => {
 | 
				
			||||||
    const guild = useGuild();
 | 
					    const guild = useGuild();
 | 
				
			||||||
@@ -19,6 +22,23 @@ export const Guild = ({ children }: PropsWithChildren) => {
 | 
				
			|||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <>
 | 
					            <>
 | 
				
			||||||
                {importModal}
 | 
					                {importModal}
 | 
				
			||||||
 | 
					                <div class="page-links">
 | 
				
			||||||
 | 
					                    <Link
 | 
				
			||||||
 | 
					                        class="button is-outlined is-success is-small"
 | 
				
			||||||
 | 
					                        href={`/${guild}/reminders`}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        <span>Reminders</span>
 | 
				
			||||||
 | 
					                        <span class="icon">
 | 
				
			||||||
 | 
					                            <i class="fa fa-chevron-right"></i>
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    </Link>
 | 
				
			||||||
 | 
					                    <Link class="button is-outlined is-success is-small" href={`/${guild}/todos`}>
 | 
				
			||||||
 | 
					                        <span>Todo lists</span>
 | 
				
			||||||
 | 
					                        <span class="icon">
 | 
				
			||||||
 | 
					                            <i class="fa fa-chevron-right"></i>
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                    </Link>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
                {children}
 | 
					                {children}
 | 
				
			||||||
            </>
 | 
					            </>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
import { useQuery } from "react-query";
 | 
					import { useQuery } from "react-query";
 | 
				
			||||||
import { useParams } from "wouter";
 | 
					 | 
				
			||||||
import { fetchGuildChannels } from "../../api";
 | 
					import { fetchGuildChannels } from "../../api";
 | 
				
			||||||
 | 
					import { useGuild } from "../App/useGuild";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ChannelSelector = ({ channel, setChannel }) => {
 | 
					export const ChannelSelector = ({ channel, setChannel }) => {
 | 
				
			||||||
    const { guild } = useParams();
 | 
					    const guild = useGuild();
 | 
				
			||||||
    const { isSuccess, data } = useQuery(fetchGuildChannels(guild));
 | 
					    const { isSuccess, data } = useQuery(fetchGuildChannels(guild));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,6 +61,7 @@ export const CreateReminder = () => {
 | 
				
			|||||||
        <ReminderContext.Provider value={[reminder, setReminder]}>
 | 
					        <ReminderContext.Provider value={[reminder, setReminder]}>
 | 
				
			||||||
            <div class={collapsed ? "reminderContent is-collapsed" : "reminderContent"}>
 | 
					            <div class={collapsed ? "reminderContent is-collapsed" : "reminderContent"}>
 | 
				
			||||||
                <TopBar
 | 
					                <TopBar
 | 
				
			||||||
 | 
					                    isCreating={true}
 | 
				
			||||||
                    toggleCollapsed={() => {
 | 
					                    toggleCollapsed={() => {
 | 
				
			||||||
                        setCollapsed(!collapsed);
 | 
					                        setCollapsed(!collapsed);
 | 
				
			||||||
                    }}
 | 
					                    }}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,7 @@ export const EditReminder = ({ reminder: initialReminder, globalCollapse }: Prop
 | 
				
			|||||||
                id={`reminder-${reminder.uid.slice(0, 12)}`}
 | 
					                id={`reminder-${reminder.uid.slice(0, 12)}`}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <TopBar
 | 
					                <TopBar
 | 
				
			||||||
 | 
					                    isCreating={false}
 | 
				
			||||||
                    toggleCollapsed={() => {
 | 
					                    toggleCollapsed={() => {
 | 
				
			||||||
                        setCollapsed(!collapsed);
 | 
					                        setCollapsed(!collapsed);
 | 
				
			||||||
                    }}
 | 
					                    }}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ import { useCallback } from "preact/hooks";
 | 
				
			|||||||
import { DateTime } from "luxon";
 | 
					import { DateTime } from "luxon";
 | 
				
			||||||
import { Name } from "../Name";
 | 
					import { Name } from "../Name";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Guild = ({ toggleCollapsed }) => {
 | 
					export const Guild = ({ toggleCollapsed, isCreating }) => {
 | 
				
			||||||
    const guild = useGuild();
 | 
					    const guild = useGuild();
 | 
				
			||||||
    const [reminder] = useReminder();
 | 
					    const [reminder] = useReminder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -55,7 +55,7 @@ export const Guild = ({ toggleCollapsed }) => {
 | 
				
			|||||||
        <div class="columns is-mobile column reminder-topbar">
 | 
					        <div class="columns is-mobile column reminder-topbar">
 | 
				
			||||||
            {isSuccess && <div class="invert-collapses channel-bar">#{channelName(reminder)}</div>}
 | 
					            {isSuccess && <div class="invert-collapses channel-bar">#{channelName(reminder)}</div>}
 | 
				
			||||||
            <Name />
 | 
					            <Name />
 | 
				
			||||||
            <div class="time-bar">in {string}</div>
 | 
					            {!isCreating && <div class="time-bar">in {string}</div>}
 | 
				
			||||||
            <div class="hide-button-bar">
 | 
					            <div class="hide-button-bar">
 | 
				
			||||||
                <button class="button hide-box" onClick={toggleCollapsed}>
 | 
					                <button class="button hide-box" onClick={toggleCollapsed}>
 | 
				
			||||||
                    <span class="is-sr-only">Hide reminder</span>
 | 
					                    <span class="is-sr-only">Hide reminder</span>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,11 +2,11 @@ import { useGuild } from "../../App/useGuild";
 | 
				
			|||||||
import { Guild } from "./Guild";
 | 
					import { Guild } from "./Guild";
 | 
				
			||||||
import { User } from "./User";
 | 
					import { User } from "./User";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const TopBar = ({ toggleCollapsed }) => {
 | 
					export const TopBar = ({ toggleCollapsed, isCreating }) => {
 | 
				
			||||||
    const guild = useGuild();
 | 
					    const guild = useGuild();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (guild) {
 | 
					    if (guild) {
 | 
				
			||||||
        return <Guild toggleCollapsed={toggleCollapsed} />;
 | 
					        return <Guild toggleCollapsed={toggleCollapsed} isCreating={isCreating} />;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        return <User toggleCollapsed={toggleCollapsed} />;
 | 
					        return <User toggleCollapsed={toggleCollapsed} />;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,9 +17,6 @@ export const GuildEntry = ({ guild }: Props) => {
 | 
				
			|||||||
                        ? "is-active switch-pane"
 | 
					                        ? "is-active switch-pane"
 | 
				
			||||||
                        : "switch-pane"
 | 
					                        : "switch-pane"
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                data-pane="guild"
 | 
					 | 
				
			||||||
                data-guild={guild.id}
 | 
					 | 
				
			||||||
                data-name={guild.name}
 | 
					 | 
				
			||||||
                href={`/${guild.id}/reminders`}
 | 
					                href={`/${guild.id}/reminders`}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <>
 | 
					                <>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,7 +29,6 @@ const SidebarContent = ({ guilds }: ContentProps) => {
 | 
				
			|||||||
                    <li>
 | 
					                    <li>
 | 
				
			||||||
                        <Link
 | 
					                        <Link
 | 
				
			||||||
                            class={loc.startsWith("/@me") ? "is-active switch-pane" : "switch-pane"}
 | 
					                            class={loc.startsWith("/@me") ? "is-active switch-pane" : "switch-pane"}
 | 
				
			||||||
                            data-pane="guild"
 | 
					 | 
				
			||||||
                            href={"/@me/reminders"}
 | 
					                            href={"/@me/reminders"}
 | 
				
			||||||
                        >
 | 
					                        >
 | 
				
			||||||
                            <>
 | 
					                            <>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										31
									
								
								reminder-dashboard/src/components/Todo/CreateTodo.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								reminder-dashboard/src/components/Todo/CreateTodo.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					import { useQuery } from "react-query";
 | 
				
			||||||
 | 
					import { fetchGuildChannels } from "../../api";
 | 
				
			||||||
 | 
					import { useGuild } from "../App/useGuild";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const CreateTodo = () => {
 | 
				
			||||||
 | 
					    const guild = useGuild();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { isSuccess, data: channels } = useQuery(fetchGuildChannels(guild));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div class="todo">
 | 
				
			||||||
 | 
					            <textarea class="input todo-input" onInput={() => null} />
 | 
				
			||||||
 | 
					            <div class="control has-icons-left">
 | 
				
			||||||
 | 
					                <div class="select">
 | 
				
			||||||
 | 
					                    <select name="channel" class="channel-selector" onInput={() => {}}>
 | 
				
			||||||
 | 
					                        <option value="">(None)</option>
 | 
				
			||||||
 | 
					                        {isSuccess && channels.map((c) => <option value={c.id}>{c.name}</option>)}
 | 
				
			||||||
 | 
					                    </select>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="icon is-small is-left">
 | 
				
			||||||
 | 
					                    <i class="fas fa-hashtag"></i>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <button onClick={() => null} class="button is-success save-btn">
 | 
				
			||||||
 | 
					                <span class="icon">
 | 
				
			||||||
 | 
					                    <i class="fa fa-sparkles"></i>
 | 
				
			||||||
 | 
					                </span>
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										10
									
								
								reminder-dashboard/src/components/Todo/index.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								reminder-dashboard/src/components/Todo/index.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					.todo {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: row;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    margin: 6px 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    > * {
 | 
				
			||||||
 | 
					        margin: 0 3px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,19 +1,21 @@
 | 
				
			|||||||
import { Todo as TodoT } from "../../api";
 | 
					import { Todo as TodoT } from "../../api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./index.scss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    todo: TodoT;
 | 
					    todo: TodoT;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Todo = ({ todo }: Props) => {
 | 
					export const Todo = ({ todo }: Props) => {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div>
 | 
					        <div class="todo">
 | 
				
			||||||
            <textarea value={todo.value} onInput={() => null} />
 | 
					            <textarea class="input todo-input" value={todo.value} onInput={() => null} />
 | 
				
			||||||
            <button onClick={() => null} class="btn save-btn">
 | 
					            <button onClick={() => null} class="button is-success save-btn">
 | 
				
			||||||
                <span class="icon">
 | 
					                <span class="icon">
 | 
				
			||||||
                    <i class="fa fa-save"></i>
 | 
					                    <i class="fa fa-save"></i>
 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
            <button onClick={() => null} class="btn delete-btn">
 | 
					            <button onClick={() => null} class="button is-danger">
 | 
				
			||||||
                <span class="icon">
 | 
					                <span class="icon">
 | 
				
			||||||
                    <i class="fa fa-trash"></i>
 | 
					                    <i class="fa fa-trash"></i>
 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,4 +45,5 @@ lazy_static! {
 | 
				
			|||||||
        .map(|inner| inner.parse::<u32>().ok())
 | 
					        .map(|inner| inner.parse::<u32>().ok())
 | 
				
			||||||
        .flatten()
 | 
					        .flatten()
 | 
				
			||||||
        .unwrap_or(600);
 | 
					        .unwrap_or(600);
 | 
				
			||||||
 | 
					    pub static ref SALT: String = env::var("SALT").unwrap();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,46 @@ mod catchers;
 | 
				
			|||||||
mod guards;
 | 
					mod guards;
 | 
				
			||||||
mod metrics;
 | 
					mod metrics;
 | 
				
			||||||
mod routes;
 | 
					mod routes;
 | 
				
			||||||
 | 
					pub mod string {
 | 
				
			||||||
 | 
					    use std::{fmt::Display, str::FromStr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use serde::{de, Deserialize, Deserializer, Serializer};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        T: Display,
 | 
				
			||||||
 | 
					        S: Serializer,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        serializer.collect_str(value)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        T: FromStr,
 | 
				
			||||||
 | 
					        T::Err: Display,
 | 
				
			||||||
 | 
					        D: Deserializer<'de>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod string_opt {
 | 
				
			||||||
 | 
					    use std::fmt::Display;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use serde::{Deserializer, Serializer};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        T: Display,
 | 
				
			||||||
 | 
					        S: Serializer,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if let Some(v) = value {
 | 
				
			||||||
 | 
					            serializer.collect_str(v)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            serializer.serialize_none()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::{env, path::Path};
 | 
					use std::{env, path::Path};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					use log::warn;
 | 
				
			||||||
use rocket::{
 | 
					use rocket::{
 | 
				
			||||||
    delete, get,
 | 
					    delete, get,
 | 
				
			||||||
    http::CookieJar,
 | 
					    http::CookieJar,
 | 
				
			||||||
@@ -5,13 +6,23 @@ use rocket::{
 | 
				
			|||||||
    serde::json::{json, Json},
 | 
					    serde::json::{json, Json},
 | 
				
			||||||
    State,
 | 
					    State,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use serde::Deserialize;
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use serenity::prelude::Context;
 | 
					use serenity::prelude::Context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::web::{check_authorization, guards::transaction::Transaction, routes::JsonResult};
 | 
					use crate::web::{
 | 
				
			||||||
 | 
					    check_authorization, guards::transaction::Transaction, routes::JsonResult, string_opt,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Deserialize)]
 | 
					#[derive(Deserialize)]
 | 
				
			||||||
struct CreateTodo {
 | 
					pub struct CreateTodo {
 | 
				
			||||||
 | 
					    channel_id: Option<u64>,
 | 
				
			||||||
 | 
					    value: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize)]
 | 
				
			||||||
 | 
					struct GetTodo {
 | 
				
			||||||
 | 
					    id: u32,
 | 
				
			||||||
 | 
					    #[serde(with = "string_opt")]
 | 
				
			||||||
    channel_id: Option<u64>,
 | 
					    channel_id: Option<u64>,
 | 
				
			||||||
    value: String,
 | 
					    value: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -38,7 +49,30 @@ pub async fn get_todo(
 | 
				
			|||||||
) -> JsonResult {
 | 
					) -> JsonResult {
 | 
				
			||||||
    check_authorization(cookies, ctx.inner(), id).await?;
 | 
					    check_authorization(cookies, ctx.inner(), id).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(json!([]))
 | 
					    let todos = sqlx::query_as!(
 | 
				
			||||||
 | 
					        GetTodo,
 | 
				
			||||||
 | 
					        "
 | 
				
			||||||
 | 
					        SELECT
 | 
				
			||||||
 | 
					            todos.id,
 | 
				
			||||||
 | 
					            channels.channel AS channel_id,
 | 
				
			||||||
 | 
					            value
 | 
				
			||||||
 | 
					        FROM todos
 | 
				
			||||||
 | 
					        INNER JOIN guilds
 | 
				
			||||||
 | 
					        ON guilds.id = todos.guild_id
 | 
				
			||||||
 | 
					        LEFT JOIN channels
 | 
				
			||||||
 | 
					        ON channels.id = todos.channel_id
 | 
				
			||||||
 | 
					        WHERE guilds.guild = ?
 | 
				
			||||||
 | 
					        ",
 | 
				
			||||||
 | 
					        id
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .fetch_all(transaction.executor())
 | 
				
			||||||
 | 
					    .await
 | 
				
			||||||
 | 
					    .map_err(|e| {
 | 
				
			||||||
 | 
					        warn!("Error fetching todos: {:?}", e);
 | 
				
			||||||
 | 
					        json!({ "errors": vec!["Unknown error"] })
 | 
				
			||||||
 | 
					    })?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(json!(todos))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[patch("/api/guild/<id>/todos")]
 | 
					#[patch("/api/guild/<id>/todos")]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,7 +29,7 @@ use crate::web::{
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    guards::transaction::Transaction,
 | 
					    guards::transaction::Transaction,
 | 
				
			||||||
    routes::JsonResult,
 | 
					    routes::JsonResult,
 | 
				
			||||||
    Error,
 | 
					    string, Error,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod api;
 | 
					pub mod api;
 | 
				
			||||||
@@ -348,30 +348,6 @@ where
 | 
				
			|||||||
    Ok(Some(Option::deserialize(deserializer)?))
 | 
					    Ok(Some(Option::deserialize(deserializer)?))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://github.com/serde-rs/json/issues/329#issuecomment-305608405
 | 
					 | 
				
			||||||
mod string {
 | 
					 | 
				
			||||||
    use std::{fmt::Display, str::FromStr};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    use serde::{de, Deserialize, Deserializer, Serializer};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
 | 
					 | 
				
			||||||
    where
 | 
					 | 
				
			||||||
        T: Display,
 | 
					 | 
				
			||||||
        S: Serializer,
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        serializer.collect_str(value)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
 | 
					 | 
				
			||||||
    where
 | 
					 | 
				
			||||||
        T: FromStr,
 | 
					 | 
				
			||||||
        T::Err: Display,
 | 
					 | 
				
			||||||
        D: Deserializer<'de>,
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Deserialize)]
 | 
					#[derive(Deserialize)]
 | 
				
			||||||
pub struct DeleteReminder {
 | 
					pub struct DeleteReminder {
 | 
				
			||||||
    uid: String,
 | 
					    uid: String,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user