Compare commits
No commits in common. "141eac7d173132b6c7ae5c0d43bd4242fddcb3c1" and "056401bb22ce1a898346d765d97448cad448dd9b" have entirely different histories.
141eac7d17
...
056401bb22
@ -7,7 +7,4 @@ blueprint = Blueprint("control", __name__)
|
|||||||
@blueprint.get("/control/ping/")
|
@blueprint.get("/control/ping/")
|
||||||
@rate_exempt
|
@rate_exempt
|
||||||
async def ping() -> ResponseReturnValue:
|
async def ping() -> ResponseReturnValue:
|
||||||
"""Ping the server
|
|
||||||
Check if server is up and running.
|
|
||||||
"""
|
|
||||||
return {"ping": "pong"}
|
return {"ping": "pong"}
|
||||||
|
@ -79,5 +79,5 @@
|
|||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "all"
|
"trailingComma": "all"
|
||||||
},
|
},
|
||||||
"proxy": "http://127.0.0.1:5050"
|
"proxy": "http://localhost:5050"
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { BrowserRouter, Route, Routes } from "react-router-dom";
|
|||||||
|
|
||||||
import ScrollToTop from "./components/ScrollToTop";
|
import ScrollToTop from "./components/ScrollToTop";
|
||||||
import TopBar from "./components/TopBar";
|
import TopBar from "./components/TopBar";
|
||||||
|
|
||||||
import Register from "./pages/Register";
|
import Register from "./pages/Register";
|
||||||
import ConfirmEmail from "./pages/ConfirmEmail";
|
import ConfirmEmail from "./pages/ConfirmEmail";
|
||||||
import Login from "./pages/Login";
|
import Login from "./pages/Login";
|
||||||
@ -11,10 +10,6 @@ import ChangePassword from "./pages/ChangePassword";
|
|||||||
import ForgottenPassword from "./pages/ForgottenPassword";
|
import ForgottenPassword from "./pages/ForgottenPassword";
|
||||||
import ResetPassword from "./pages/ResetPassword";
|
import ResetPassword from "./pages/ResetPassword";
|
||||||
|
|
||||||
import CreateTodo from "./pages/CreateTodo";
|
|
||||||
import EditTodo from "./pages/EditTodo";
|
|
||||||
import Todos from "./pages/Todos";
|
|
||||||
|
|
||||||
const Router = () => (
|
const Router = () => (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
@ -33,30 +28,6 @@ const Router = () => (
|
|||||||
/>
|
/>
|
||||||
<Route path="/forgotten-password/" element={<ForgottenPassword />} />
|
<Route path="/forgotten-password/" element={<ForgottenPassword />} />
|
||||||
<Route path="/reset-password/:token/" element={<ResetPassword />} />
|
<Route path="/reset-password/:token/" element={<ResetPassword />} />
|
||||||
<Route
|
|
||||||
path="/"
|
|
||||||
element={
|
|
||||||
<RequireAuth>
|
|
||||||
<Todos />
|
|
||||||
</RequireAuth>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/todos/new/"
|
|
||||||
element={
|
|
||||||
<RequireAuth>
|
|
||||||
<CreateTodo />
|
|
||||||
</RequireAuth>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/todos/:id/"
|
|
||||||
element={
|
|
||||||
<RequireAuth>
|
|
||||||
<EditTodo />
|
|
||||||
</RequireAuth>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
import Checkbox from "@mui/material/Checkbox";
|
|
||||||
import IconButton from "@mui/material/IconButton";
|
|
||||||
import ListItem from "@mui/material/ListItem";
|
|
||||||
import ListItemButton from "@mui/material/ListItemButton";
|
|
||||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
|
||||||
import ListItemText from "@mui/material/ListItemText";
|
|
||||||
import Skeleton from "@mui/material/Skeleton";
|
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
|
||||||
import { format } from "date-fns";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
import { Todo as TodoModel } from "../models";
|
|
||||||
import { useDeleteTodoMutation } from "../queries";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
todo?: TodoModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Todo = ({ todo }: IProps) => {
|
|
||||||
const { mutateAsync: deleteTodo } = useDeleteTodoMutation();
|
|
||||||
let secondary;
|
|
||||||
if (todo === undefined) {
|
|
||||||
secondary = <Skeleton width="200px" />;
|
|
||||||
} else if (todo.due !== null) {
|
|
||||||
secondary = format(todo.due, "P");
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ListItem
|
|
||||||
secondaryAction={
|
|
||||||
<IconButton
|
|
||||||
disabled={todo === undefined}
|
|
||||||
edge="end"
|
|
||||||
onClick={() => deleteTodo(todo?.id!)}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ListItemButton
|
|
||||||
component={Link}
|
|
||||||
disabled={todo === undefined}
|
|
||||||
to={`/todos/${todo?.id}/`}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Checkbox
|
|
||||||
checked={todo?.complete ?? false}
|
|
||||||
disabled
|
|
||||||
disableRipple
|
|
||||||
edge="start"
|
|
||||||
tabIndex={-1}
|
|
||||||
/>
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText
|
|
||||||
primary={todo?.task ?? <Skeleton />}
|
|
||||||
secondary={secondary}
|
|
||||||
/>
|
|
||||||
</ListItemButton>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default Todo;
|
|
@ -1,44 +0,0 @@
|
|||||||
import { Form, Formik } from "formik";
|
|
||||||
import * as yup from "yup";
|
|
||||||
|
|
||||||
import CheckboxField from "../components/CheckboxField";
|
|
||||||
import DateField from "../components/DateField";
|
|
||||||
import FormActions from "../components/FormActions";
|
|
||||||
import TextField from "../components/TextField";
|
|
||||||
import type { ITodoData } from "../queries";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
initialValues: ITodoData;
|
|
||||||
label: string;
|
|
||||||
onSubmit: (data: ITodoData) => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validationSchema = yup.object({
|
|
||||||
complete: yup.boolean(),
|
|
||||||
due: yup.date().nullable(),
|
|
||||||
task: yup.string().required("Required"),
|
|
||||||
});
|
|
||||||
|
|
||||||
const TodoForm = ({ initialValues, label, onSubmit }: IProps) => (
|
|
||||||
<Formik<ITodoData>
|
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
validationSchema={validationSchema}
|
|
||||||
>
|
|
||||||
{({ dirty, isSubmitting }) => (
|
|
||||||
<Form>
|
|
||||||
<TextField fullWidth label="Task" name="task" required />
|
|
||||||
<DateField fullWidth label="Due" name="due" />
|
|
||||||
<CheckboxField fullWidth label="Complete" name="complete" />
|
|
||||||
<FormActions
|
|
||||||
disabled={!dirty}
|
|
||||||
isSubmitting={isSubmitting}
|
|
||||||
label={label}
|
|
||||||
links={[{ label: "Back", to: "/" }]}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default TodoForm;
|
|
@ -1,36 +0,0 @@
|
|||||||
import { useContext } from "react";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import TodoForm from "../components/TodoForm";
|
|
||||||
import Title from "../components/Title";
|
|
||||||
import type { ITodoData } from "../queries";
|
|
||||||
import { useCreateTodoMutation } from "../queries";
|
|
||||||
import { ToastContext } from "../ToastContext";
|
|
||||||
|
|
||||||
const CreateTodo = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { addToast } = useContext(ToastContext);
|
|
||||||
const { mutateAsync: createTodo } = useCreateTodoMutation();
|
|
||||||
|
|
||||||
const onSubmit = async (data: ITodoData) => {
|
|
||||||
try {
|
|
||||||
await createTodo(data);
|
|
||||||
navigate("/");
|
|
||||||
} catch {
|
|
||||||
addToast("Try Again", "error");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Title title="Create a Todo" />
|
|
||||||
<TodoForm
|
|
||||||
initialValues={{ complete: false, due: null, task: "" }}
|
|
||||||
label="Create"
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CreateTodo;
|
|
@ -1,52 +0,0 @@
|
|||||||
import Skeleton from "@mui/material/Skeleton";
|
|
||||||
import { useContext } from "react";
|
|
||||||
import { useNavigate, useParams } from "react-router";
|
|
||||||
|
|
||||||
import TodoForm from "../components/TodoForm";
|
|
||||||
import Title from "../components/Title";
|
|
||||||
import type { ITodoData } from "../queries";
|
|
||||||
import { useEditTodoMutation, useTodoQuery } from "../queries";
|
|
||||||
import { ToastContext } from "../ToastContext";
|
|
||||||
|
|
||||||
interface Iparams {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditTodo = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const params = useParams<keyof Iparams>() as Iparams;
|
|
||||||
const todoId = parseInt(params.id, 10);
|
|
||||||
const { addToast } = useContext(ToastContext);
|
|
||||||
const { data: todo } = useTodoQuery(todoId);
|
|
||||||
const { mutateAsync: editTodo } = useEditTodoMutation(todoId);
|
|
||||||
|
|
||||||
const onSubmit = async (data: ITodoData) => {
|
|
||||||
try {
|
|
||||||
await editTodo(data);
|
|
||||||
navigate("/");
|
|
||||||
} catch {
|
|
||||||
addToast("Try again", "error");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Title title="Edit todo" />
|
|
||||||
{todo === undefined ? (
|
|
||||||
<Skeleton height="80px" />
|
|
||||||
) : (
|
|
||||||
<TodoForm
|
|
||||||
initialValues={{
|
|
||||||
complete: todo.complete,
|
|
||||||
due: todo.due,
|
|
||||||
task: todo.task,
|
|
||||||
}}
|
|
||||||
label="Edit"
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditTodo;
|
|
@ -1,38 +0,0 @@
|
|||||||
import Fab from "@mui/material/Fab";
|
|
||||||
import List from "@mui/material/List";
|
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
|
||||||
import { Link, Navigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import Todo from "../components/Todo";
|
|
||||||
import { useTodosQuery } from "../queries";
|
|
||||||
|
|
||||||
const Todos = () => {
|
|
||||||
const { data: todos } = useTodosQuery();
|
|
||||||
|
|
||||||
if (todos?.length === 0) {
|
|
||||||
return <Navigate to="/todos/new/" />;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<List>
|
|
||||||
{todos !== undefined
|
|
||||||
? todos.map((todo) => <Todo key={todo.id} todo={todo} />)
|
|
||||||
: [1, 2, 3].map((id) => <Todo key={-id} />)}
|
|
||||||
</List>
|
|
||||||
<Fab
|
|
||||||
component={Link}
|
|
||||||
sx={{
|
|
||||||
bottom: (theme) => theme.spacing(2),
|
|
||||||
position: "fixed",
|
|
||||||
right: (theme) => theme.spacing(2),
|
|
||||||
}}
|
|
||||||
to="/todos/new/"
|
|
||||||
>
|
|
||||||
<AddIcon />
|
|
||||||
</Fab>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Todos;
|
|
@ -1,72 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
import { Todo } from "./models";
|
|
||||||
import { useMutation, useQuery } from "./query";
|
|
||||||
|
|
||||||
export const STALE_TIME = 1000 * 60 * 5; // 5 mins
|
|
||||||
|
|
||||||
export const useTodosQuery = () =>
|
|
||||||
useQuery<Todo[]>(
|
|
||||||
["todos"],
|
|
||||||
async () => {
|
|
||||||
const response = await axios.get("/todos/");
|
|
||||||
return response.data.todos.map((json: any) => new Todo(json));
|
|
||||||
},
|
|
||||||
{ staleTime: STALE_TIME },
|
|
||||||
);
|
|
||||||
|
|
||||||
export const useTodoQuery = (id: number) => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useQuery<Todo>(
|
|
||||||
["todos", id.toString()],
|
|
||||||
async () => {
|
|
||||||
const response = await axios.get(`/todos/${id}/`);
|
|
||||||
return new Todo(response.data);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
initialData: () => {
|
|
||||||
return queryClient
|
|
||||||
.getQueryData<Todo[]>(["todos"])
|
|
||||||
?.filter((todo: Todo) => todo.id === id)[0];
|
|
||||||
},
|
|
||||||
staleTime: STALE_TIME,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ITodoData {
|
|
||||||
complete: boolean;
|
|
||||||
due: Date | null;
|
|
||||||
task: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCreateTodoMutation = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation(
|
|
||||||
async (data: ITodoData) => await axios.post("/todos/", data),
|
|
||||||
{
|
|
||||||
onSuccess: () => queryClient.invalidateQueries(["todos"]),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useEditTodoMutation = (id: number) => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation(
|
|
||||||
async (data: ITodoData) => await axios.put(`/todos/${id}/`, data),
|
|
||||||
{
|
|
||||||
onSuccess: () => queryClient.invalidateQueries(["todos"]),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDeleteTodoMutation = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation(
|
|
||||||
async (id: number) => await axios.delete(`/todos/${id}/`),
|
|
||||||
{
|
|
||||||
onSuccess: () => queryClient.invalidateQueries(["todos"]),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user