53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/src/pages/login.js:
--------------------------------------------------------------------------------
1 | // pages/login.js
2 | import { useRouter } from "next/router";
3 | import { Magic } from "magic-sdk";
4 |
5 | export default function Login() {
6 | const router = useRouter();
7 | const handleSubmit = async (event) => {
8 | event.preventDefault();
9 |
10 | const { elements } = event.target;
11 |
12 | // the Magic code
13 | const did = await new Magic(
14 | process.env.NEXT_PUBLIC_MAGIC_PUB_KEY
15 | ).auth.loginWithMagicLink({ email: elements.email.value });
16 |
17 | const authRequest = await fetch("/api/login", {
18 | method: "POST",
19 | headers: { Authorization: `Bearer ${did}` },
20 | });
21 |
22 | if (authRequest.ok) {
23 | // We successfully logged in, our API
24 | // set authorization cookies and now we
25 | // can redirect to the dashboard!
26 | router.push("/todos");
27 | } else {
28 | /* handle errors */
29 | }
30 | };
31 |
32 | return (
33 |
34 |
35 | Log in to Sanity-Next Todo
36 |
37 |
With ✨Magic Link✨
38 |
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/Todo.js:
--------------------------------------------------------------------------------
1 | // src/components/Todo.js
2 |
3 | import { useState, useContext } from "react";
4 | // import a simple date formatting library
5 | import dayjs from "dayjs";
6 | // import a trashcan icon for our delete button
7 | import { RiDeleteBin5Line } from "react-icons/ri";
8 | import { TodoContext } from "../pages/todos"
9 |
10 | export default function Todo({ todo }) {
11 | //with useContext we do not need to pass handleDelete to
12 | const { handleDelete, fetchTodos } = useContext(TodoContext)
13 | //setting states for the isCompleted boolean and a date completed
14 | const [isCompleted, setIsCompleted] = useState(todo.isCompleted);
15 | const [completedTime, setCompletedTime] = useState(todo.completedAt);
16 |
17 | //function that syncs the completed checkbox with Sanity
18 | const handleToggle = async (e) => {
19 | e.preventDefault();
20 | const result = await fetch("/api/todo", {
21 | method: "PUT",
22 | headers: {
23 | Accept: "application/json",
24 | "Content-Type": "application/json",
25 | },
26 | body: JSON.stringify({
27 | id: todo._id,
28 | //passes isCompleted React state to Sanity
29 | isCompleted: isCompleted,
30 | completedAt: todo.completedAt,
31 | }),
32 | });
33 |
34 | const { status, completedAt } = await result.json();
35 | await fetchTodos();
36 | //pass our Sanity results back into React
37 | setIsCompleted(status);
38 | setCompletedTime(completedAt);
39 |
40 | };
41 | return (
42 |
47 |
53 | {/*if todo is done, cross it out and turn it gray*/}
54 |
59 | {todo.text}
60 |
61 |
62 | {/*if todo is done, show completedTime
63 | if not done, show due date */}
64 | {todo.isCompleted
65 | ? `Done ${dayjs(completedTime).format("MMM D, YYYY")}`
66 | : `Due ${dayjs(todo.dueDate).format("MMM D, YYYY")}`}
67 |
68 |
77 |
78 | );
79 | }
--------------------------------------------------------------------------------
/src/pages/todos.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, createContext } from "react";
2 | //we must import the datepicker's css modules manually
3 | //so it plays nice with Next.
4 | import DatePicker from "react-date-picker/dist/entry.nostyle";
5 | import "react-date-picker/dist/DatePicker.css";
6 | import "react-calendar/dist/Calendar.css";
7 | import useAuth from "../hooks/useAuth";
8 | import Logout from "../components/Logout";
9 | import client from "../lib/sanity/client";
10 | import TodoList from "../components/TodoList"
11 |
12 | export const TodoContext = createContext()
13 |
14 | export default function Todos() {
15 | const { user, loading } = useAuth();
16 | const [todoList, setTodoList] = useState([]);
17 | //create a state for the text in the todo input form
18 | const [userInput, setUserInput] = useState("");
19 | //create a state for the due date chosen in the datepicker
20 | const [dueDate, setDueDate] = useState("");
21 | //set an error message if either input is missing
22 | const [errMessage, setErrMessage] = useState("");
23 |
24 | //after the useState hooks
25 | const fetchTodos = async () => {
26 | let fetchedTodos;
27 | //make sure the user is loaded
28 | if (!loading) {
29 | //pass userEmail as a query parameter
30 | fetchedTodos = await client.fetch(
31 | `*[_type=="todo" && userEmail==$userEmail] | order(dueDate asc)
32 | {_id, text, createdAt, dueDate, isCompleted, completedAt, userEmail}`,
33 | {
34 | userEmail: user.email,
35 | });
36 | //insert our response in the todoList state
37 | setTodoList(fetchedTodos);
38 | }
39 | };
40 |
41 | useEffect(
42 | () => {
43 | //now it will fetch todos on page load...
44 | fetchTodos();
45 | },
46 | //this dependecy array tells React to run the
47 | //hook again whenever loading or user values change
48 | [loading, user]
49 | );
50 |
51 | //FOR THE INPUT FORM:
52 | const handleChange = (e) => {
53 | e.preventDefault();
54 | setUserInput(e.target.value);
55 | };
56 |
57 | //FOR THE SUBMIT BUTTON:
58 | const handleSubmit = async (e) => {
59 | e.preventDefault();
60 | //if either part of the form isn't filled out
61 | //set an error message and exit
62 | if (userInput.length == 0 || dueDate == "") {
63 | setErrMessage("Todo text and due date must be filled out.");
64 | } else {
65 | //otherwise send the todo to our api
66 | // (we'll make this next!)
67 | await fetch("/api/todo", {
68 | method: "POST",
69 | body: JSON.stringify({
70 | text: userInput,
71 | dueDate: dueDate,
72 | user: user.email,
73 | }),
74 | });
75 | await fetchTodos();
76 | // Clear all inputs after the todo is sent to Sanity
77 | setUserInput("");
78 | setErrMessage("");
79 | setDueDate("");
80 | }
81 | };
82 | const handleDelete = async (selectedTodo) => {
83 | await fetch("/api/todo", {
84 | method: "DELETE",
85 | body: selectedTodo._id,
86 | });
87 | //todos will refresh after delete, too
88 | fetchTodos();
89 | };
90 |
91 | return (
92 |
93 | {/* all your rendered JSX */}
94 |
95 |
96 |
97 |
98 |
My To-do List
99 |
100 | {loading ? "Loading..." : `Logged in as ${user.email}`}
101 |